use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub(crate) fn rollback_files(
modified_files: &[PathBuf],
original_contents: &HashMap<PathBuf, String>,
) -> Vec<(PathBuf, std::io::Error)> {
let mut errors = Vec::new();
for file_path in modified_files {
if let Some(original) = original_contents.get(file_path)
&& let Err(e) = std::fs::write(file_path, original)
{
eprintln!("Warning: failed to rollback {}: {}", file_path.display(), e);
errors.push((file_path.clone(), e));
}
}
errors
}
pub(crate) fn report_rollback_errors(errors: &[(PathBuf, std::io::Error)]) {
if errors.is_empty() {
return;
}
eprintln!(
"Warning: {} file(s) could not be rolled back:",
errors.len()
);
for (path, err) in errors {
eprintln!(" - {}: {}", path.display(), err);
}
}
pub(crate) fn atomic_write(path: &Path, content: &str) -> std::io::Result<()> {
let original_perms = std::fs::metadata(path).ok().map(|m| m.permissions());
let tmp_path = path.with_extension("tmp");
std::fs::write(&tmp_path, content)?;
if let Err(e) = std::fs::rename(&tmp_path, path) {
let _ = std::fs::remove_file(&tmp_path);
return Err(e);
}
if let Some(perms) = original_perms {
std::fs::set_permissions(path, perms)?;
}
Ok(())
}
pub(crate) struct BackupGuard {
backup_path: PathBuf,
committed: bool,
}
impl BackupGuard {
pub(crate) fn new(backup_path: PathBuf) -> Self {
Self {
backup_path,
committed: false,
}
}
pub(crate) fn commit(&mut self) {
self.committed = true;
}
}
impl Drop for BackupGuard {
fn drop(&mut self) {
if !self.committed {
let _ = std::fs::remove_file(&self.backup_path);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn test_rollback_files_restores_modified() {
let dir = tempfile::tempdir().expect("failed to create temp dir");
let file1 = dir.path().join("file1.rs");
let file2 = dir.path().join("file2.rs");
std::fs::write(&file1, "original1").expect("write file1");
std::fs::write(&file2, "original2").expect("write file2");
let mut originals = HashMap::new();
originals.insert(file1.clone(), "original1".to_string());
originals.insert(file2.clone(), "original2".to_string());
std::fs::write(&file1, "modified1").expect("modify file1");
let errors = rollback_files(&[file1.clone()], &originals);
assert!(errors.is_empty(), "rollback should succeed");
assert_eq!(
std::fs::read_to_string(&file1).unwrap(),
"original1",
"file1 should be restored"
);
assert_eq!(
std::fs::read_to_string(&file2).unwrap(),
"original2",
"file2 should remain unchanged since it was not in modified list"
);
}
#[rstest]
fn test_rollback_files_skips_untracked() {
let originals = HashMap::new();
let nonexistent = PathBuf::from("/tmp/reinhardt-test-nonexistent-file.rs");
let errors = rollback_files(&[nonexistent], &originals);
assert!(errors.is_empty(), "should skip files not in originals map");
}
}