use std::cmp::Ordering; use bstr::ByteSlice; use git_hash::ObjectId; use crate::{ extension::{Signature, Tree}, util::{split_at_byte_exclusive, split_at_pos}, }; pub const SIGNATURE: Signature = *b"TREE"; pub mod verify { use bstr::BString; use quick_error::quick_error; quick_error! { #[derive(Debug)] pub enum Error { MissingTreeDirectory { parent_id: git_hash::ObjectId, entry_id: git_hash::ObjectId, name: BString } { display("The entry {} at path '{}' in parent tree {} wasn't found in the nodes children, making it incomplete", entry_id, name, parent_id) } TreeNodeNotFound { oid: git_hash::ObjectId } { display("The tree with id {} wasn't found in the object database", oid) } TreeNodeChildcountMismatch { oid: git_hash::ObjectId, expected_childcount: usize, actual_childcount: usize } { display("The tree with id {} should have {} children, but its cached representation had {} of them", oid, expected_childcount, actual_childcount) } RootWithName { name: BString } { display("The root tree was named '{}', even though it should be empty", name) } EntriesCount {actual: u32, expected: u32 } { display("Expected not more than {} entries to be reachable from the top-level, but actual count was {}", expected, actual) } OutOfOrder { parent_id: git_hash::ObjectId, current_path: BString, previous_path: BString } { display("Parent tree '{}' contained out-of order trees prev = '{}' and next = '{}'", parent_id, previous_path, current_path) } } } } impl Tree { pub fn verify(&self, use_find: bool, mut find: F) -> Result<(), verify::Error> where F: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>, { fn verify_recursive( parent_id: git_hash::ObjectId, children: &[Tree], mut find_buf: Option<&mut Vec>, find: &mut F, ) -> Result, verify::Error> where F: for<'a> FnMut(&git_hash::oid, &'a mut Vec) -> Option>, { if children.is_empty() { return Ok(None); } let mut entries = 0; let mut prev = None::<&Tree>; for child in children { entries += child.num_entries; if let Some(prev) = prev { if prev.name.cmp(&child.name) != Ordering::Less { return Err(verify::Error::OutOfOrder { parent_id, previous_path: prev.name.as_bstr().into(), current_path: child.name.as_bstr().into(), }); } } prev = Some(child); } if let Some(buf) = find_buf.as_mut() { let tree_entries = find(&parent_id, *buf).ok_or(verify::Error::TreeNodeNotFound { oid: parent_id })?; let mut num_entries = 0; for entry in tree_entries .filter_map(Result::ok) .filter(|e| e.mode == git_object::tree::EntryMode::Tree) { children .binary_search_by(|e| e.name.as_bstr().cmp(entry.filename)) .map_err(|_| verify::Error::MissingTreeDirectory { parent_id, entry_id: entry.oid.to_owned(), name: entry.filename.to_owned(), })?; num_entries += 1; } if num_entries != children.len() { return Err(verify::Error::TreeNodeChildcountMismatch { oid: parent_id, expected_childcount: num_entries, actual_childcount: children.len(), }); } } for child in children { // This is actually needed here as it's a mut ref, which isn't copy. We do a re-borrow here. #[allow(clippy::needless_option_as_deref)] let actual_num_entries = verify_recursive(child.id, &child.children, find_buf.as_deref_mut(), find)?; if let Some(actual) = actual_num_entries { if actual > child.num_entries { return Err(verify::Error::EntriesCount { actual, expected: child.num_entries, }); } } } Ok(entries.into()) } if !self.name.is_empty() { return Err(verify::Error::RootWithName { name: self.name.as_bstr().into(), }); } let mut buf = Vec::new(); let declared_entries = verify_recursive(self.id, &self.children, use_find.then(|| &mut buf), &mut find)?; if let Some(actual) = declared_entries { if actual > self.num_entries { return Err(verify::Error::EntriesCount { actual, expected: self.num_entries, }); } } Ok(()) } } /// A recursive data structure pub fn decode(data: &[u8], object_hash: git_hash::Kind) -> Option { let (tree, data) = one_recursive(data, object_hash.len_in_bytes())?; assert!( data.is_empty(), "BUG: should fully consume the entire tree extension chunk, got {} left", data.len() ); Some(tree) } pub fn one_recursive(data: &[u8], hash_len: usize) -> Option<(Tree, &[u8])> { let (path, data) = split_at_byte_exclusive(data, 0)?; let (entry_count, data) = split_at_byte_exclusive(data, b' ')?; let num_entries: u32 = atoi::atoi(entry_count)?; let (subtree_count, data) = split_at_byte_exclusive(data, b'\n')?; let subtree_count: usize = atoi::atoi(subtree_count)?; let (hash, mut data) = split_at_pos(data, hash_len)?; let id = ObjectId::from(hash); let mut subtrees = Vec::with_capacity(subtree_count); for _ in 0..subtree_count { let (tree, rest) = one_recursive(data, hash_len)?; subtrees.push(tree); data = rest; } subtrees.sort_by(|a, b| a.name.cmp(&b.name)); let num_trees = subtrees.len(); subtrees.dedup_by(|a, b| a.name == b.name); if num_trees != subtrees.len() { return None; } Some(( Tree { id, num_entries, name: path.into(), children: subtrees, }, data, )) } #[cfg(test)] mod tests { use super::*; #[test] fn size_of_tree() { assert_eq!(std::mem::size_of::(), 80); } }