use std::collections::HashMap;
use std::io::BufRead;
use std::path::{Path, PathBuf};
use anyhow::{Context, bail};
use clap::Parser;
use pijul_core::pristine::{Base32, ChannelRef, Hash};
use pijul_core::{MutTxnT, MutTxnTExt};
use crate::commands::common_opts::RepoPath;
#[derive(Parser, Debug)]
pub struct Replay {
#[clap(flatten)]
base: RepoPath,
#[clap(long = "log", value_name = "PATH")]
log: Option<PathBuf>,
#[clap(long = "count", short = 'n', value_name = "N")]
count: Option<usize>,
}
impl Replay {
pub fn repository_path(&mut self) -> Option<&Path> {
self.base.repo_path()
}
pub fn run(mut self) -> Result<(), anyhow::Error> {
let repo = self.base.find_root()?;
let log_path = self
.log
.clone()
.unwrap_or_else(|| repo.path.join(pijul_core::DOT_DIR).join("replay.log"));
let file = std::fs::File::open(&log_path)
.with_context(|| format!("Opening replay log {}", log_path.display()))?;
let reader = std::io::BufReader::new(file);
let txn = repo.pristine.arc_txn_begin()?;
let mut channels: HashMap<String, ChannelRef<_>> = HashMap::new();
let mut touched = pijul_core::unrecord::TouchedInodes::new();
for (i, line) in reader.lines().enumerate() {
let line = line?;
if line.trim().is_empty() {
continue;
}
if let Some(max) = self.count {
if i >= max {
eprintln!("Stopping after {} operations", max);
break;
}
}
let v: serde_json::Value =
serde_json::from_str(&line).with_context(|| format!("Parsing log line {}", i))?;
let op = v["op"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing `op` on line {}", i))?;
let channel_name = v["channel"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing `channel` on line {}", i))?;
let hash_str = v["hash"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing `hash` on line {}", i))?;
let hash = Hash::from_base32(hash_str.as_bytes())
.ok_or_else(|| anyhow::anyhow!("Invalid hash {:?} on line {}", hash_str, i))?;
if !channels.contains_key(channel_name) {
let sm: pijul_core::small_string::SmallString = channel_name.parse()?;
let ch = txn.write().open_or_create_channel(&sm)?;
channels.insert(channel_name.to_string(), ch);
}
let channel = channels.get(channel_name).unwrap();
eprintln!("[{}] {} {} {}", i, op, channel_name, hash_str);
match op {
"apply" | "pending" => {
let mut ch = channel.write();
txn.write()
.apply_change_rec(&repo.changes, &mut ch, &hash)?;
}
"unrecord" | "unrecord_pending" => {
let salt = v["salt"].as_u64().unwrap_or(0);
txn.write()
.unrecord(&repo.changes, channel, &hash, salt, &mut touched)?;
}
other => bail!("Unknown operation {:?} on line {}", other, i),
}
}
txn.commit()?;
Ok(())
}
}