Skip to content

Commit 94930b2

Browse files
committed
more natural top-level handling
- when deleting items, we will always list them according to the prefix - when listing items otherwise, the top-level only if the traversal starts in the worktree.
1 parent b6ea37a commit 94930b2

File tree

6 files changed

+542
-134
lines changed

6 files changed

+542
-134
lines changed

gix-dir/src/walk/function.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ use crate::{entry, EntryRef};
1818
/// * `ctx` - everything needed to classify the paths seen during the traversal.
1919
/// * `delegate` - an implementation of [`Delegate`] to control details of the traversal and receive its results.
2020
///
21+
/// Returns `(outcome, traversal_root)`, with the `traversal_root` actually being used for the traversal,
22+
/// useful to transform the paths returned for the user. It's always within the `worktree_root`, or the same,
23+
/// but is hard to guess due to additional logic affecting it.
24+
///
2125
/// ### Performance Notes
2226
///
2327
/// In theory, parallel directory traversal can be significantly faster, and what's possible for our current
@@ -40,7 +44,7 @@ pub fn walk(
4044
mut ctx: Context<'_>,
4145
options: Options,
4246
delegate: &mut dyn Delegate,
43-
) -> Result<Outcome, Error> {
47+
) -> Result<(Outcome, PathBuf), Error> {
4448
let root = match ctx.explicit_traversal_root {
4549
Some(root) => root.to_owned(),
4650
None => ctx
@@ -78,12 +82,17 @@ pub fn walk(
7882
&mut out,
7983
delegate,
8084
);
81-
return Ok(out);
85+
return Ok((out, root.to_owned()));
8286
}
8387

84-
let mut state = readdir::State::default();
85-
let _ = readdir::recursive(
86-
true,
88+
let mut state = readdir::State::new(worktree_root, ctx.current_dir, options.for_deletion.is_some());
89+
let may_collapse = if options.for_deletion.is_some() {
90+
false
91+
} else {
92+
root != worktree_root
93+
};
94+
let (action, _) = readdir::recursive(
95+
may_collapse,
8796
&mut current,
8897
&mut buf,
8998
root_info,
@@ -93,8 +102,12 @@ pub fn walk(
93102
&mut out,
94103
&mut state,
95104
)?;
105+
if action != Action::Cancel {
106+
state.emit_remaining(may_collapse, options, &mut out, delegate);
107+
assert_eq!(state.on_hold.len(), 0, "BUG: after emission, on hold must be empty");
108+
}
96109
gix_trace::debug!(statistics = ?out);
97-
Ok(out)
110+
Ok((out, root.to_owned()))
98111
}
99112

100113
/// Note that we only check symlinks on the way from `worktree_root` to `root`,

gix-dir/src/walk/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub struct Context<'a> {
182182
pub git_dir_realpath: &'a std::path::Path,
183183
/// The current working directory as returned by `gix_fs::current_dir()` to assure it respects `core.precomposeUnicode`.
184184
/// It's used to produce the realpath of the git-dir of a repository candidate to assure it's not our own repository.
185+
///
186+
/// It is also used to assure that when the walk is for deletion, that the current working dir will not be collapsed.
185187
pub current_dir: &'a std::path::Path,
186188
/// The index to quickly understand if a file or directory is tracked or not.
187189
///
@@ -233,7 +235,7 @@ pub struct Context<'a> {
233235
}
234236

235237
/// Additional information collected as outcome of [`walk()`](function::walk()).
236-
#[derive(Default, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
238+
#[derive(Default, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
237239
pub struct Outcome {
238240
/// The amount of calls to read the directory contents.
239241
pub read_dir_calls: u32,

gix-dir/src/walk/readdir.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bstr::{BStr, BString, ByteSlice};
22
use std::borrow::Cow;
3-
use std::path::PathBuf;
3+
use std::path::{Path, PathBuf};
44

55
use crate::entry::{PathspecMatch, Status};
66
use crate::walk::function::{can_recurse, emit_entry};
@@ -13,7 +13,7 @@ use crate::{entry, walk, Entry};
1313
/// Git mostly silently ignores IO errors and stops iterating seemingly quietly, while we error loudly.
1414
#[allow(clippy::too_many_arguments)]
1515
pub(super) fn recursive(
16-
is_top_level: bool,
16+
may_collapse: bool,
1717
current: &mut PathBuf,
1818
current_bstr: &mut BString,
1919
current_info: classify::Outcome,
@@ -30,7 +30,7 @@ pub(super) fn recursive(
3030
})?;
3131

3232
let mut num_entries = 0;
33-
let mark = state.mark(is_top_level);
33+
let mark = state.mark(may_collapse);
3434
let mut prevent_collapse = false;
3535
for entry in entries {
3636
let entry = entry.map_err(|err| Error::DirEntry {
@@ -64,8 +64,18 @@ pub(super) fn recursive(
6464
)?;
6565

6666
if can_recurse(current_bstr.as_bstr(), info, opts.for_deletion, delegate) {
67-
let (action, subdir_prevent_collapse) =
68-
recursive(false, current, current_bstr, info, ctx, opts, delegate, out, state)?;
67+
let subdir_may_collapse = state.may_collapse(current);
68+
let (action, subdir_prevent_collapse) = recursive(
69+
subdir_may_collapse,
70+
current,
71+
current_bstr,
72+
info,
73+
ctx,
74+
opts,
75+
delegate,
76+
out,
77+
state,
78+
)?;
6979
prevent_collapse |= subdir_prevent_collapse;
7080
if action != Action::Continue {
7181
return Ok((action, prevent_collapse));
@@ -94,10 +104,11 @@ pub(super) fn recursive(
94104
Ok((res, prevent_collapse))
95105
}
96106

97-
#[derive(Default)]
98107
pub(super) struct State {
99108
/// The entries to hold back until it's clear what to do with them.
100109
pub on_hold: Vec<Entry>,
110+
/// The path the user is currently in, as seen from the workdir root.
111+
worktree_relative_current_dir: Option<PathBuf>,
101112
}
102113

103114
impl State {
@@ -119,17 +130,60 @@ impl State {
119130

120131
/// Keep track of state we need to later resolve the state.
121132
/// Top-level directories are special, as they don't fold.
122-
fn mark(&self, is_top_level: bool) -> Mark {
133+
fn mark(&self, may_collapse: bool) -> Mark {
123134
Mark {
124135
start_index: self.on_hold.len(),
125-
is_top_level,
136+
may_collapse,
126137
}
127138
}
139+
140+
pub(super) fn new(worktree_root: &Path, current_dir: &Path, is_delete_mode: bool) -> Self {
141+
dbg!(current_dir, worktree_root);
142+
let worktree_relative_current_dir = if is_delete_mode {
143+
gix_path::realpath_opts(worktree_root, current_dir, gix_path::realpath::MAX_SYMLINKS)
144+
.ok()
145+
.and_then(|real_worktree_root| current_dir.strip_prefix(real_worktree_root).ok().map(ToOwned::to_owned))
146+
.map(|relative_cwd| worktree_root.join(relative_cwd))
147+
} else {
148+
None
149+
};
150+
Self {
151+
on_hold: Vec::new(),
152+
worktree_relative_current_dir,
153+
}
154+
}
155+
156+
/// Returns `true` if the worktree-relative `directory_to_traverse` is not the current working directory.
157+
/// This is only the case when
158+
pub(super) fn may_collapse(&self, directory_to_traverse: &Path) -> bool {
159+
dbg!(self.worktree_relative_current_dir.as_ref(), directory_to_traverse);
160+
self.worktree_relative_current_dir
161+
.as_ref()
162+
.map_or(true, |cwd| cwd != directory_to_traverse)
163+
}
164+
165+
pub(super) fn emit_remaining(
166+
&mut self,
167+
is_top_level: bool,
168+
opts: Options,
169+
out: &mut walk::Outcome,
170+
delegate: &mut dyn walk::Delegate,
171+
) {
172+
if self.on_hold.is_empty() {
173+
return;
174+
}
175+
176+
_ = Mark {
177+
start_index: 0,
178+
may_collapse: is_top_level,
179+
}
180+
.emit_all_held(self, opts, out, delegate);
181+
}
128182
}
129183

130184
struct Mark {
131185
start_index: usize,
132-
is_top_level: bool,
186+
may_collapse: bool,
133187
}
134188

135189
impl Mark {
@@ -211,7 +265,7 @@ impl Mark {
211265
ctx: &mut Context<'_>,
212266
delegate: &mut dyn walk::Delegate,
213267
) -> Option<Action> {
214-
if self.is_top_level {
268+
if !self.may_collapse {
215269
return None;
216270
}
217271
let (mut expendable, mut precious, mut untracked, mut entries, mut matching_entries) = (0, 0, 0, 0, 0);

gix-dir/tests/dir_walk_cwd.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn prefixes_work_as_expected() -> gix_testtools::Result {
1313
let root = fixture("only-untracked");
1414
std::env::set_current_dir(root.join("d"))?;
1515
let troot = Path::new("..").join("d");
16-
let (out, entries) = collect(Path::new(".."), Some(&troot), |keep, ctx| {
16+
let ((out, _root), entries) = collect(Path::new(".."), Some(&troot), |keep, ctx| {
1717
walk(Path::new(".."), ctx, options(), keep)
1818
});
1919
assert_eq!(

0 commit comments

Comments
 (0)