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