1use crate::error::Result;
7use crate::objects::{ObjectId, TreeEntry, TreeRefIter};
8
9use super::{Odb, SessionTree};
10
11const MODE_TREE: u32 = 0o040000;
13
14impl Odb {
15 pub fn tree_entry_at_path(&self, tree: &ObjectId, path: &[u8]) -> Result<Option<TreeEntry>> {
41 let mut current = *tree;
42 let mut components = path.split(|&b| b == b'/').peekable();
43 while let Some(name) = components.next() {
44 let Some((mode, oid)) = self.tree_child(¤t, name)? else {
45 return Ok(None);
46 };
47 if components.peek().is_none() {
48 return Ok(Some(TreeEntry {
49 mode,
50 name: name.to_vec(),
51 oid,
52 }));
53 }
54 if mode != MODE_TREE {
55 return Ok(None);
56 }
57 current = oid;
58 }
59 Ok(None)
60 }
61
62 fn tree_child(&self, tree: &ObjectId, name: &[u8]) -> Result<Option<(u32, ObjectId)>> {
64 match self.cached_tree_for_walk(tree)? {
65 SessionTree::Parsed(entries) => Ok(entries
66 .iter()
67 .find(|e| e.name == name)
68 .map(|e| (e.mode, e.oid))),
69 SessionTree::Raw(data) => {
70 for entry in TreeRefIter::with_oid_len(&data, self.hash_algo().len()) {
71 let entry = entry?;
72 if entry.name == name {
73 return Ok(Some((entry.mode, entry.oid)));
74 }
75 }
76 Ok(None)
77 }
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 #![allow(clippy::expect_used, clippy::unwrap_used)]
85
86 use super::*;
87 use crate::error::Error;
88 use crate::objects::{serialize_tree, ObjectKind};
89 use tempfile::TempDir;
90
91 fn write_tree(odb: &Odb, mut entries: Vec<TreeEntry>) -> ObjectId {
92 entries.sort_by(|a, b| {
93 crate::objects::tree_entry_cmp(
94 &a.name,
95 a.mode == MODE_TREE,
96 &b.name,
97 b.mode == MODE_TREE,
98 )
99 });
100 odb.write(ObjectKind::Tree, &serialize_tree(&entries))
101 .unwrap()
102 }
103
104 fn entry(mode: u32, name: &[u8], oid: ObjectId) -> TreeEntry {
105 TreeEntry {
106 mode,
107 name: name.to_vec(),
108 oid,
109 }
110 }
111
112 fn fixture(odb: &Odb) -> (ObjectId, ObjectId, ObjectId, ObjectId) {
114 let blob_a = odb.write(ObjectKind::Blob, b"a\n").unwrap();
115 let blob_b = odb.write(ObjectKind::Blob, b"b\n").unwrap();
116 let nested = write_tree(odb, vec![entry(0o100755, b"b", blob_b)]);
117 let dir = write_tree(
118 odb,
119 vec![
120 entry(0o100644, b"a", blob_a),
121 entry(MODE_TREE, b"nested", nested),
122 ],
123 );
124 let gitlink = ObjectId::from_hex("1111111111111111111111111111111111111111").unwrap();
125 let root = write_tree(
126 odb,
127 vec![
128 entry(0o100644, b"file", blob_a),
129 entry(0o120000, b"link", blob_b),
130 entry(0o160000, b"sub", gitlink),
131 entry(MODE_TREE, b"dir", dir),
132 ],
133 );
134 (root, dir, nested, blob_b)
135 }
136
137 #[test]
138 fn finds_entries_of_every_mode() {
139 let tmp = TempDir::new().unwrap();
140 let odb = Odb::new(tmp.path());
141 let (root, dir, nested, blob_b) = fixture(&odb);
142
143 let found = odb
144 .tree_entry_at_path(&root, b"dir/nested/b")
145 .unwrap()
146 .unwrap();
147 assert_eq!(found, entry(0o100755, b"b", blob_b));
148 assert_eq!(
149 odb.tree_entry_at_path(&root, b"dir").unwrap().unwrap(),
150 entry(MODE_TREE, b"dir", dir)
151 );
152 assert_eq!(
153 odb.tree_entry_at_path(&root, b"dir/nested")
154 .unwrap()
155 .unwrap()
156 .oid,
157 nested
158 );
159 assert_eq!(
160 odb.tree_entry_at_path(&root, b"link")
161 .unwrap()
162 .unwrap()
163 .mode,
164 0o120000
165 );
166 assert_eq!(
167 odb.tree_entry_at_path(&root, b"sub").unwrap().unwrap().mode,
168 0o160000
169 );
170 }
171
172 #[test]
173 fn missing_paths_are_none() {
174 let tmp = TempDir::new().unwrap();
175 let odb = Odb::new(tmp.path());
176 let (root, ..) = fixture(&odb);
177
178 for path in [
179 &b"absent"[..],
180 b"dir/absent",
181 b"dir/nested/b/deeper",
182 b"file/child",
183 b"sub/child",
184 b"",
185 b"dir/",
186 b"/dir",
187 b"dir//a",
188 b"di",
189 ] {
190 assert_eq!(
191 odb.tree_entry_at_path(&root, path).unwrap(),
192 None,
193 "{path:?}"
194 );
195 }
196 }
197
198 #[test]
199 fn parsed_and_raw_cache_entries_agree() {
200 let tmp = TempDir::new().unwrap();
201 let odb = Odb::new(tmp.path());
202 let (root, ..) = fixture(&odb);
203 let raw = odb.tree_entry_at_path(&root, b"dir/a").unwrap();
204
205 let fresh = Odb::new(tmp.path());
206 fresh.cached_tree_entries(&root).unwrap();
207 assert_eq!(fresh.tree_entry_at_path(&root, b"dir/a").unwrap(), raw);
208 assert!(raw.is_some());
209 }
210
211 #[test]
212 fn non_tree_roots_and_missing_trees_are_errors() {
213 let tmp = TempDir::new().unwrap();
214 let odb = Odb::new(tmp.path());
215 let blob = odb.write(ObjectKind::Blob, b"not a tree").unwrap();
216 assert!(matches!(
217 odb.tree_entry_at_path(&blob, b"x"),
218 Err(Error::CorruptObject(_))
219 ));
220
221 let missing = ObjectId::from_hex("2222222222222222222222222222222222222222").unwrap();
222 assert!(matches!(
223 odb.tree_entry_at_path(&missing, b"x"),
224 Err(Error::ObjectNotFound(_))
225 ));
226 }
227}