diff --git a/gix-status/src/index_as_worktree_with_renames/mod.rs b/gix-status/src/index_as_worktree_with_renames/mod.rs index a3953bbed78..719bfcd738c 100644 --- a/gix-status/src/index_as_worktree_with_renames/mod.rs +++ b/gix-status/src/index_as_worktree_with_renames/mod.rs @@ -49,7 +49,7 @@ pub(super) mod function { objects: Find, progress: &mut dyn gix_features::progress::Progress, mut ctx: Context<'_>, - options: Options, + options: Options<'_>, ) -> Result where T: Send + Clone, diff --git a/gix-status/src/index_as_worktree_with_renames/types.rs b/gix-status/src/index_as_worktree_with_renames/types.rs index 84ba2e24ec5..dbfdefc8a98 100644 --- a/gix-status/src/index_as_worktree_with_renames/types.rs +++ b/gix-status/src/index_as_worktree_with_renames/types.rs @@ -274,7 +274,7 @@ impl Entry<'_, ContentChange, SubmoduleStatus> { /// Options for use in [index_as_worktree_with_renames()](crate::index_as_worktree_with_renames()). #[derive(Clone, Default)] -pub struct Options { +pub struct Options<'a> { /// The way all output should be sorted. /// /// If `None`, and depending on the `rewrites` field, output will be immediate but the output order @@ -299,7 +299,7 @@ pub struct Options { /// /// If `None`, the directory walk portion will not run at all, yielding data similar /// to a bare [index_as_worktree()](crate::index_as_worktree()) call. - pub dirwalk: Option, + pub dirwalk: Option>, /// The configuration for the rewrite tracking. Note that if set, the [`dirwalk`](Self::dirwalk) should be configured /// to *not* collapse untracked and ignored entries, as rewrite tracking is on a file-by-file basis. /// Also note that when `Some(_)`, it will collect certain changes depending on the exact configuration, which typically increases diff --git a/gix/src/dirwalk/mod.rs b/gix/src/dirwalk/mod.rs index 4d590c3f36c..8f9ced6ef2a 100644 --- a/gix/src/dirwalk/mod.rs +++ b/gix/src/dirwalk/mod.rs @@ -54,6 +54,8 @@ pub enum Error { Prefix(#[from] gix_path::realpath::Error), #[error(transparent)] FilesystemOptions(#[from] config::boolean::Error), + #[error("Could not list worktrees to assure they are no candidates for deletion")] + ListWorktrees(#[from] std::io::Error), } /// The outcome of the [dirwalk()](crate::Repository::dirwalk). diff --git a/gix/src/dirwalk/options.rs b/gix/src/dirwalk/options.rs index 1ce6a262c53..dffb09a1972 100644 --- a/gix/src/dirwalk/options.rs +++ b/gix/src/dirwalk/options.rs @@ -22,7 +22,7 @@ impl Options { } } -impl From for gix_dir::walk::Options { +impl From for gix_dir::walk::Options<'static> { fn from(v: Options) -> Self { gix_dir::walk::Options { precompose_unicode: v.precompose_unicode, @@ -38,6 +38,7 @@ impl From for gix_dir::walk::Options { emit_collapsed: v.emit_collapsed, symlinks_to_directories_are_ignored_like_directories: v .symlinks_to_directories_are_ignored_like_directories, + worktree_relative_worktree_dirs: None, } } } diff --git a/gix/src/repository/dirwalk.rs b/gix/src/repository/dirwalk.rs index db64410e42a..8bc2656ec48 100644 --- a/gix/src/repository/dirwalk.rs +++ b/gix/src/repository/dirwalk.rs @@ -20,6 +20,12 @@ impl Repository { /// lookup. Note that items will only count as tracked if they have the [`gix_index::entry::Flags::UPTODATE`] /// flag set. /// + /// Note that dirwalks for the purpose of deletion will be initialized with the worktrees of this repository + /// if they fall into the working directory of this repository as well to mark them as `tracked`. That way + /// it's hard to accidentally flag them for deletion. + /// This is intentionally not the case when deletion is not intended so they look like + /// untracked repositories instead. + /// /// See [`gix_dir::walk::delegate::Collect`] for a delegate that collects all seen entries. pub fn dirwalk( &self, @@ -48,6 +54,27 @@ impl Repository { crate::path::realpath_opts(self.git_dir(), self.current_dir(), crate::path::realpath::MAX_SYMLINKS)?; let fs_caps = self.filesystem_options()?; let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing()); + let mut opts = gix_dir::walk::Options::from(options); + let worktree_relative_worktree_dirs_storage; + if let Some(workdir) = self.work_dir().filter(|_| opts.for_deletion.is_some()) { + let linked_worktrees = self.worktrees()?; + if !linked_worktrees.is_empty() { + let real_workdir = gix_path::realpath_opts( + workdir, + self.options.current_dir_or_empty(), + gix_path::realpath::MAX_SYMLINKS, + )?; + worktree_relative_worktree_dirs_storage = linked_worktrees + .into_iter() + .filter_map(|proxy| proxy.base().ok()) + .filter_map(|base| base.strip_prefix(&real_workdir).map(ToOwned::to_owned).ok()) + .map(|rela_path| { + gix_path::to_unix_separators_on_windows(gix_path::into_bstr(rela_path)).into_owned() + }) + .collect(); + opts.worktree_relative_worktree_dirs = Some(&worktree_relative_worktree_dirs_storage); + } + } let (outcome, traversal_root) = gix_dir::walk( workdir, gix_dir::walk::Context { @@ -71,7 +98,7 @@ impl Repository { objects: &self.objects, explicit_traversal_root: (!options.empty_patterns_match_prefix).then_some(workdir), }, - options.into(), + opts, delegate, )?;