1use gix_object::bstr::ByteSlice;
2use gix_path::RelativePath;
3use std::{
4 borrow::Cow,
5 cmp::Ordering,
6 io::Read,
7 iter::Peekable,
8 path::{Path, PathBuf},
9};
10
11use crate::{
12 file::loose::{self, iter::SortedLoosePaths},
13 store_impl::{file, packed},
14 BStr, FullName, Namespace, Reference,
15};
16
17pub struct LooseThenPacked<'p, 's> {
22 git_dir: &'s Path,
23 common_dir: Option<&'s Path>,
24 hash_kind: gix_hash::Kind,
25 namespace: Option<&'s Namespace>,
26 iter_packed: Option<Peekable<packed::Iter<'p>>>,
27 iter_git_dir: Peekable<SortedLoosePaths>,
28 #[allow(dead_code)]
29 iter_common_dir: Option<Peekable<SortedLoosePaths>>,
30 buf: Vec<u8>,
31}
32
33enum IterKind {
34 Git,
35 GitAndConsumeCommon,
36 Common,
37}
38
39#[must_use = "Iterators should be obtained from this platform"]
41pub struct Platform<'s> {
42 store: &'s file::Store,
43 packed: Option<file::packed::SharedBufferSnapshot>,
44}
45
46impl<'p> LooseThenPacked<'p, '_> {
47 fn strip_namespace(&self, mut r: Reference) -> Reference {
48 if let Some(namespace) = &self.namespace {
49 r.strip_namespace(namespace);
50 }
51 r
52 }
53
54 fn loose_iter(&mut self, kind: IterKind) -> &mut Peekable<SortedLoosePaths> {
55 match kind {
56 IterKind::GitAndConsumeCommon => {
57 drop(self.iter_common_dir.as_mut().map(Iterator::next));
58 &mut self.iter_git_dir
59 }
60 IterKind::Git => &mut self.iter_git_dir,
61 IterKind::Common => self
62 .iter_common_dir
63 .as_mut()
64 .expect("caller knows there is a common iter"),
65 }
66 }
67
68 fn convert_packed(
69 &mut self,
70 packed: Result<packed::Reference<'p>, packed::iter::Error>,
71 ) -> Result<Reference, Error> {
72 packed
73 .map(Into::into)
74 .map(|r| self.strip_namespace(r))
75 .map_err(|err| match err {
76 packed::iter::Error::Reference {
77 invalid_line,
78 line_number,
79 } => Error::PackedReference {
80 invalid_line,
81 line_number,
82 },
83 packed::iter::Error::Header { .. } => unreachable!("this one only happens on iteration creation"),
84 })
85 }
86
87 fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result<Reference, Error> {
88 let buf = &mut self.buf;
89 let git_dir = self.git_dir;
90 let common_dir = self.common_dir;
91 let (refpath, name) = res.map_err(Error::Traversal)?;
92 std::fs::File::open(&refpath)
93 .and_then(|mut f| {
94 buf.clear();
95 f.read_to_end(buf)
96 })
97 .map_err(|err| Error::ReadFileContents {
98 source: err,
99 path: refpath.to_owned(),
100 })?;
101 loose::Reference::try_from_path(name, buf, self.hash_kind)
102 .map_err(|err| {
103 let relative_path = refpath
104 .strip_prefix(git_dir)
105 .ok()
106 .or_else(|| common_dir.and_then(|common_dir| refpath.strip_prefix(common_dir).ok()))
107 .expect("one of our bases contains the path");
108 Error::ReferenceCreation {
109 source: err,
110 relative_path: relative_path.into(),
111 }
112 })
113 .map(Into::into)
114 .map(|r| self.strip_namespace(r))
115 }
116}
117
118impl Iterator for LooseThenPacked<'_, '_> {
119 type Item = Result<Reference, Error>;
120
121 fn next(&mut self) -> Option<Self::Item> {
122 fn advance_to_non_private(iter: &mut Peekable<SortedLoosePaths>) {
123 while let Some(Ok((_path, name))) = iter.peek() {
124 if name.category().is_some_and(|cat| cat.is_worktree_private()) {
125 iter.next();
126 } else {
127 break;
128 }
129 }
130 }
131
132 fn peek_loose<'a>(
133 git_dir: &'a mut Peekable<SortedLoosePaths>,
134 common_dir: Option<&'a mut Peekable<SortedLoosePaths>>,
135 ) -> Option<(&'a std::io::Result<(PathBuf, FullName)>, IterKind)> {
136 match common_dir {
137 Some(common_dir) => match (git_dir.peek(), {
138 advance_to_non_private(common_dir);
139 common_dir.peek()
140 }) {
141 (None, None) => None,
142 (None, Some(res)) | (Some(_), Some(res @ Err(_))) => Some((res, IterKind::Common)),
143 (Some(res), None) | (Some(res @ Err(_)), Some(_)) => Some((res, IterKind::Git)),
144 (Some(r_gitdir @ Ok((_, git_dir_name))), Some(r_cd @ Ok((_, common_dir_name)))) => {
145 match git_dir_name.cmp(common_dir_name) {
146 Ordering::Less => Some((r_gitdir, IterKind::Git)),
147 Ordering::Equal => Some((r_gitdir, IterKind::GitAndConsumeCommon)),
148 Ordering::Greater => Some((r_cd, IterKind::Common)),
149 }
150 }
151 },
152 None => git_dir.peek().map(|r| (r, IterKind::Git)),
153 }
154 }
155 match self.iter_packed.as_mut() {
156 Some(packed_iter) => match (
157 peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()),
158 packed_iter.peek(),
159 ) {
160 (None, None) => None,
161 (None, Some(_)) | (Some(_), Some(Err(_))) => {
162 let res = packed_iter.next().expect("peeked value exists");
163 Some(self.convert_packed(res))
164 }
165 (Some((_, kind)), None) | (Some((Err(_), kind)), Some(_)) => {
166 let res = self.loose_iter(kind).next().expect("prior peek");
167 Some(self.convert_loose(res))
168 }
169 (Some((Ok((_, loose_name)), kind)), Some(Ok(packed))) => match loose_name.as_ref().cmp(packed.name) {
170 Ordering::Less => {
171 let res = self.loose_iter(kind).next().expect("prior peek");
172 Some(self.convert_loose(res))
173 }
174 Ordering::Equal => {
175 drop(packed_iter.next());
176 let res = self.loose_iter(kind).next().expect("prior peek");
177 Some(self.convert_loose(res))
178 }
179 Ordering::Greater => {
180 let res = packed_iter.next().expect("name retrieval configured");
181 Some(self.convert_packed(res))
182 }
183 },
184 },
185 None => match peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()) {
186 None => None,
187 Some((_, kind)) => self.loose_iter(kind).next().map(|res| self.convert_loose(res)),
188 },
189 }
190 }
191}
192
193impl<'repo> Platform<'repo> {
194 pub fn all<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
198 self.store.iter_packed(self.packed.as_ref().map(|b| &***b))
199 }
200
201 pub fn prefixed<'p>(&'p self, prefix: &RelativePath) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
210 self.store
211 .iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
212 }
213
214 pub fn pseudo<'p>(&'p self) -> std::io::Result<LooseThenPacked<'p, 'repo>> {
217 self.store.iter_pseudo()
218 }
219}
220
221impl file::Store {
222 pub fn iter(&self) -> Result<Platform<'_>, packed::buffer::open::Error> {
229 Ok(Platform {
230 store: self,
231 packed: self.assure_packed_refs_uptodate()?,
232 })
233 }
234}
235
236#[derive(Debug)]
237pub(crate) enum IterInfo<'a> {
238 Base {
239 base: &'a Path,
240 precompose_unicode: bool,
241 },
242 BaseAndIterRoot {
243 base: &'a Path,
244 iter_root: PathBuf,
245 prefix: PathBuf,
246 precompose_unicode: bool,
247 },
248 PrefixAndBase {
249 base: &'a Path,
250 prefix: &'a Path,
251 precompose_unicode: bool,
252 },
253 ComputedIterationRoot {
254 iter_root: PathBuf,
256 base: &'a Path,
258 prefix: Cow<'a, BStr>,
260 precompose_unicode: bool,
262 },
263 Pseudo {
264 base: &'a Path,
265 precompose_unicode: bool,
266 },
267}
268
269impl<'a> IterInfo<'a> {
270 fn prefix(&self) -> Option<Cow<'_, BStr>> {
271 match self {
272 IterInfo::Base { .. } => None,
273 IterInfo::PrefixAndBase { prefix, .. } => Some(gix_path::into_bstr(*prefix)),
274 IterInfo::BaseAndIterRoot { prefix, .. } => Some(gix_path::into_bstr(prefix.clone())),
275 IterInfo::ComputedIterationRoot { prefix, .. } => Some(prefix.clone()),
276 IterInfo::Pseudo { .. } => None,
277 }
278 }
279
280 fn into_iter(self) -> Peekable<SortedLoosePaths> {
281 match self {
282 IterInfo::Base {
283 base,
284 precompose_unicode,
285 } => SortedLoosePaths::at(&base.join("refs"), base.into(), None, None, precompose_unicode),
286 IterInfo::BaseAndIterRoot {
287 base,
288 iter_root,
289 prefix: _,
290 precompose_unicode,
291 } => SortedLoosePaths::at(&iter_root, base.into(), None, None, precompose_unicode),
292 IterInfo::PrefixAndBase {
293 base,
294 prefix,
295 precompose_unicode,
296 } => SortedLoosePaths::at(&base.join(prefix), base.into(), None, None, precompose_unicode),
297 IterInfo::ComputedIterationRoot {
298 iter_root,
299 base,
300 prefix,
301 precompose_unicode,
302 } => SortedLoosePaths::at(
303 &iter_root,
304 base.into(),
305 Some(prefix.into_owned()),
306 None,
307 precompose_unicode,
308 ),
309 IterInfo::Pseudo {
310 base,
311 precompose_unicode,
312 } => SortedLoosePaths::at(base, base.into(), None, Some("HEAD".into()), precompose_unicode),
313 }
314 .peekable()
315 }
316
317 fn from_prefix(base: &'a Path, prefix: &'a RelativePath, precompose_unicode: bool) -> std::io::Result<Self> {
318 let prefix_path = gix_path::from_bstr(prefix.as_ref().as_bstr());
319 let iter_root = base.join(&prefix_path);
320 if prefix.as_ref().ends_with(b"/") {
321 Ok(IterInfo::BaseAndIterRoot {
322 base,
323 iter_root,
324 prefix: prefix_path.into_owned(),
325 precompose_unicode,
326 })
327 } else {
328 let iter_root = iter_root
329 .parent()
330 .expect("a parent is always there unless empty")
331 .to_owned();
332 Ok(IterInfo::ComputedIterationRoot {
333 base,
334 prefix: prefix.as_ref().as_bstr().into(),
335 iter_root,
336 precompose_unicode,
337 })
338 }
339 }
340}
341
342impl file::Store {
343 pub fn iter_packed<'s, 'p>(
347 &'s self,
348 packed: Option<&'p packed::Buffer>,
349 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
350 match self.namespace.as_ref() {
351 Some(namespace) => self.iter_from_info(
352 IterInfo::PrefixAndBase {
353 base: self.git_dir(),
354 prefix: namespace.to_path(),
355 precompose_unicode: self.precompose_unicode,
356 },
357 self.common_dir().map(|base| IterInfo::PrefixAndBase {
358 base,
359 prefix: namespace.to_path(),
360 precompose_unicode: self.precompose_unicode,
361 }),
362 packed,
363 ),
364 None => self.iter_from_info(
365 IterInfo::Base {
366 base: self.git_dir(),
367 precompose_unicode: self.precompose_unicode,
368 },
369 self.common_dir().map(|base| IterInfo::Base {
370 base,
371 precompose_unicode: self.precompose_unicode,
372 }),
373 packed,
374 ),
375 }
376 }
377
378 pub fn iter_pseudo<'p>(&'_ self) -> std::io::Result<LooseThenPacked<'p, '_>> {
383 self.iter_from_info(
384 IterInfo::Pseudo {
385 base: self.git_dir(),
386 precompose_unicode: self.precompose_unicode,
387 },
388 None,
389 None,
390 )
391 }
392
393 pub fn iter_prefixed_packed<'s, 'p>(
401 &'s self,
402 prefix: &RelativePath,
403 packed: Option<&'p packed::Buffer>,
404 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
405 match self.namespace.as_ref() {
406 None => {
407 let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix, self.precompose_unicode)?;
408 let common_dir_info = self
409 .common_dir()
410 .map(|base| IterInfo::from_prefix(base, prefix, self.precompose_unicode))
411 .transpose()?;
412 self.iter_from_info(git_dir_info, common_dir_info, packed)
413 }
414 Some(namespace) => {
415 let prefix = namespace.to_owned().into_namespaced_prefix(prefix);
416 let prefix = prefix.as_bstr().try_into().map_err(std::io::Error::other)?;
417 let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix, self.precompose_unicode)?;
418 let common_dir_info = self
419 .common_dir()
420 .map(|base| IterInfo::from_prefix(base, prefix, self.precompose_unicode))
421 .transpose()?;
422 self.iter_from_info(git_dir_info, common_dir_info, packed)
423 }
424 }
425 }
426
427 fn iter_from_info<'s, 'p>(
428 &'s self,
429 git_dir_info: IterInfo<'_>,
430 common_dir_info: Option<IterInfo<'_>>,
431 packed: Option<&'p packed::Buffer>,
432 ) -> std::io::Result<LooseThenPacked<'p, 's>> {
433 Ok(LooseThenPacked {
434 git_dir: self.git_dir(),
435 common_dir: self.common_dir(),
436 hash_kind: self.object_hash,
437 iter_packed: match packed {
438 Some(packed) => Some(
439 match git_dir_info.prefix() {
440 Some(prefix) => packed.iter_prefixed(prefix.into_owned()),
441 None => packed.iter(),
442 }
443 .map_err(std::io::Error::other)?
444 .peekable(),
445 ),
446 None => None,
447 },
448 iter_git_dir: git_dir_info.into_iter(),
449 iter_common_dir: common_dir_info.map(IterInfo::into_iter),
450 buf: Vec::new(),
451 namespace: self.namespace.as_ref(),
452 })
453 }
454}
455
456mod error {
457 use std::{io, path::PathBuf};
458
459 use gix_object::bstr::BString;
460
461 use crate::store_impl::file;
462
463 #[derive(Debug, thiserror::Error)]
465 #[allow(missing_docs)]
466 pub enum Error {
467 #[error("The file system could not be traversed")]
468 Traversal(#[source] io::Error),
469 #[error("The ref file {path:?} could not be read in full")]
470 ReadFileContents { source: io::Error, path: PathBuf },
471 #[error("The reference at \"{relative_path}\" could not be instantiated")]
472 ReferenceCreation {
473 source: file::loose::reference::decode::Error,
474 relative_path: PathBuf,
475 },
476 #[error("Invalid reference in line {line_number}: {invalid_line:?}")]
477 PackedReference { invalid_line: BString, line_number: usize },
478 }
479}
480pub use error::Error;