1use gix_error::{ExnMessageResult, ExnResult, ResultExt, message};
2
3use gix_object::bstr::ByteSlice;
4use gix_path::RelativePath;
5use std::{
6 borrow::Cow,
7 cmp::Ordering,
8 io::Read,
9 iter::Peekable,
10 path::{Path, PathBuf},
11};
12
13use crate::{
14 BStr, FullName, Namespace, Reference,
15 file::loose::{self, iter::SortedLoosePaths},
16 store_impl::{file, packed},
17};
18
19pub struct LooseThenPacked<'p, 's> {
24 git_dir: &'s Path,
25 common_dir: Option<&'s Path>,
26 object_hash: gix_hash::Kind,
27 namespace: Option<&'s Namespace>,
28 iter_packed: Option<Peekable<packed::Iter<'p>>>,
29 iter_git_dir: Peekable<SortedLoosePaths>,
30 iter_common_dir: Option<Peekable<SortedLoosePaths>>,
31 buf: Vec<u8>,
32}
33
34enum IterKind {
35 Git,
36 GitAndConsumeCommon,
37 Common,
38}
39
40#[must_use = "Iterators should be obtained from this platform"]
42pub struct Platform<'s> {
43 store: &'s file::Store,
44 packed: Option<file::packed::SharedBufferSnapshot>,
45}
46
47impl<'p> LooseThenPacked<'p, '_> {
48 fn strip_namespace(&self, mut r: Reference) -> Reference {
49 if let Some(namespace) = &self.namespace {
50 r.strip_namespace(namespace);
51 }
52 r
53 }
54
55 fn loose_iter(&mut self, kind: IterKind) -> &mut Peekable<SortedLoosePaths> {
56 match kind {
57 IterKind::GitAndConsumeCommon => {
58 drop(self.iter_common_dir.as_mut().map(Iterator::next));
59 &mut self.iter_git_dir
60 }
61 IterKind::Git => &mut self.iter_git_dir,
62 IterKind::Common => self
63 .iter_common_dir
64 .as_mut()
65 .expect("caller knows there is a common iter"),
66 }
67 }
68
69 fn convert_packed(&mut self, packed: ExnMessageResult<packed::Reference<'p>>) -> ExnResult<Reference> {
70 packed.map(Into::into).map(|r| self.strip_namespace(r)).or_erased()
71 }
72
73 fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> ExnResult<Reference> {
76 let buf = &mut self.buf;
77 let git_dir = self.git_dir;
78 let common_dir = self.common_dir;
79 let (refpath, name) = res.or_raise_erased(|| message("Could not traverse reference directory"))?;
80 std::fs::File::open(&refpath)
81 .and_then(|mut f| {
82 buf.clear();
83 f.read_to_end(buf)
84 })
85 .or_raise_erased(|| file::find::read_reference_error(refpath.as_path()))?;
86 loose::Reference::try_from_path(name, buf, self.object_hash)
87 .or_raise_erased(|| {
88 let relative_path = refpath
89 .strip_prefix(git_dir)
90 .ok()
91 .or_else(|| common_dir.and_then(|common_dir| refpath.strip_prefix(common_dir).ok()))
92 .expect("one of our bases contains the path");
93 file::find::ReferenceDecode {
94 relative_path: relative_path.into(),
95 }
96 })
97 .map(Into::into)
98 .map(|r| self.strip_namespace(r))
99 }
100}
101
102impl Iterator for LooseThenPacked<'_, '_> {
103 type Item = ExnResult<Reference>;
104
105 fn next(&mut self) -> Option<Self::Item> {
106 fn advance_to_non_private(iter: &mut Peekable<SortedLoosePaths>) {
107 while let Some(Ok((_path, name))) = iter.peek() {
108 if name.category().is_some_and(|cat| cat.is_worktree_private()) {
109 iter.next();
110 } else {
111 break;
112 }
113 }
114 }
115
116 fn peek_loose<'a>(
117 git_dir: &'a mut Peekable<SortedLoosePaths>,
118 common_dir: Option<&'a mut Peekable<SortedLoosePaths>>,
119 ) -> Option<(&'a std::io::Result<(PathBuf, FullName)>, IterKind)> {
120 match common_dir {
121 Some(common_dir) => match (git_dir.peek(), {
122 advance_to_non_private(common_dir);
123 common_dir.peek()
124 }) {
125 (None, None) => None,
126 (None, Some(res)) | (Some(_), Some(res @ Err(_))) => Some((res, IterKind::Common)),
127 (Some(res), None) | (Some(res @ Err(_)), Some(_)) => Some((res, IterKind::Git)),
128 (Some(r_gitdir @ Ok((_, git_dir_name))), Some(r_cd @ Ok((_, common_dir_name)))) => {
129 match git_dir_name.cmp(common_dir_name) {
130 Ordering::Less => Some((r_gitdir, IterKind::Git)),
131 Ordering::Equal => Some((r_gitdir, IterKind::GitAndConsumeCommon)),
132 Ordering::Greater => Some((r_cd, IterKind::Common)),
133 }
134 }
135 },
136 None => git_dir.peek().map(|r| (r, IterKind::Git)),
137 }
138 }
139 match self.iter_packed.as_mut() {
140 Some(packed_iter) => match (
141 peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()),
142 packed_iter.peek(),
143 ) {
144 (None, None) => None,
145 (None, Some(_)) | (Some(_), Some(Err(_))) => {
146 let res = packed_iter.next().expect("peeked value exists");
147 Some(self.convert_packed(res))
148 }
149 (Some((_, kind)), None) | (Some((Err(_), kind)), Some(_)) => {
150 let res = self.loose_iter(kind).next().expect("prior peek");
151 Some(self.convert_loose(res))
152 }
153 (Some((Ok((_, loose_name)), kind)), Some(Ok(packed))) => match loose_name.as_ref().cmp(packed.name) {
154 Ordering::Less => {
155 let res = self.loose_iter(kind).next().expect("prior peek");
156 Some(self.convert_loose(res))
157 }
158 Ordering::Equal => {
159 drop(packed_iter.next());
160 let res = self.loose_iter(kind).next().expect("prior peek");
161 Some(self.convert_loose(res))
162 }
163 Ordering::Greater => {
164 let res = packed_iter.next().expect("name retrieval configured");
165 Some(self.convert_packed(res))
166 }
167 },
168 },
169 None => match peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()) {
170 None => None,
171 Some((_, kind)) => self.loose_iter(kind).next().map(|res| self.convert_loose(res)),
172 },
173 }
174 }
175}
176
177impl<'repo> Platform<'repo> {
178 pub fn all<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
182 self.store.iter_packed(self.packed.as_ref().map(|b| &***b))
183 }
184
185 pub fn prefixed<'p>(&'p self, prefix: &RelativePath) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
194 self.store
195 .iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
196 }
197
198 pub fn pseudo<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
201 self.store.iter_pseudo()
202 }
203}
204
205impl file::Store {
206 pub fn iter(&self) -> ExnResult<Platform<'_>> {
213 Ok(Platform {
214 store: self,
215 packed: self.assure_packed_refs_uptodate()?,
216 })
217 }
218}
219
220#[derive(Debug)]
221pub(crate) enum IterInfo<'a> {
222 Base {
223 base: &'a Path,
224 precompose_unicode: bool,
225 },
226 BaseAndIterRoot {
227 base: &'a Path,
228 iter_root: PathBuf,
229 prefix: PathBuf,
230 precompose_unicode: bool,
231 },
232 PrefixAndBase {
233 base: &'a Path,
234 prefix: &'a Path,
235 precompose_unicode: bool,
236 },
237 ComputedIterationRoot {
238 iter_root: PathBuf,
240 base: &'a Path,
242 prefix: Cow<'a, BStr>,
244 precompose_unicode: bool,
246 },
247 Pseudo {
248 base: &'a Path,
249 precompose_unicode: bool,
250 },
251}
252
253impl<'a> IterInfo<'a> {
254 fn prefix(&self) -> Option<Cow<'_, BStr>> {
255 match self {
256 IterInfo::Base { .. } => None,
257 IterInfo::PrefixAndBase { prefix, .. } => Some(gix_path::into_bstr(*prefix)),
258 IterInfo::BaseAndIterRoot { prefix, .. } => Some(gix_path::into_bstr(prefix.clone())),
259 IterInfo::ComputedIterationRoot { prefix, .. } => Some(prefix.clone()),
260 IterInfo::Pseudo { .. } => None,
261 }
262 }
263
264 fn into_iter(self) -> Peekable<SortedLoosePaths> {
265 match self {
266 IterInfo::Base {
267 base,
268 precompose_unicode,
269 } => SortedLoosePaths::at(&base.join("refs"), base.into(), None, None, precompose_unicode),
270 IterInfo::BaseAndIterRoot {
271 base,
272 iter_root,
273 prefix: _,
274 precompose_unicode,
275 } => SortedLoosePaths::at(&iter_root, base.into(), None, None, precompose_unicode),
276 IterInfo::PrefixAndBase {
277 base,
278 prefix,
279 precompose_unicode,
280 } => SortedLoosePaths::at(&base.join(prefix), base.into(), None, None, precompose_unicode),
281 IterInfo::ComputedIterationRoot {
282 iter_root,
283 base,
284 prefix,
285 precompose_unicode,
286 } => SortedLoosePaths::at(
287 &iter_root,
288 base.into(),
289 Some(prefix.into_owned()),
290 None,
291 precompose_unicode,
292 ),
293 IterInfo::Pseudo {
294 base,
295 precompose_unicode,
296 } => SortedLoosePaths::at(base, base.into(), None, Some("HEAD".into()), precompose_unicode),
297 }
298 .peekable()
299 }
300
301 fn from_prefix(base: &'a Path, prefix: &'a RelativePath, precompose_unicode: bool) -> std::io::Result<Self> {
302 let prefix_path = gix_path::from_bstr(prefix.as_ref().as_bstr());
303 let iter_root = base.join(&prefix_path);
304 if prefix.as_ref().ends_with(b"/") {
305 Ok(IterInfo::BaseAndIterRoot {
306 base,
307 iter_root,
308 prefix: prefix_path.into_owned(),
309 precompose_unicode,
310 })
311 } else {
312 let iter_root = iter_root
313 .parent()
314 .expect("a parent is always there unless empty")
315 .to_owned();
316 Ok(IterInfo::ComputedIterationRoot {
317 base,
318 prefix: prefix.as_ref().as_bstr().into(),
319 iter_root,
320 precompose_unicode,
321 })
322 }
323 }
324}
325
326impl file::Store {
327 pub fn iter_packed<'s, 'p>(
331 &'s self,
332 packed: Option<&'p packed::Buffer>,
333 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
334 match self.namespace.as_ref() {
335 Some(namespace) => self.iter_from_info(
336 IterInfo::PrefixAndBase {
337 base: self.git_dir(),
338 prefix: namespace.to_path(),
339 precompose_unicode: self.precompose_unicode,
340 },
341 self.common_dir().map(|base| IterInfo::PrefixAndBase {
342 base,
343 prefix: namespace.to_path(),
344 precompose_unicode: self.precompose_unicode,
345 }),
346 packed,
347 ),
348 None => self.iter_from_info(
349 IterInfo::Base {
350 base: self.git_dir(),
351 precompose_unicode: self.precompose_unicode,
352 },
353 self.common_dir().map(|base| IterInfo::Base {
354 base,
355 precompose_unicode: self.precompose_unicode,
356 }),
357 packed,
358 ),
359 }
360 }
361
362 pub fn iter_pseudo<'p>(&'_ self) -> std::io::Result<LooseThenPacked<'p, '_>> {
367 self.iter_from_info(
368 IterInfo::Pseudo {
369 base: self.git_dir(),
370 precompose_unicode: self.precompose_unicode,
371 },
372 None,
373 None,
374 )
375 }
376
377 pub fn iter_prefixed_packed<'s, 'p>(
385 &'s self,
386 prefix: &RelativePath,
387 packed: Option<&'p packed::Buffer>,
388 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
389 match self.namespace.as_ref() {
390 None => {
391 let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix, self.precompose_unicode)?;
392 let common_dir_info = self
393 .common_dir()
394 .map(|base| IterInfo::from_prefix(base, prefix, self.precompose_unicode))
395 .transpose()?;
396 self.iter_from_info(git_dir_info, common_dir_info, packed)
397 }
398 Some(namespace) => {
399 let prefix = namespace.to_owned().into_namespaced_prefix(prefix);
400 let prefix = prefix
401 .as_bstr()
402 .try_into()
403 .map_err(|err: gix_error::Exn<gix_error::Message>| std::io::Error::other(err.into_error()))?;
404 let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix, self.precompose_unicode)?;
405 let common_dir_info = self
406 .common_dir()
407 .map(|base| IterInfo::from_prefix(base, prefix, self.precompose_unicode))
408 .transpose()?;
409 self.iter_from_info(git_dir_info, common_dir_info, packed)
410 }
411 }
412 }
413
414 fn iter_from_info<'s, 'p>(
415 &'s self,
416 git_dir_info: IterInfo<'_>,
417 common_dir_info: Option<IterInfo<'_>>,
418 packed: Option<&'p packed::Buffer>,
419 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
420 Ok(LooseThenPacked {
421 git_dir: self.git_dir(),
422 common_dir: self.common_dir(),
423 object_hash: self.object_hash,
424 iter_packed: match packed {
425 Some(packed) => Some(
426 match git_dir_info.prefix() {
427 Some(prefix) => packed.iter_prefixed(prefix.into_owned()),
428 None => packed.iter(),
429 }
430 .map_err(|err| std::io::Error::other(err.into_error()))?
431 .peekable(),
432 ),
433 None => None,
434 },
435 iter_git_dir: git_dir_info.into_iter(),
436 iter_common_dir: common_dir_info.map(IterInfo::into_iter),
437 buf: Vec::new(),
438 namespace: self.namespace.as_ref(),
439 })
440 }
441}