use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::time::UNIX_EPOCH;
const MAX_DEPTH: usize = 16;
#[must_use]
pub fn tree_fingerprint(root: &Path) -> u64 {
let mut hasher = DefaultHasher::new();
walk(root, root, 0, &mut hasher);
hasher.finish()
}
fn walk(root: &Path, dir: &Path, depth: usize, hasher: &mut DefaultHasher) {
if depth > MAX_DEPTH {
return;
}
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
let mut paths: Vec<_> = entries.filter_map(Result::ok).map(|e| e.path()).collect();
paths.sort();
for path in paths {
let relative = path.strip_prefix(root).unwrap_or(&path);
relative.hash(hasher);
let Ok(meta) = std::fs::metadata(&path) else {
continue;
};
if meta.is_dir() {
walk(root, &path, depth + 1, hasher);
continue;
}
meta.len().hash(hasher);
if let Some(nanos) = meta
.modified()
.ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
{
nanos.as_nanos().hash(hasher);
}
}
}