use super::PathContext;
use crate::{
checkpoints::{
SnapshotEligibility, SnapshotTool, capture_file_snapshot, classify_snapshot_eligibility,
},
support::path::lexical_normalize,
};
use std::{
fs,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
#[derive(Clone, Copy)]
pub(in crate::tools) struct MutationContext<'a> {
pub(in crate::tools) paths: PathContext<'a>,
pub(in crate::tools) mutation_locks:
&'a Mutex<std::collections::HashMap<PathBuf, Arc<Mutex<()>>>>,
pub(in crate::tools) checkpoint_context: &'a Mutex<Option<crate::checkpoints::SnapshotContext>>,
}
impl MutationContext<'_> {
pub(in crate::tools) fn file_lock(&self, path: &Path) -> anyhow::Result<Arc<Mutex<()>>> {
let key = path
.canonicalize()
.unwrap_or_else(|_| lexical_normalize(path));
let mut locks = self
.mutation_locks
.lock()
.map_err(|_| anyhow::anyhow!("tool mutation lock registry mutex poisoned"))?;
Ok(locks
.entry(key)
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone())
}
fn snapshot_eligible(&self, path: &Path) -> bool {
let Some(context) = self.checkpoint_context() else {
return false;
};
matches!(
classify_snapshot_eligibility(path, self.paths.cwd_canonical, &context.paths),
SnapshotEligibility::Eligible
)
}
pub(in crate::tools) fn pre_snapshot_bytes(
&self,
path: &Path,
) -> anyhow::Result<Option<Vec<u8>>> {
if !path.exists() || !self.snapshot_eligible(path) {
return Ok(None);
}
Ok(Some(fs::read(path)?))
}
pub(in crate::tools) fn record_file_snapshot(
&self,
tool: SnapshotTool,
path: &Path,
pre: Option<&[u8]>,
post: Option<&[u8]>,
) {
let Some(context) = self.checkpoint_context() else {
return;
};
if let Err(error) =
capture_file_snapshot(&context, self.paths.cwd_canonical, tool, path, pre, post)
{
eprintln!(
"warning: checkpoint snapshot failed: {}",
crate::output::redact_sensitive_text(&error.to_string())
);
}
}
fn checkpoint_context(&self) -> Option<crate::checkpoints::SnapshotContext> {
self.checkpoint_context
.lock()
.ok()
.and_then(|slot| slot.clone())
}
}