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