Skip to content

Commit ec0bcb5

Browse files
committed
feat!: simplify walk() signature to compute root with pathspec prefix.
This makes the overall handling more unified, while assuring it's always in the worktree.
1 parent 57f0a24 commit ec0bcb5

File tree

6 files changed

+678
-483
lines changed

6 files changed

+678
-483
lines changed

gix-dir/src/walk/function.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ use crate::{entry, EntryRef};
88

99
/// A function to perform a git-style, unsorted, directory walk.
1010
///
11-
/// * `root` - the starting point of the walk and a readable directory.
12-
/// - Note that if the path leading to this directory or `root` itself is excluded, it will be provided to [`Delegate::emit()`]
13-
/// without further traversal.
14-
/// - If [`Options::precompose_unicode`] is enabled, this path must be precomposed.
15-
/// - Must be contained in `worktree_root`.
1611
/// * `worktree_root` - the top-most root of the worktree, which must be a prefix to `root`.
1712
/// - If [`Options::precompose_unicode`] is enabled, this path must be precomposed.
13+
/// - The starting point of the traversal (traversal root) is calculated from by doing `worktree_root + pathspec.common_prefix()`.
14+
/// - Note that if the traversal root leading to this directory or it itself is excluded, it will be provided to [`Delegate::emit()`]
15+
/// without further traversal.
16+
/// - If [`Options::precompose_unicode`] is enabled, all involved paths must be precomposed.
17+
/// - Must be contained in `worktree_root`.
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
///
@@ -36,14 +36,24 @@ use crate::{entry, EntryRef};
3636
/// or 0.25s for optimal multi-threaded performance, all in the WebKit directory with 388k items to traverse.
3737
/// Thus, the speedup could easily be 2x or more and thus worth investigating in due time.
3838
pub fn walk(
39-
root: &Path,
4039
worktree_root: &Path,
4140
mut ctx: Context<'_>,
4241
options: Options,
4342
delegate: &mut dyn Delegate,
4443
) -> Result<Outcome, Error> {
44+
let root = match ctx.explicit_traversal_root {
45+
Some(root) => root.to_owned(),
46+
None => ctx
47+
.pathspec
48+
.longest_common_directory()
49+
.and_then(|candidate| {
50+
let candidate = worktree_root.join(candidate);
51+
candidate.is_dir().then_some(candidate)
52+
})
53+
.unwrap_or_else(|| worktree_root.join(ctx.pathspec.prefix_directory())),
54+
};
4555
let _span = gix_trace::coarse!("walk", root = ?root, worktree_root = ?worktree_root, options = ?options);
46-
let (mut current, worktree_root_relative) = assure_no_symlink_in_root(worktree_root, root)?;
56+
let (mut current, worktree_root_relative) = assure_no_symlink_in_root(worktree_root, &root)?;
4757
let mut out = Outcome::default();
4858
let mut buf = BString::default();
4959
let root_info = classify::root(
@@ -73,7 +83,7 @@ pub fn walk(
7383

7484
let mut state = readdir::State::default();
7585
let _ = readdir::recursive(
76-
root == worktree_root,
86+
true,
7787
&mut current,
7888
&mut buf,
7989
root_info,
@@ -95,10 +105,9 @@ fn assure_no_symlink_in_root<'root>(
95105
root: &'root Path,
96106
) -> Result<(PathBuf, Cow<'root, Path>), Error> {
97107
let mut current = worktree_root.to_owned();
98-
let worktree_relative = root.strip_prefix(worktree_root).map_err(|_| Error::RootNotInWorktree {
99-
worktree_root: worktree_root.to_owned(),
100-
root: root.to_owned(),
101-
})?;
108+
let worktree_relative = root
109+
.strip_prefix(worktree_root)
110+
.expect("BUG: root was created from worktree_root + prefix");
102111
let worktree_relative = gix_path::normalize(worktree_relative.into(), Path::new(""))
103112
.ok_or(Error::NormalizeRoot { root: root.to_owned() })?;
104113

gix-dir/src/walk/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ pub struct Context<'a> {
200200
/// in case-sensitive mode. It does, however, skip the directory hash creation (for looking
201201
/// up directories) unless `core.ignoreCase` is enabled.
202202
///
203-
/// We only use the hashmap when when available and when [`ignore_case`](Options::ignore_case) is enabled in the options.
203+
/// We only use the hashmap when available and when [`ignore_case`](Options::ignore_case) is enabled in the options.
204204
pub ignore_case_index_lookup: Option<&'a gix_index::AccelerateLookup<'a>>,
205205
/// A pathspec to use as filter - we only traverse into directories if it matches.
206206
/// Note that the `ignore_case` setting it uses should match our [Options::ignore_case].
@@ -221,6 +221,15 @@ pub struct Context<'a> {
221221
pub excludes: Option<&'a mut gix_worktree::Stack>,
222222
/// Access to the object database for use with `excludes` - it's possible to access `.gitignore` files in the index if configured.
223223
pub objects: &'a dyn gix_object::Find,
224+
/// If not `None`, override the traversal root that is computed and use this one instead.
225+
///
226+
/// This can be useful if the traversal root may be a file, in which case the traversal will
227+
/// still be returning possibly matching root entries.
228+
///
229+
/// ### Panics
230+
///
231+
/// If the `traversal_root` is not in the `worktree_root` passed to [walk()](crate::walk()).
232+
pub explicit_traversal_root: Option<&'a std::path::Path>,
224233
}
225234

226235
/// Additional information collected as outcome of [`walk()`](function::walk()).
@@ -242,8 +251,6 @@ pub enum Error {
242251
WorktreeRootIsFile { root: PathBuf },
243252
#[error("Traversal root '{}' contains relative path components and could not be normalized", root.display())]
244253
NormalizeRoot { root: PathBuf },
245-
#[error("Traversal root '{}' must be literally contained in worktree root '{}'", root.display(), worktree_root.display())]
246-
RootNotInWorktree { root: PathBuf, worktree_root: PathBuf },
247254
#[error("A symlink was found at component {component_index} of traversal root '{}' as seen from worktree root '{}'", root.display(), worktree_root.display())]
248255
SymlinkInRoot {
249256
root: PathBuf,

gix-dir/src/walk/readdir.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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_worktree_dir: bool,
16+
is_top_level: 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_worktree_dir);
33+
let mark = state.mark(is_top_level);
3434
let mut prevent_collapse = false;
3535
for entry in entries {
3636
let entry = entry.map_err(|err| Error::DirEntry {
@@ -118,18 +118,18 @@ impl State {
118118
}
119119

120120
/// Keep track of state we need to later resolve the state.
121-
/// Worktree directories are special, as they don't fold.
122-
fn mark(&self, is_worktree_dir: bool) -> Mark {
121+
/// Top-level directories are special, as they don't fold.
122+
fn mark(&self, is_top_level: bool) -> Mark {
123123
Mark {
124124
start_index: self.on_hold.len(),
125-
is_worktree_dir,
125+
is_top_level,
126126
}
127127
}
128128
}
129129

130130
struct Mark {
131131
start_index: usize,
132-
is_worktree_dir: bool,
132+
is_top_level: bool,
133133
}
134134

135135
impl Mark {
@@ -211,7 +211,7 @@ impl Mark {
211211
ctx: &mut Context<'_>,
212212
delegate: &mut dyn walk::Delegate,
213213
) -> Option<Action> {
214-
if self.is_worktree_dir {
214+
if self.is_top_level {
215215
return None;
216216
}
217217
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: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
use crate::walk_utils::{collect, entry, fixture, options};
1+
use crate::walk_utils::{collect, entryps, fixture, options};
22
use gix_dir::entry::Kind::File;
3+
use gix_dir::entry::PathspecMatch::Prefix;
34
use gix_dir::entry::Status::Untracked;
45
use gix_dir::walk;
6+
use pretty_assertions::assert_eq;
57
use std::path::Path;
68

79
pub mod walk_utils;
@@ -10,8 +12,9 @@ pub mod walk_utils;
1012
fn prefixes_work_as_expected() -> gix_testtools::Result {
1113
let root = fixture("only-untracked");
1214
std::env::set_current_dir(root.join("d"))?;
13-
let (out, entries) = collect(&root, |keep, ctx| {
14-
walk(&Path::new("..").join("d"), Path::new(".."), ctx, options(), keep)
15+
let troot = Path::new("..").join("d");
16+
let (out, entries) = collect(Path::new(".."), Some(&troot), |keep, ctx| {
17+
walk(Path::new(".."), ctx, options(), keep)
1518
});
1619
assert_eq!(
1720
out,
@@ -24,9 +27,9 @@ fn prefixes_work_as_expected() -> gix_testtools::Result {
2427
assert_eq!(
2528
&entries,
2629
&[
27-
entry("d/a", Untracked, File),
28-
entry("d/b", Untracked, File),
29-
entry("d/d/a", Untracked, File),
30+
entryps("d/a", Untracked, File, Prefix),
31+
entryps("d/b", Untracked, File, Prefix),
32+
entryps("d/d/a", Untracked, File, Prefix),
3033
]
3134
);
3235
Ok(())

0 commit comments

Comments
 (0)