Skip to content

Commit 3e7635b

Browse files
committed
feat!: allow to emit all collapsed entries.
This is useful for rename tracking as it allows to see all files that may take part in a rename (i.e. when a directory is renamed).
1 parent 92c44ba commit 3e7635b

File tree

4 files changed

+85
-8
lines changed

4 files changed

+85
-8
lines changed

gix-dir/src/walk/mod.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ pub trait Delegate {
5858
/// item isn't yet known. Pruned entries are also only emitted if [`Options::emit_pruned`] is `true`.
5959
///
6060
/// `collapsed_directory_status` is `Some(dir_status)` if this entry was part of a directory with the given
61-
/// `dir_status` that wasn't the same as the one of `entry`. Depending on the operation, these then want to be
62-
/// used or discarded.
61+
/// `dir_status` that wasn't the same as the one of `entry` and if [Options::emit_collapsed] was
62+
/// [CollapsedEntriesEmissionMode::OnStatusMismatch]. It will also be `Some(dir_status)` if that option
63+
/// was [CollapsedEntriesEmissionMode::All].
6364
fn emit(&mut self, entry: EntryRef<'_>, collapsed_directory_status: Option<entry::Status>) -> Action;
6465

6566
/// Return `true` if the given entry can be recursed into. Will only be called if the entry is a physical directory.
@@ -94,6 +95,23 @@ pub enum EmissionMode {
9495
CollapseDirectory,
9596
}
9697

98+
/// The way entries that are contained in collapsed directories are emitted using the [Delegate].
99+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
100+
pub enum CollapsedEntriesEmissionMode {
101+
/// Emit only entries if their status does not match the one of the parent directory that is
102+
/// going to be collapsed.
103+
///
104+
/// E.g. if a directory is determined to be untracked, and the entries in question are ignored,
105+
/// they will be emitted.
106+
///
107+
/// Entries that have the same status will essentially be 'merged' into the collapsing directory
108+
/// and won't be observable anymore.
109+
#[default]
110+
OnStatusMismatch,
111+
/// Emit all entries inside of a collapsed directory to make them observable.
112+
All,
113+
}
114+
97115
/// When the walk is for deletion, assure that we don't collapse directories that have precious files in
98116
/// them, and otherwise assure that no entries are observable that shouldn't be deleted.
99117
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
@@ -151,6 +169,8 @@ pub struct Options {
151169
/// subdirectories, as long as none of them includes a file.
152170
/// Thus, this makes leaf-level empty directories visible, as those don't have any content.
153171
pub emit_empty_directories: bool,
172+
/// If `None`, no entries inside of collapsed directories are emitted. Otherwise, act as specified by `Some(mode)`.
173+
pub emit_collapsed: Option<CollapsedEntriesEmissionMode>,
154174
}
155175

156176
/// All information that is required to perform a dirwalk, and classify paths properly.

gix-dir/src/walk/readdir.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::path::PathBuf;
55
use crate::entry::{PathspecMatch, Status};
66
use crate::walk::function::{can_recurse, emit_entry};
77
use crate::walk::EmissionMode::CollapseDirectory;
8-
use crate::walk::{classify, Action, Context, Delegate, Error, Options, Outcome};
8+
use crate::walk::{classify, Action, CollapsedEntriesEmissionMode, Context, Delegate, Error, Options, Outcome};
99
use crate::{entry, walk, Entry};
1010

1111
/// ### Deviation
@@ -286,12 +286,21 @@ impl Mark {
286286
let mut removed_without_emitting = 0;
287287
let mut action = Action::Continue;
288288
for entry in state.on_hold.drain(self.start_index..) {
289-
if entry.status != dir_status && action == Action::Continue {
290-
let info = classify::Outcome::from(&entry);
291-
action = emit_entry(Cow::Owned(entry.rela_path), info, Some(dir_status), opts, out, delegate);
292-
} else {
289+
if action != Action::Continue {
293290
removed_without_emitting += 1;
294-
};
291+
continue;
292+
}
293+
match opts.emit_collapsed {
294+
Some(mode) => {
295+
if mode == CollapsedEntriesEmissionMode::All || entry.status != dir_status {
296+
let info = classify::Outcome::from(&entry);
297+
action = emit_entry(Cow::Owned(entry.rela_path), info, Some(dir_status), opts, out, delegate);
298+
} else {
299+
removed_without_emitting += 1;
300+
}
301+
}
302+
None => removed_without_emitting += 1,
303+
}
295304
}
296305
out.seen_entries += removed_without_emitting as u32;
297306

gix-dir/tests/walk/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use gix_dir::entry;
1010
use gix_dir::entry::Kind::*;
1111
use gix_dir::entry::PathspecMatch::*;
1212
use gix_dir::entry::Status::*;
13+
use gix_dir::walk::CollapsedEntriesEmissionMode::{All, OnStatusMismatch};
1314
use gix_dir::walk::EmissionMode::*;
1415
use gix_dir::walk::ForDeletionMode;
1516
use gix_ignore::Kind::*;
@@ -1060,6 +1061,7 @@ fn untracked_and_ignored() -> crate::Result {
10601061
walk::Options {
10611062
emit_ignored: Some(CollapseDirectory),
10621063
emit_untracked: CollapseDirectory,
1064+
emit_collapsed: Some(OnStatusMismatch),
10631065
..options()
10641066
},
10651067
keep,
@@ -1147,6 +1149,7 @@ fn untracked_and_ignored_collapse_handling_mixed() -> crate::Result {
11471149
emit_ignored: Some(CollapseDirectory),
11481150
emit_untracked: CollapseDirectory,
11491151
for_deletion: None,
1152+
emit_collapsed: Some(OnStatusMismatch),
11501153
..options()
11511154
},
11521155
keep,
@@ -1242,6 +1245,7 @@ fn untracked_and_ignored_collapse_handling_for_deletion_with_wildcards() -> crat
12421245
emit_ignored: Some(CollapseDirectory),
12431246
emit_untracked: CollapseDirectory,
12441247
for_deletion: Some(Default::default()),
1248+
emit_collapsed: Some(OnStatusMismatch),
12451249
..options()
12461250
},
12471251
keep,
@@ -1363,6 +1367,7 @@ fn untracked_and_ignored_collapse_handling_for_deletion_mixed() -> crate::Result
13631367
emit_ignored: Some(CollapseDirectory),
13641368
emit_untracked: CollapseDirectory,
13651369
for_deletion: Some(Default::default()),
1370+
emit_collapsed: Some(OnStatusMismatch),
13661371
..options()
13671372
},
13681373
keep,
@@ -1408,6 +1413,7 @@ fn untracked_and_ignored_collapse_handling_for_deletion_mixed() -> crate::Result
14081413
emit_ignored: Some(CollapseDirectory),
14091414
emit_untracked: CollapseDirectory,
14101415
for_deletion: Some(Default::default()),
1416+
emit_collapsed: Some(OnStatusMismatch),
14111417
..options()
14121418
},
14131419
keep,
@@ -1489,6 +1495,7 @@ fn untracked_and_ignored_collapse_handling_for_deletion_mixed() -> crate::Result
14891495
emit_ignored: Some(CollapseDirectory),
14901496
emit_untracked: CollapseDirectory,
14911497
for_deletion: Some(Default::default()),
1498+
emit_collapsed: Some(OnStatusMismatch),
14921499
..options()
14931500
},
14941501
keep,
@@ -1590,6 +1597,7 @@ fn precious_are_not_expendable() {
15901597
walk::Options {
15911598
emit_ignored: Some(CollapseDirectory),
15921599
emit_untracked: CollapseDirectory,
1600+
emit_collapsed: Some(OnStatusMismatch),
15931601
..options()
15941602
},
15951603
keep,
@@ -1629,6 +1637,7 @@ fn precious_are_not_expendable() {
16291637
walk::Options {
16301638
emit_ignored: Some(CollapseDirectory),
16311639
emit_untracked: CollapseDirectory,
1640+
emit_collapsed: Some(OnStatusMismatch),
16321641
..options()
16331642
},
16341643
keep,
@@ -2763,6 +2772,7 @@ fn untracked_and_ignored_collapse_mix() {
27632772
walk::Options {
27642773
emit_ignored: Some(Matching),
27652774
emit_untracked: CollapseDirectory,
2775+
emit_collapsed: Some(OnStatusMismatch),
27662776
..options_emit_all()
27672777
},
27682778
keep,
@@ -2788,6 +2798,43 @@ fn untracked_and_ignored_collapse_mix() {
27882798
],
27892799
"untracked collapses separately from ignored, but note that matching directories are still emitted, i.e. ignored/"
27902800
);
2801+
2802+
let (out, entries) = collect(&root, |keep, ctx| {
2803+
walk(
2804+
&root,
2805+
&root,
2806+
ctx,
2807+
walk::Options {
2808+
emit_ignored: Some(Matching),
2809+
emit_untracked: CollapseDirectory,
2810+
emit_collapsed: Some(All),
2811+
..options_emit_all()
2812+
},
2813+
keep,
2814+
)
2815+
});
2816+
assert_eq!(
2817+
out,
2818+
walk::Outcome {
2819+
read_dir_calls: 4,
2820+
returned_entries: entries.len(),
2821+
seen_entries: 8,
2822+
}
2823+
);
2824+
assert_eq!(
2825+
entries,
2826+
[
2827+
entry(".gitignore", Untracked, File),
2828+
entry("ignored", Ignored(Expendable), Directory),
2829+
entry("ignored-inside/d.o", Ignored(Expendable), File),
2830+
entry("mixed", Untracked, Directory),
2831+
entry_dirstat("mixed/c", Untracked, File, Untracked),
2832+
entry_dirstat("mixed/c.o", Ignored(Expendable), File, Untracked),
2833+
entry("untracked", Untracked, Directory),
2834+
entry_dirstat("untracked/a", Untracked, File, Untracked),
2835+
],
2836+
"we can also emit all collapsed entries"
2837+
);
27912838
}
27922839

27932840
#[test]

gix-dir/tests/walk_utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub fn options_emit_all() -> walk::Options {
3030
emit_tracked: true,
3131
emit_untracked: walk::EmissionMode::Matching,
3232
emit_empty_directories: true,
33+
emit_collapsed: None,
3334
}
3435
}
3536

0 commit comments

Comments
 (0)