use std::path::Path;
use std::process::Command;
pub fn snapshot(project_root: &Path, label: &str) -> Option<String> {
if !has_git(project_root) {
tracing::debug!(" sin repositorio git β inicializando...");
if !init_git(project_root) {
return None;
}
}
let has_changes = check_git_changes(project_root);
if !has_changes {
return current_hash(project_root);
}
let output = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("add")
.arg("-A")
.output();
if output.is_err() {
return None;
}
let output = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("commit")
.arg("-q")
.arg("-m")
.arg(format!("snapshot: {label}"))
.output();
match output {
Ok(o) if o.status.success() => {
let hash = current_hash(project_root);
tracing::debug!(" πΈ snapshot: {label} β {:?}", hash);
hash
}
_ => None,
}
}
pub fn rollback(project_root: &Path, prev_hash: &str, label: &str) -> bool {
tracing::info!(
" β» rollback al commit {} ({label})",
&prev_hash[..8.min(prev_hash.len())]
);
let output = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("reset")
.arg("--hard")
.arg(prev_hash)
.output();
matches!(output, Ok(o) if o.status.success())
}
fn has_git(project_root: &Path) -> bool {
project_root.join(".git").is_dir()
}
fn init_git(project_root: &Path) -> bool {
let _ = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("init")
.arg("-q")
.output();
let _ = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("config")
.arg("user.email")
.arg("regista@localhost")
.output();
let _ = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("config")
.arg("user.name")
.arg("regista")
.output();
has_git(project_root)
}
fn check_git_changes(project_root: &Path) -> bool {
let status = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("diff")
.arg("--quiet")
.arg("--exit-code")
.status();
if status.map(|s| s.code() == Some(1)).unwrap_or(false) {
return true;
}
let status = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("diff")
.arg("--cached")
.arg("--quiet")
.arg("--exit-code")
.status();
if status.map(|s| s.code() == Some(1)).unwrap_or(false) {
return true;
}
let output = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("ls-files")
.arg("--others")
.arg("--exclude-standard")
.output();
output.map(|o| !o.stdout.is_empty()).unwrap_or(false)
}
fn current_hash(project_root: &Path) -> Option<String> {
let output = Command::new("git")
.arg("-C")
.arg(project_root)
.arg("rev-parse")
.arg("HEAD")
.output()
.ok()?;
if output.status.success() {
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
None
}
}