gix_object/tree/
ref_iter.rs1use gix_error::ExnMessageResult;
2use std::ops::ControlFlow;
3
4use gix_error::ExnResult;
5
6use bstr::BStr;
7
8use crate::{TreeRef, TreeRefIter, tree, tree::EntryRef};
9
10pub fn next_entry<'a, I, P>(
29 components: &mut core::iter::Peekable<I>,
30 tree: crate::Data<'a>,
31) -> core::ops::ControlFlow<Option<EntryRef<'a>>, gix_hash::ObjectId>
32where
33 I: Iterator<Item = P>,
34 P: PartialEq<BStr>,
35{
36 if !tree.kind.is_tree() {
37 return ControlFlow::Break(None);
38 }
39
40 let Some(component) = components.next() else {
41 return ControlFlow::Break(None);
42 };
43
44 let Some(entry) = TreeRefIter::from_bytes(tree.data, tree.object_hash)
45 .filter_map(std::result::Result::ok)
46 .find(|entry| component.eq(entry.filename))
47 else {
48 return ControlFlow::Break(None);
49 };
50
51 if components.peek().is_none() {
52 ControlFlow::Break(Some(entry))
53 } else {
54 ControlFlow::Continue(entry.oid.to_owned())
55 }
56}
57
58impl<'a> TreeRefIter<'a> {
59 pub fn from_bytes(data: &'a [u8], hash_kind: gix_hash::Kind) -> TreeRefIter<'a> {
61 TreeRefIter { data, hash_kind }
62 }
63
64 pub fn lookup_entry<I, P>(
73 &self,
74 odb: impl crate::Find,
75 buffer: &'a mut Vec<u8>,
76 path: I,
77 ) -> ExnResult<Option<tree::Entry>>
78 where
79 I: IntoIterator<Item = P>,
80 P: PartialEq<BStr>,
81 {
82 buffer.clear();
83 buffer.extend_from_slice(self.data);
84
85 let mut iter = path.into_iter().peekable();
86 let mut data = crate::Data::new(buffer, crate::Kind::Tree, self.hash_kind);
87
88 loop {
89 data = match next_entry(&mut iter, data) {
90 ControlFlow::Continue(oid) => {
91 let Some(next_tree) = odb.try_find(&oid, buffer)? else {
92 break Ok(None);
93 };
94 next_tree
95 }
96 ControlFlow::Break(v) => break Ok(v.map(Into::into)),
97 }
98 }
99 }
100
101 pub fn lookup_entry_by_path(
110 &self,
111 odb: impl crate::Find,
112 buffer: &'a mut Vec<u8>,
113 relative_path: impl AsRef<std::path::Path>,
114 ) -> ExnResult<Option<tree::Entry>> {
115 self.lookup_entry(
116 odb,
117 buffer,
118 relative_path
119 .as_ref()
120 .components()
121 .map(|c| c.as_os_str().as_encoded_bytes()),
122 )
123 }
124}
125
126impl<'a> TreeRef<'a> {
127 pub fn from_bytes(data: &'a [u8], hash_kind: gix_hash::Kind) -> ExnMessageResult<TreeRef<'a>> {
129 decode::tree(data, hash_kind.len_in_bytes())
130 }
131
132 pub fn bisect_entry(&self, name: &BStr, is_dir: bool) -> Option<EntryRef<'a>> {
136 self.entries
137 .binary_search_by(|entry| tree::name_order(entry.filename, entry.mode.is_tree(), name, is_dir))
138 .ok()
139 .map(|idx| self.entries[idx])
140 }
141
142 pub const fn empty() -> TreeRef<'static> {
146 TreeRef { entries: Vec::new() }
147 }
148}
149
150impl<'a> TreeRefIter<'a> {
151 pub fn entries(self) -> ExnMessageResult<Vec<EntryRef<'a>>> {
153 self.collect()
154 }
155
156 pub fn offset_to_next_entry(&self, buf: &[u8]) -> usize {
161 let before = (*buf).as_ptr();
162 let after = (*self.data).as_ptr();
163
164 debug_assert!(
165 before <= after,
166 "`TreeRefIter::offset_to_next_entry(): {after:?} <= {before:?}) violated"
167 );
168 (after as usize - before as usize) / std::mem::size_of::<u8>()
169 }
170}
171
172impl<'a> Iterator for TreeRefIter<'a> {
173 type Item = ExnMessageResult<EntryRef<'a>>;
174
175 fn next(&mut self) -> Option<Self::Item> {
176 if self.data.is_empty() {
177 return None;
178 }
179 match decode::fast_entry(self.data, self.hash_kind.len_in_bytes()) {
180 Some((data_left, entry)) => {
181 self.data = data_left;
182 Some(Ok(entry))
183 }
184 None => {
185 self.data = &[];
186 Some(Err(crate::decode::empty_error().into()))
187 }
188 }
189 }
190}
191
192impl<'a> TryFrom<&'a [u8]> for tree::EntryMode {
193 type Error = &'a [u8];
194
195 fn try_from(mode: &'a [u8]) -> std::result::Result<Self, Self::Error> {
196 tree::EntryMode::from_bytes(mode).ok_or(mode)
197 }
198}
199
200mod decode {
201 use bstr::ByteSlice;
202 use gix_error::ExnMessageResult;
203
204 use crate::{TreeRef, tree, tree::EntryRef};
205
206 pub fn fast_entry(i: &[u8], hash_len: usize) -> Option<(&[u8], EntryRef<'_>)> {
207 let (mode, i) = tree::EntryMode::extract_from_bytes(i)?;
208 let (filename, i) = i.split_at(i.find_byte(0)?);
209 let i = &i[1..];
210 let (oid, i) = match i.len() {
211 len if len < hash_len => return None,
212 _ => i.split_at(hash_len),
213 };
214 Some((
215 i,
216 EntryRef {
217 mode,
218 filename: filename.as_bstr(),
219 oid: gix_hash::oid::try_from_bytes(oid)
220 .unwrap_or_else(|_| panic!("we counted exactly {hash_len} bytes")),
221 },
222 ))
223 }
224
225 pub fn tree(data: &[u8], hash_len: usize) -> ExnMessageResult<TreeRef<'_>> {
226 let mut i = data;
227
228 const AVERAGE_FILENAME_LEN: usize = 24;
232 const AVERAGE_MODE_LEN: usize = 6;
233 const ENTRY_DELIMITER_LEN: usize = 2; const AVERAGE_TREE_ENTRIES: usize = 16 * 2; let average_entry_len = ENTRY_DELIMITER_LEN + hash_len + AVERAGE_MODE_LEN + AVERAGE_FILENAME_LEN;
236 let upper_bound = i.len() / average_entry_len;
237 let mut out = Vec::with_capacity(upper_bound.min(AVERAGE_TREE_ENTRIES));
238
239 while !i.is_empty() {
240 let Some((rest, entry)) = fast_entry(i, hash_len) else {
241 return Err(crate::decode::empty_error().into());
242 };
243 i = rest;
244 out.push(entry);
245 }
246 Ok(TreeRef { entries: out })
247 }
248}