use anyhow::{Context, Result};
use sha1::{Digest, Sha1};
use std::path::Path;
use walkdir::WalkDir;
pub fn verify_tree_integrity(zip_path: &Path, git_dir: &Path) -> Result<bool> {
let repo = git2::Repository::open(git_dir).context("Failed to open git repository")?;
let head = repo.head().context("Failed to get HEAD reference")?;
let commit = head
.peel_to_commit()
.context("Failed to peel HEAD to commit")?;
let tree = commit
.tree()
.context("Failed to get tree from HEAD commit")?;
let mut zip_files: Vec<(String, Vec<u8>)> = Vec::new();
for entry in WalkDir::new(zip_path)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
{
if !entry.file_type().is_file() {
continue;
}
let rel = entry.path().strip_prefix(zip_path).unwrap_or(entry.path());
let rel_str = rel.to_string_lossy().replace('\\', "/");
if let Ok(content) = std::fs::read(entry.path()) {
zip_files.push((rel_str, content));
}
}
zip_files.sort_by(|a, b| a.0.cmp(&b.0));
let mut zip_hasher = Sha1::new();
for (path, content) in &zip_files {
zip_hasher.update(path.as_bytes());
zip_hasher.update((content.len() as u64).to_le_bytes());
zip_hasher.update(content);
}
let zip_digest = zip_hasher.finalize();
let mut git_files: Vec<(String, Vec<u8>)> = Vec::new();
tree.walk(git2::TreeWalkMode::PreOrder, |dir, entry| {
if let Some(git2::ObjectType::Blob) = entry.kind() {
let path = if dir.is_empty() {
entry.name().unwrap_or("").to_string()
} else {
format!("{}{}", dir, entry.name().unwrap_or(""))
};
if let Ok(blob) = repo.find_blob(entry.id()) {
git_files.push((path, blob.content().to_vec()));
}
}
git2::TreeWalkResult::Ok
})?;
git_files.sort_by(|a, b| a.0.cmp(&b.0));
let mut git_hasher = Sha1::new();
for (path, content) in &git_files {
git_hasher.update(path.as_bytes());
git_hasher.update((content.len() as u64).to_le_bytes());
git_hasher.update(content);
}
let git_digest = git_hasher.finalize();
Ok(zip_digest == git_digest)
}