git_ref/store/file/loose/
iter.rs

1use std::path::{Path, PathBuf};
2
3use git_features::fs::walkdir::DirEntryIter;
4use git_object::bstr::ByteSlice;
5
6use crate::{file::iter::LooseThenPacked, store_impl::file, BString, FullName};
7
8/// An iterator over all valid loose reference paths as seen from a particular base directory.
9pub(in crate::store_impl::file) struct SortedLoosePaths {
10    pub(crate) base: PathBuf,
11    filename_prefix: Option<BString>,
12    file_walk: Option<DirEntryIter>,
13}
14
15impl SortedLoosePaths {
16    pub fn at(path: impl AsRef<Path>, base: impl Into<PathBuf>, filename_prefix: Option<BString>) -> Self {
17        let path = path.as_ref();
18        SortedLoosePaths {
19            base: base.into(),
20            filename_prefix,
21            file_walk: path.is_dir().then(|| {
22                // serial iteration as we expect most refs in packed-refs anyway.
23                git_features::fs::walkdir_sorted_new(path, git_features::fs::walkdir::Parallelism::Serial).into_iter()
24            }),
25        }
26    }
27}
28
29impl Iterator for SortedLoosePaths {
30    type Item = std::io::Result<(PathBuf, FullName)>;
31
32    fn next(&mut self) -> Option<Self::Item> {
33        for entry in self.file_walk.as_mut()?.by_ref() {
34            match entry {
35                Ok(entry) => {
36                    if !entry.file_type().is_file() {
37                        continue;
38                    }
39                    let full_path = entry.path().to_owned();
40                    if let Some((prefix, name)) = self
41                        .filename_prefix
42                        .as_deref()
43                        .and_then(|prefix| full_path.file_name().map(|name| (prefix, name)))
44                    {
45                        match git_path::os_str_into_bstr(name) {
46                            Ok(name) => {
47                                if !name.starts_with(prefix) {
48                                    continue;
49                                }
50                            }
51                            Err(_) => continue, // TODO: silently skipping ill-formed UTF-8 on windows - maybe this can be better?
52                        }
53                    }
54                    let full_name = full_path
55                        .strip_prefix(&self.base)
56                        .expect("prefix-stripping cannot fail as prefix is our root");
57                    let full_name = match git_path::try_into_bstr(full_name) {
58                        Ok(name) => {
59                            let name = git_path::to_unix_separators_on_windows(name);
60                            name.into_owned()
61                        }
62                        Err(_) => continue, // TODO: silently skipping ill-formed UTF-8 on windows here, maybe there are better ways?
63                    };
64
65                    if git_validate::reference::name_partial(full_name.as_bstr()).is_ok() {
66                        let name = FullName(full_name);
67                        return Some(Ok((full_path, name)));
68                    } else {
69                        continue;
70                    }
71                }
72                Err(err) => return Some(Err(err.into_io_error().expect("no symlink related errors"))),
73            }
74        }
75        None
76    }
77}
78
79impl file::Store {
80    /// Return an iterator over all loose references, notably not including any packed ones, in lexical order.
81    /// Each of the references may fail to parse and the iterator will not stop if parsing fails, allowing the caller
82    /// to see all files that look like references whether valid or not.
83    ///
84    /// Reference files that do not constitute valid names will be silently ignored.
85    pub fn loose_iter(&self) -> std::io::Result<LooseThenPacked<'_, '_>> {
86        self.iter_packed(None)
87    }
88
89    /// Return an iterator over all loose references that start with the given `prefix`.
90    ///
91    /// Otherwise it's similar to [`loose_iter()`][file::Store::loose_iter()].
92    pub fn loose_iter_prefixed(&self, prefix: impl AsRef<Path>) -> std::io::Result<LooseThenPacked<'_, '_>> {
93        self.iter_prefixed_packed(prefix, None)
94    }
95}