use std::path::Path;
use std::process::Command;
pub struct WorkspaceSnapshot {
workspace_root: std::path::PathBuf,
tree_hash: String,
index_file: std::path::PathBuf,
}
impl WorkspaceSnapshot {
pub fn take(workspace_root: &Path) -> Option<Self> {
let status = Command::new("git")
.arg("rev-parse")
.arg("--is-inside-work-tree")
.current_dir(workspace_root)
.output()
.ok()?;
if !status.status.success() {
return None;
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let index_file = workspace_root.join(".git").join(format!(
"crabmate_backup_{}_{}.idx",
std::process::id(),
timestamp
));
let add_status = Command::new("git")
.env("GIT_INDEX_FILE", &index_file)
.arg("add")
.arg("-A")
.current_dir(workspace_root)
.output()
.ok()?;
if !add_status.status.success() {
let _ = std::fs::remove_file(&index_file);
return None;
}
let tree_output = Command::new("git")
.env("GIT_INDEX_FILE", &index_file)
.arg("write-tree")
.current_dir(workspace_root)
.output()
.ok()?;
if !tree_output.status.success() {
let _ = std::fs::remove_file(&index_file);
return None;
}
let tree_hash = String::from_utf8_lossy(&tree_output.stdout)
.trim()
.to_string();
Some(Self {
workspace_root: workspace_root.to_path_buf(),
tree_hash,
index_file,
})
}
pub fn restore(&self) -> Result<(), String> {
let checkout_status = Command::new("git")
.env("GIT_INDEX_FILE", &self.index_file)
.arg("checkout")
.arg(&self.tree_hash)
.arg("--")
.arg(".")
.current_dir(&self.workspace_root)
.output()
.map_err(|e| e.to_string())?;
if !checkout_status.status.success() {
return Err(format!(
"git checkout failed: {}",
String::from_utf8_lossy(&checkout_status.stderr)
));
}
let clean_status = Command::new("git")
.env("GIT_INDEX_FILE", &self.index_file)
.arg("clean")
.arg("-fd")
.current_dir(&self.workspace_root)
.output()
.map_err(|e| e.to_string())?;
if !clean_status.status.success() {
return Err(format!(
"git clean failed: {}",
String::from_utf8_lossy(&clean_status.stderr)
));
}
Ok(())
}
}
impl Drop for WorkspaceSnapshot {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.index_file);
}
}