use anyhow::Result;
use blazehash::manifest_loader::load_manifest;
use std::collections::HashMap;
use std::path::PathBuf;
pub enum DiffEntry {
Added(PathBuf),
Removed(PathBuf),
Modified { path: PathBuf },
Moved { from: PathBuf, to: PathBuf },
}
pub fn run(
paths: &[PathBuf],
recursive: bool,
compare_by: &str,
show_identical: bool,
) -> Result<bool> {
if paths.len() < 3 {
anyhow::bail!("usage: blazehash diff <left> <right>");
}
let left = &paths[1];
let right = &paths[2];
if left.is_dir() && right.is_dir() {
use blazehash::folder_diff::{diff_folders, print_entry, print_summary, CompareBy};
let cmp = match compare_by {
"paranoid" => CompareBy::Paranoid,
"size-time" => CompareBy::SizeTime,
"name" => CompareBy::Name,
_ => CompareBy::Content,
};
let result = diff_folders(left, right, recursive, cmp)?;
for e in &result.entries {
print_entry(e, show_identical);
}
print_summary(left, right, &result);
return Ok(result.has_diff());
}
let before_records = load_manifest(left)?;
let after_records = load_manifest(right)?;
let before_map: HashMap<PathBuf, String> = before_records
.iter()
.filter_map(|r| {
r.hashes
.values()
.next()
.map(|h| (r.path.clone(), h.clone()))
})
.collect();
let after_map: HashMap<PathBuf, String> = after_records
.iter()
.filter_map(|r| {
r.hashes
.values()
.next()
.map(|h| (r.path.clone(), h.clone()))
})
.collect();
let before_by_hash: HashMap<String, PathBuf> = before_map
.iter()
.map(|(p, h)| (h.clone(), p.clone()))
.collect();
let mut diffs: Vec<DiffEntry> = Vec::new();
let mut moved_froms: std::collections::HashSet<PathBuf> = Default::default();
for (path, hash) in &after_map {
match before_map.get(path) {
None => {
if let Some(from) = before_by_hash.get(hash) {
if !after_map.contains_key(from) {
moved_froms.insert(from.clone());
diffs.push(DiffEntry::Moved {
from: from.clone(),
to: path.clone(),
});
continue;
}
}
diffs.push(DiffEntry::Added(path.clone()));
}
Some(bh) if bh != hash => diffs.push(DiffEntry::Modified { path: path.clone() }),
_ => {}
}
}
for path in before_map.keys() {
if !after_map.contains_key(path) && !moved_froms.contains(path) {
diffs.push(DiffEntry::Removed(path.clone()));
}
}
let has_diff = !diffs.is_empty();
for d in &diffs {
match d {
DiffEntry::Added(p) => println!("[+] ADDED {}", p.display()),
DiffEntry::Removed(p) => println!("[-] REMOVED {}", p.display()),
DiffEntry::Modified { path } => println!("[!] MODIFIED {}", path.display()),
DiffEntry::Moved { from, to } => {
println!("[*] MOVED {} <- {}", to.display(), from.display())
}
}
}
if !has_diff {
println!("[=] Manifests are identical");
}
Ok(has_diff)
}