use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::path::Path;
use std::process::Command;
pub fn compute_fingerprints(repo_root: &Path, staged_only: bool) -> BTreeMap<String, String> {
let mut fingerprints = BTreeMap::new();
if staged_only {
if let Some(files) = git(repo_root, &["diff", "--cached", "--name-only"]) {
for file in files.lines().filter(|l| !l.is_empty()) {
if let Some(diff) = git(repo_root, &["diff", "--cached", "--", file]) {
fingerprints.insert(file.to_string(), sha256_hex(diff.as_bytes()));
}
}
}
} else {
let diff_files = git(repo_root, &["diff", "HEAD", "--name-only"])
.or_else(|| git(repo_root, &["diff", "--name-only"]));
if let Some(files) = diff_files {
for file in files.lines().filter(|l| !l.is_empty()) {
let diff = git(repo_root, &["diff", "HEAD", "--", file])
.or_else(|| git(repo_root, &["diff", "--", file]));
if let Some(diff) = diff {
fingerprints.insert(file.to_string(), sha256_hex(diff.as_bytes()));
}
}
}
if let Some(files) = git(repo_root, &["diff", "--cached", "--name-only"]) {
for file in files.lines().filter(|l| !l.is_empty()) {
if !fingerprints.contains_key(file)
&& let Some(diff) = git(repo_root, &["diff", "--cached", "--", file])
{
fingerprints.insert(file.to_string(), sha256_hex(diff.as_bytes()));
}
}
}
if let Some(untracked) = git(repo_root, &["ls-files", "--others", "--exclude-standard"]) {
for file in untracked.lines().filter(|l| !l.is_empty()) {
let full_path = repo_root.join(file);
if let Ok(content) = std::fs::read(&full_path) {
fingerprints.insert(file.to_string(), sha256_hex(&content));
}
}
}
}
fingerprints
}
pub fn sha256_hex(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:x}", hasher.finalize())
}
fn git(repo_root: &Path, args: &[&str]) -> Option<String> {
let output = Command::new("git")
.args(["-C", repo_root.to_str()?])
.args(args)
.output()
.ok()?;
if !output.status.success() {
return None;
}
Some(String::from_utf8_lossy(&output.stdout).to_string())
}