git_ref/store/file/
overlay_iter.rs

1use std::{
2    borrow::Cow,
3    cmp::Ordering,
4    io::Read,
5    iter::Peekable,
6    path::{Path, PathBuf},
7};
8
9use crate::{
10    file::{loose, loose::iter::SortedLoosePaths, path_to_name},
11    store_impl::{file, packed},
12    BString, FullName, Namespace, Reference,
13};
14
15/// An iterator stepping through sorted input of loose references and packed references, preferring loose refs over otherwise
16/// equivalent packed references.
17///
18/// All errors will be returned verbatim, while packed errors are depleted first if loose refs also error.
19pub struct LooseThenPacked<'p, 's> {
20    git_dir: &'s Path,
21    common_dir: Option<&'s Path>,
22    namespace: Option<&'s Namespace>,
23    iter_packed: Option<Peekable<packed::Iter<'p>>>,
24    iter_git_dir: Peekable<SortedLoosePaths>,
25    #[allow(dead_code)]
26    iter_common_dir: Option<Peekable<SortedLoosePaths>>,
27    buf: Vec<u8>,
28}
29
30enum IterKind {
31    Git,
32    GitAndConsumeCommon,
33    Common,
34}
35
36/// An intermediate structure to hold shared state alive long enough for iteration to happen.
37#[must_use = "Iterators should be obtained from this platform"]
38pub struct Platform<'s> {
39    store: &'s file::Store,
40    packed: Option<file::packed::SharedBufferSnapshot>,
41}
42
43impl<'p, 's> LooseThenPacked<'p, 's> {
44    fn strip_namespace(&self, mut r: Reference) -> Reference {
45        if let Some(namespace) = &self.namespace {
46            r.strip_namespace(namespace);
47        }
48        r
49    }
50
51    fn loose_iter(&mut self, kind: IterKind) -> &mut Peekable<SortedLoosePaths> {
52        match kind {
53            IterKind::GitAndConsumeCommon => {
54                drop(self.iter_common_dir.as_mut().map(|iter| iter.next()));
55                &mut self.iter_git_dir
56            }
57            IterKind::Git => &mut self.iter_git_dir,
58            IterKind::Common => self
59                .iter_common_dir
60                .as_mut()
61                .expect("caller knows there is a common iter"),
62        }
63    }
64
65    fn convert_packed(
66        &mut self,
67        packed: Result<packed::Reference<'p>, packed::iter::Error>,
68    ) -> Result<Reference, Error> {
69        packed
70            .map(Into::into)
71            .map(|r| self.strip_namespace(r))
72            .map_err(|err| match err {
73                packed::iter::Error::Reference {
74                    invalid_line,
75                    line_number,
76                } => Error::PackedReference {
77                    invalid_line,
78                    line_number,
79                },
80                packed::iter::Error::Header { .. } => unreachable!("this one only happens on iteration creation"),
81            })
82    }
83
84    fn convert_loose(&mut self, res: std::io::Result<(PathBuf, FullName)>) -> Result<Reference, Error> {
85        let (refpath, name) = res.map_err(Error::Traversal)?;
86        std::fs::File::open(&refpath)
87            .and_then(|mut f| {
88                self.buf.clear();
89                f.read_to_end(&mut self.buf)
90            })
91            .map_err(|err| Error::ReadFileContents {
92                source: err,
93                path: refpath.to_owned(),
94            })?;
95        loose::Reference::try_from_path(name, &self.buf)
96            .map_err(|err| {
97                let relative_path = refpath
98                    .strip_prefix(self.git_dir)
99                    .ok()
100                    .or_else(|| {
101                        self.common_dir
102                            .and_then(|common_dir| refpath.strip_prefix(common_dir).ok())
103                    })
104                    .expect("one of our bases contains the path");
105                Error::ReferenceCreation {
106                    source: err,
107                    relative_path: relative_path.into(),
108                }
109            })
110            .map(Into::into)
111            .map(|r| self.strip_namespace(r))
112    }
113}
114
115impl<'p, 's> Iterator for LooseThenPacked<'p, 's> {
116    type Item = Result<Reference, Error>;
117
118    fn next(&mut self) -> Option<Self::Item> {
119        fn advance_to_non_private(iter: &mut Peekable<SortedLoosePaths>) {
120            while let Some(Ok((_path, name))) = iter.peek() {
121                if name.category().map_or(true, |cat| cat.is_worktree_private()) {
122                    iter.next();
123                } else {
124                    break;
125                }
126            }
127        }
128
129        fn peek_loose<'a>(
130            git_dir: &'a mut Peekable<SortedLoosePaths>,
131            common_dir: Option<&'a mut Peekable<SortedLoosePaths>>,
132        ) -> Option<(&'a std::io::Result<(PathBuf, FullName)>, IterKind)> {
133            match common_dir {
134                Some(common_dir) => match (git_dir.peek(), {
135                    advance_to_non_private(common_dir);
136                    common_dir.peek()
137                }) {
138                    (None, None) => None,
139                    (None, Some(res)) | (Some(_), Some(res @ Err(_))) => Some((res, IterKind::Common)),
140                    (Some(res), None) | (Some(res @ Err(_)), Some(_)) => Some((res, IterKind::Git)),
141                    (Some(r_gitdir @ Ok((_, git_dir_name))), Some(r_cd @ Ok((_, common_dir_name)))) => {
142                        match git_dir_name.cmp(common_dir_name) {
143                            Ordering::Less => Some((r_gitdir, IterKind::Git)),
144                            Ordering::Equal => Some((r_gitdir, IterKind::GitAndConsumeCommon)),
145                            Ordering::Greater => Some((r_cd, IterKind::Common)),
146                        }
147                    }
148                },
149                None => git_dir.peek().map(|r| (r, IterKind::Git)),
150            }
151        }
152        match self.iter_packed.as_mut() {
153            Some(packed_iter) => match (
154                peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()),
155                packed_iter.peek(),
156            ) {
157                (None, None) => None,
158                (None, Some(_)) | (Some(_), Some(Err(_))) => {
159                    let res = packed_iter.next().expect("peeked value exists");
160                    Some(self.convert_packed(res))
161                }
162                (Some((_, kind)), None) | (Some((Err(_), kind)), Some(_)) => {
163                    let res = self.loose_iter(kind).next().expect("prior peek");
164                    Some(self.convert_loose(res))
165                }
166                (Some((Ok((_, loose_name)), kind)), Some(Ok(packed))) => match loose_name.as_ref().cmp(packed.name) {
167                    Ordering::Less => {
168                        let res = self.loose_iter(kind).next().expect("prior peek");
169                        Some(self.convert_loose(res))
170                    }
171                    Ordering::Equal => {
172                        drop(packed_iter.next());
173                        let res = self.loose_iter(kind).next().expect("prior peek");
174                        Some(self.convert_loose(res))
175                    }
176                    Ordering::Greater => {
177                        let res = packed_iter.next().expect("name retrieval configured");
178                        Some(self.convert_packed(res))
179                    }
180                },
181            },
182            None => match peek_loose(&mut self.iter_git_dir, self.iter_common_dir.as_mut()) {
183                None => None,
184                Some((_, kind)) => self.loose_iter(kind).next().map(|res| self.convert_loose(res)),
185            },
186        }
187    }
188}
189
190impl<'s> Platform<'s> {
191    /// Return an iterator over all references, loose or `packed`, sorted by their name.
192    ///
193    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
194    pub fn all(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
195        self.store.iter_packed(self.packed.as_ref().map(|b| &***b))
196    }
197
198    /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads".
199    ///
200    /// Please note that "refs/heads` or "refs\\heads" is equivalent to "refs/heads/"
201    pub fn prefixed(&self, prefix: impl AsRef<Path>) -> std::io::Result<LooseThenPacked<'_, '_>> {
202        self.store
203            .iter_prefixed_packed(prefix, self.packed.as_ref().map(|b| &***b))
204    }
205}
206
207impl file::Store {
208    /// Return a platform to obtain iterator over all references, or prefixed ones, loose or packed, sorted by their name.
209    ///
210    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
211    pub fn iter(&self) -> Result<Platform<'_>, packed::buffer::open::Error> {
212        Ok(Platform {
213            store: self,
214            packed: self.assure_packed_refs_uptodate()?,
215        })
216    }
217}
218
219#[derive(Debug)]
220pub(crate) enum IterInfo<'a> {
221    Base {
222        base: &'a Path,
223    },
224    BaseAndIterRoot {
225        base: &'a Path,
226        iter_root: PathBuf,
227        prefix: Cow<'a, Path>,
228    },
229    PrefixAndBase {
230        base: &'a Path,
231        prefix: &'a Path,
232    },
233    ComputedIterationRoot {
234        /// The root to iterate over
235        iter_root: PathBuf,
236        /// The top-level directory as boundary of all references, used to create their short-names after iteration
237        base: &'a Path,
238        /// The original prefix
239        prefix: Cow<'a, Path>,
240        /// The remainder of the prefix that wasn't a valid path
241        remainder: Option<BString>,
242    },
243}
244
245impl<'a> IterInfo<'a> {
246    fn prefix(&self) -> Option<&Path> {
247        match self {
248            IterInfo::Base { .. } => None,
249            IterInfo::PrefixAndBase { prefix, .. } => Some(*prefix),
250            IterInfo::ComputedIterationRoot { prefix, .. } | IterInfo::BaseAndIterRoot { prefix, .. } => {
251                prefix.as_ref().into()
252            }
253        }
254    }
255
256    fn into_iter(self) -> Peekable<SortedLoosePaths> {
257        match self {
258            IterInfo::Base { base } => SortedLoosePaths::at(base.join("refs"), base, None),
259            IterInfo::BaseAndIterRoot {
260                base,
261                iter_root,
262                prefix: _,
263            } => SortedLoosePaths::at(iter_root, base, None),
264            IterInfo::PrefixAndBase { base, prefix } => SortedLoosePaths::at(base.join(prefix), base, None),
265            IterInfo::ComputedIterationRoot {
266                iter_root,
267                base,
268                prefix: _,
269                remainder,
270            } => SortedLoosePaths::at(iter_root, base, remainder),
271        }
272        .peekable()
273    }
274
275    fn from_prefix(base: &'a Path, prefix: Cow<'a, Path>) -> std::io::Result<Self> {
276        if prefix.is_absolute() {
277            return Err(std::io::Error::new(
278                std::io::ErrorKind::InvalidInput,
279                "prefix must be a relative path, like 'refs/heads'",
280            ));
281        }
282        use std::path::Component::*;
283        if prefix.components().any(|c| matches!(c, CurDir | ParentDir)) {
284            return Err(std::io::Error::new(
285                std::io::ErrorKind::InvalidInput,
286                "Refusing to handle prefixes with relative path components",
287            ));
288        }
289        let iter_root = base.join(prefix.as_ref());
290        if iter_root.is_dir() {
291            Ok(IterInfo::BaseAndIterRoot {
292                base,
293                iter_root,
294                prefix,
295            })
296        } else {
297            let filename_prefix = iter_root
298                .file_name()
299                .map(ToOwned::to_owned)
300                .map(|p| {
301                    git_path::try_into_bstr(PathBuf::from(p))
302                        .map(|p| p.into_owned())
303                        .map_err(|_| {
304                            std::io::Error::new(std::io::ErrorKind::InvalidInput, "prefix contains ill-formed UTF-8")
305                        })
306                })
307                .transpose()?;
308            let iter_root = iter_root
309                .parent()
310                .expect("a parent is always there unless empty")
311                .to_owned();
312            Ok(IterInfo::ComputedIterationRoot {
313                base,
314                prefix,
315                iter_root,
316                remainder: filename_prefix,
317            })
318        }
319    }
320}
321
322impl file::Store {
323    /// Return an iterator over all references, loose or `packed`, sorted by their name.
324    ///
325    /// Errors are returned similarly to what would happen when loose and packed refs where iterated by themselves.
326    pub fn iter_packed<'s, 'p>(
327        &'s self,
328        packed: Option<&'p packed::Buffer>,
329    ) -> std::io::Result<LooseThenPacked<'p, 's>> {
330        match self.namespace.as_ref() {
331            Some(namespace) => self.iter_from_info(
332                IterInfo::PrefixAndBase {
333                    base: self.git_dir(),
334                    prefix: namespace.to_path(),
335                },
336                self.common_dir().map(|base| IterInfo::PrefixAndBase {
337                    base,
338                    prefix: namespace.to_path(),
339                }),
340                packed,
341            ),
342            None => self.iter_from_info(
343                IterInfo::Base { base: self.git_dir() },
344                self.common_dir().map(|base| IterInfo::Base { base }),
345                packed,
346            ),
347        }
348    }
349
350    /// As [`iter(…)`][file::Store::iter()], but filters by `prefix`, i.e. "refs/heads".
351    ///
352    /// Please note that "refs/heads` or "refs\\heads" is equivalent to "refs/heads/"
353    pub fn iter_prefixed_packed<'s, 'p>(
354        &'s self,
355        prefix: impl AsRef<Path>,
356        packed: Option<&'p packed::Buffer>,
357    ) -> std::io::Result<LooseThenPacked<'p, 's>> {
358        match self.namespace.as_ref() {
359            None => {
360                let prefix = prefix.as_ref();
361                let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.into())?;
362                let common_dir_info = self
363                    .common_dir()
364                    .map(|base| IterInfo::from_prefix(base, prefix.into()))
365                    .transpose()?;
366                self.iter_from_info(git_dir_info, common_dir_info, packed)
367            }
368            Some(namespace) => {
369                let prefix = namespace.to_owned().into_namespaced_prefix(prefix);
370                let git_dir_info = IterInfo::from_prefix(self.git_dir(), prefix.clone().into())?;
371                let common_dir_info = self
372                    .common_dir()
373                    .map(|base| IterInfo::from_prefix(base, prefix.into()))
374                    .transpose()?;
375                self.iter_from_info(git_dir_info, common_dir_info, packed)
376            }
377        }
378    }
379
380    fn iter_from_info<'s, 'p>(
381        &'s self,
382        git_dir_info: IterInfo<'_>,
383        common_dir_info: Option<IterInfo<'_>>,
384        packed: Option<&'p packed::Buffer>,
385    ) -> std::io::Result<LooseThenPacked<'p, 's>> {
386        Ok(LooseThenPacked {
387            git_dir: self.git_dir(),
388            common_dir: self.common_dir(),
389            iter_packed: match packed {
390                Some(packed) => Some(
391                    match git_dir_info.prefix() {
392                        Some(prefix) => packed.iter_prefixed(path_to_name(prefix).into_owned()),
393                        None => packed.iter(),
394                    }
395                    .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
396                    .peekable(),
397                ),
398                None => None,
399            },
400            iter_git_dir: git_dir_info.into_iter(),
401            iter_common_dir: common_dir_info.map(IterInfo::into_iter),
402            buf: Vec::new(),
403            namespace: self.namespace.as_ref(),
404        })
405    }
406}
407
408mod error {
409    use std::{io, path::PathBuf};
410
411    use git_object::bstr::BString;
412
413    use crate::store_impl::file;
414
415    /// The error returned by the [`LooseThenPacked`][super::LooseThenPacked] iterator.
416    #[derive(Debug, thiserror::Error)]
417    #[allow(missing_docs)]
418    pub enum Error {
419        #[error("The file system could not be traversed")]
420        Traversal(#[source] io::Error),
421        #[error("The ref file {path:?} could not be read in full")]
422        ReadFileContents { source: io::Error, path: PathBuf },
423        #[error("The reference at \"{relative_path}\" could not be instantiated")]
424        ReferenceCreation {
425            source: file::loose::reference::decode::Error,
426            relative_path: PathBuf,
427        },
428        #[error("Invalid reference in line {line_number}: {invalid_line:?}")]
429        PackedReference { invalid_line: BString, line_number: usize },
430    }
431}
432pub use error::Error;