git_index/extension/tree/
verify.rs1use std::cmp::Ordering;
2
3use bstr::{BString, ByteSlice};
4
5use crate::extension::Tree;
6
7#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11 #[error("The entry {entry_id} at path '{name}' in parent tree {parent_id} wasn't found in the nodes children, making it incomplete")]
12 MissingTreeDirectory {
13 parent_id: git_hash::ObjectId,
14 entry_id: git_hash::ObjectId,
15 name: BString,
16 },
17 #[error("The tree with id {oid} wasn't found in the object database")]
18 TreeNodeNotFound { oid: git_hash::ObjectId },
19 #[error("The tree with id {oid} should have {expected_childcount} children, but its cached representation had {actual_childcount} of them")]
20 TreeNodeChildcountMismatch {
21 oid: git_hash::ObjectId,
22 expected_childcount: usize,
23 actual_childcount: usize,
24 },
25 #[error("The root tree was named '{name}', even though it should be empty")]
26 RootWithName { name: BString },
27 #[error(
28 "Expected not more than {expected} entries to be reachable from the top-level, but actual count was {actual}"
29 )]
30 EntriesCount { actual: u32, expected: u32 },
31 #[error(
32 "Parent tree '{parent_id}' contained out-of order trees prev = '{previous_path}' and next = '{current_path}'"
33 )]
34 OutOfOrder {
35 parent_id: git_hash::ObjectId,
36 current_path: BString,
37 previous_path: BString,
38 },
39}
40
41impl Tree {
42 pub fn verify<F>(&self, use_find: bool, mut find: F) -> Result<(), Error>
44 where
45 F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>>,
46 {
47 fn verify_recursive<F>(
48 parent_id: git_hash::ObjectId,
49 children: &[Tree],
50 mut find_buf: Option<&mut Vec<u8>>,
51 find: &mut F,
52 ) -> Result<Option<u32>, Error>
53 where
54 F: for<'a> FnMut(&git_hash::oid, &'a mut Vec<u8>) -> Option<git_object::TreeRefIter<'a>>,
55 {
56 if children.is_empty() {
57 return Ok(None);
58 }
59 let mut entries = 0;
60 let mut prev = None::<&Tree>;
61 for child in children {
62 entries += child.num_entries.unwrap_or(0);
63 if let Some(prev) = prev {
64 if prev.name.cmp(&child.name) != Ordering::Less {
65 return Err(Error::OutOfOrder {
66 parent_id,
67 previous_path: prev.name.as_bstr().into(),
68 current_path: child.name.as_bstr().into(),
69 });
70 }
71 }
72 prev = Some(child);
73 }
74 if let Some(buf) = find_buf.as_mut() {
75 let tree_entries = find(&parent_id, buf).ok_or(Error::TreeNodeNotFound { oid: parent_id })?;
76 let mut num_entries = 0;
77 for entry in tree_entries
78 .filter_map(Result::ok)
79 .filter(|e| e.mode == git_object::tree::EntryMode::Tree)
80 {
81 children
82 .binary_search_by(|e| e.name.as_bstr().cmp(entry.filename))
83 .map_err(|_| Error::MissingTreeDirectory {
84 parent_id,
85 entry_id: entry.oid.to_owned(),
86 name: entry.filename.to_owned(),
87 })?;
88 num_entries += 1;
89 }
90
91 if num_entries != children.len() {
92 return Err(Error::TreeNodeChildcountMismatch {
93 oid: parent_id,
94 expected_childcount: num_entries,
95 actual_childcount: children.len(),
96 });
97 }
98 }
99 for child in children {
100 #[allow(clippy::needless_option_as_deref)]
102 let actual_num_entries = verify_recursive(child.id, &child.children, find_buf.as_deref_mut(), find)?;
103 if let Some((actual, num_entries)) = actual_num_entries.zip(child.num_entries) {
104 if actual > num_entries {
105 return Err(Error::EntriesCount {
106 actual,
107 expected: num_entries,
108 });
109 }
110 }
111 }
112 Ok(entries.into())
113 }
114
115 if !self.name.is_empty() {
116 return Err(Error::RootWithName {
117 name: self.name.as_bstr().into(),
118 });
119 }
120
121 let mut buf = Vec::new();
122 let declared_entries = verify_recursive(self.id, &self.children, use_find.then_some(&mut buf), &mut find)?;
123 if let Some((actual, num_entries)) = declared_entries.zip(self.num_entries) {
124 if actual > num_entries {
125 return Err(Error::EntriesCount {
126 actual,
127 expected: num_entries,
128 });
129 }
130 }
131
132 Ok(())
133 }
134}