gix_ref/store/file/loose/
iter.rs

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