use std::collections::HashSet;
use super::{ContentHash, ObjectSource, Tree, TreeEntry};
use crate::error::Result;
#[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,
},
MissingTree {
hash: ContentHash,
parent_hash: Option<ContentHash>,
path: String,
},
}
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_iterative(source, root, &mut visited, visitor)?;
}
Ok(())
}
struct WalkFrame {
hash: ContentHash,
tree: Tree,
path_prefix: String,
next_entry: usize,
}
fn walk_tree_iterative<S, V>(
source: &S,
root_hash: ContentHash,
visited: &mut HashSet<ContentHash>,
visitor: &mut V,
) -> Result<()>
where
S: ObjectSource + ?Sized,
V: FnMut(TreeIntegrityEvent<'_>) -> Result<()>,
{
if !visited.insert(root_hash) {
return Ok(());
}
let Some(root_tree) = source.get_tree(&root_hash)? else {
visitor(TreeIntegrityEvent::MissingTree {
hash: root_hash,
parent_hash: None,
path: String::new(),
})?;
return Ok(());
};
visitor(TreeIntegrityEvent::EnterTree {
hash: root_hash,
tree: &root_tree,
})?;
let mut stack = vec![WalkFrame {
hash: root_hash,
tree: root_tree,
path_prefix: String::new(),
next_entry: 0,
}];
while let Some(frame) = stack.last_mut() {
let Some(entry) = frame.tree.entries().get(frame.next_entry).cloned() else {
stack.pop();
continue;
};
frame.next_entry += 1;
let path = if frame.path_prefix.is_empty() {
entry.name().to_string()
} else {
format!("{}/{}", frame.path_prefix, entry.name())
};
if entry.blob_hash().is_some() {
visitor(TreeIntegrityEvent::BlobLeaf {
entry: &entry,
path,
})?;
} else if let Some(child_hash) = entry.tree_hash() {
visitor(TreeIntegrityEvent::TreeRef {
parent_hash: frame.hash,
entry: &entry,
})?;
if !visited.insert(child_hash) {
continue;
}
let Some(child_tree) = source.get_tree(&child_hash)? else {
visitor(TreeIntegrityEvent::MissingTree {
hash: child_hash,
parent_hash: Some(frame.hash),
path,
})?;
continue;
};
visitor(TreeIntegrityEvent::EnterTree {
hash: child_hash,
tree: &child_tree,
})?;
stack.push(WalkFrame {
hash: child_hash,
tree: child_tree,
path_prefix: path,
next_entry: 0,
});
}
}
Ok(())
}