gix/repository/
dirwalk.rs

1use std::sync::atomic::AtomicBool;
2
3use crate::{
4    bstr::{BStr, BString},
5    config, dirwalk, is_dir_to_mode,
6    util::OwnedOrStaticAtomicBool,
7    worktree::IndexPersistedOrInMemory,
8    Repository,
9};
10
11impl Repository {
12    /// Return default options suitable for performing a directory walk on this repository.
13    ///
14    /// Used in conjunction with [`dirwalk()`](Self::dirwalk())
15    pub fn dirwalk_options(&self) -> Result<dirwalk::Options, config::boolean::Error> {
16        Ok(dirwalk::Options::from_fs_caps(self.filesystem_options()?))
17    }
18
19    /// Perform a directory walk configured with `options` under control of the `delegate`. Use `patterns` to
20    /// further filter entries. `should_interrupt` is polled to see if an interrupt is requested, causing an
21    /// error to be returned instead.
22    ///
23    /// The `index` is used to determine if entries are tracked, and for excludes and attributes
24    /// lookup. Note that items will only count as tracked if they have the [`gix_index::entry::Flags::UPTODATE`]
25    /// flag set.
26    ///
27    /// Note that dirwalks for the purpose of deletion will be initialized with the worktrees of this repository
28    /// if they fall into the working directory of this repository as well to mark them as `tracked`. That way
29    /// it's hard to accidentally flag them for deletion.
30    /// This is intentionally not the case when deletion is not intended so they look like
31    /// untracked repositories instead.
32    ///
33    /// See [`gix_dir::walk::delegate::Collect`] for a delegate that collects all seen entries.
34    pub fn dirwalk(
35        &self,
36        index: &gix_index::State,
37        patterns: impl IntoIterator<Item = impl AsRef<BStr>>,
38        should_interrupt: &AtomicBool,
39        options: dirwalk::Options,
40        delegate: &mut dyn gix_dir::walk::Delegate,
41    ) -> Result<dirwalk::Outcome<'_>, dirwalk::Error> {
42        let _span = gix_trace::coarse!("gix::dirwalk");
43        let workdir = self.workdir().ok_or(dirwalk::Error::MissingWorkDir)?;
44        let mut excludes = self.excludes(
45            index,
46            None,
47            crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
48        )?;
49        let mut pathspec = self.pathspec(
50            options.empty_patterns_match_prefix, /* empty patterns match prefix */
51            patterns,
52            true, /* inherit ignore case */
53            index,
54            crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
55        )?;
56
57        let git_dir_realpath =
58            crate::path::realpath_opts(self.git_dir(), self.current_dir(), crate::path::realpath::MAX_SYMLINKS)?;
59        let fs_caps = self.filesystem_options()?;
60        let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing());
61        let mut opts = gix_dir::walk::Options::from(options);
62        let worktree_relative_worktree_dirs_storage;
63        if let Some(workdir) = self.workdir().filter(|_| opts.for_deletion.is_some()) {
64            let linked_worktrees = self.worktrees()?;
65            if !linked_worktrees.is_empty() {
66                let real_workdir = gix_path::realpath_opts(
67                    workdir,
68                    self.options.current_dir_or_empty(),
69                    gix_path::realpath::MAX_SYMLINKS,
70                )?;
71                worktree_relative_worktree_dirs_storage = linked_worktrees
72                    .into_iter()
73                    .filter_map(|proxy| proxy.base().ok())
74                    .filter_map(|base| base.strip_prefix(&real_workdir).map(ToOwned::to_owned).ok())
75                    .map(|rela_path| {
76                        gix_path::to_unix_separators_on_windows(gix_path::into_bstr(rela_path)).into_owned()
77                    })
78                    .collect();
79                opts.worktree_relative_worktree_dirs = Some(&worktree_relative_worktree_dirs_storage);
80            }
81        }
82        let (outcome, traversal_root) = gix_dir::walk(
83            workdir,
84            gix_dir::walk::Context {
85                should_interrupt: Some(should_interrupt),
86                git_dir_realpath: git_dir_realpath.as_ref(),
87                current_dir: self.current_dir(),
88                index,
89                ignore_case_index_lookup: accelerate_lookup.as_ref(),
90                pathspec: &mut pathspec.search,
91                pathspec_attributes: &mut |relative_path, case, is_dir, out| {
92                    let stack = pathspec
93                        .stack
94                        .as_mut()
95                        .expect("can only be called if attributes are used in patterns");
96                    stack
97                        .set_case(case)
98                        .at_entry(relative_path, Some(is_dir_to_mode(is_dir)), &self.objects)
99                        .is_ok_and(|platform| platform.matching_attributes(out))
100                },
101                excludes: Some(&mut excludes.inner),
102                objects: &self.objects,
103                explicit_traversal_root: (!options.empty_patterns_match_prefix).then_some(workdir),
104            },
105            opts,
106            delegate,
107        )?;
108
109        Ok(dirwalk::Outcome {
110            dirwalk: outcome,
111            traversal_root,
112            excludes,
113            pathspec,
114        })
115    }
116
117    /// Create an iterator over a running traversal, which stops if the iterator is dropped. All arguments
118    /// are the same as in [`dirwalk()`](Self::dirwalk).
119    ///
120    /// `should_interrupt` should be set to `Default::default()` if it is supposed to be unused.
121    /// Otherwise, it can be created by passing a `&'static AtomicBool`, `&Arc<AtomicBool>` or `Arc<AtomicBool>`.
122    pub fn dirwalk_iter(
123        &self,
124        index: impl Into<IndexPersistedOrInMemory>,
125        patterns: impl IntoIterator<Item = impl Into<BString>>,
126        should_interrupt: OwnedOrStaticAtomicBool,
127        options: dirwalk::Options,
128    ) -> Result<dirwalk::Iter, dirwalk::iter::Error> {
129        dirwalk::Iter::new(
130            self,
131            index.into(),
132            patterns.into_iter().map(Into::into).collect(),
133            should_interrupt,
134            options,
135        )
136    }
137}