use std::collections::HashSet;
use crate::error::Result;
use super::{ContentHash, ObjectSource, Tree, TreeEntry};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TreeIntegrityEvent<'a> {
EnterTree { hash: ContentHash, tree: &'a Tree },
BlobLeaf { entry: &'a TreeEntry, path: String },
TreeRef {
parent_hash: ContentHash,
entry: &'a TreeEntry,
},
}
pub fn walk_tree_integrity<S, V>(
source: &S,
roots: impl IntoIterator<Item = ContentHash>,
visitor: &mut V,
) -> Result<()>
where
S: ObjectSource + ?Sized,
V: FnMut(TreeIntegrityEvent<'_>) -> Result<()>,
{
let mut visited = HashSet::new();
for root in roots {
walk_tree_recursive(source, &root, "", &mut visited, visitor)?;
}
Ok(())
}
fn walk_tree_recursive<S, V>(
source: &S,
tree_hash: &ContentHash,
path_prefix: &str,
visited: &mut HashSet<ContentHash>,
visitor: &mut V,
) -> Result<()>
where
S: ObjectSource + ?Sized,
V: FnMut(TreeIntegrityEvent<'_>) -> Result<()>,
{
if visited.contains(tree_hash) {
return Ok(());
}
visited.insert(*tree_hash);
let Some(tree) = source.get_tree(tree_hash)? else {
return Ok(());
};
visitor(TreeIntegrityEvent::EnterTree {
hash: *tree_hash,
tree: &tree,
})?;
for entry in tree.entries() {
let path = if path_prefix.is_empty() {
entry.name().to_string()
} else {
format!("{path_prefix}/{}", entry.name())
};
if entry.blob_hash().is_some() {
visitor(TreeIntegrityEvent::BlobLeaf {
entry,
path: path.clone(),
})?;
} else if let Some(child_hash) = entry.tree_hash() {
visitor(TreeIntegrityEvent::TreeRef {
parent_hash: *tree_hash,
entry,
})?;
walk_tree_recursive(source, &child_hash, &path, visited, visitor)?;
}
}
Ok(())
}