Skip to content

Commit cf28855

Browse files
committed
make sure that *foo* prefixes don't end up matching any directory.
Even though wildcard pathspecs shouldn't prevent recursion into direcotries, we should not end up declaring them as matches even though nothing inside matched.
1 parent a663e9f commit cf28855

File tree

3 files changed

+205
-5
lines changed

3 files changed

+205
-5
lines changed

gix-dir/src/walk/readdir.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl Mark {
158158
} else {
159159
dir_info.disk_kind
160160
},
161+
pathspec_match: filter_dir_pathspec(dir_info.pathspec_match),
161162
..dir_info
162163
};
163164
if opts.should_hold(empty_info.status) {
@@ -287,12 +288,10 @@ impl Mark {
287288
.filter_map(|e| e.pathspec_match)
288289
.max()
289290
.or_else(|| {
290-
// Only take directory matches for value if they are above the 'guessed' ones.
291+
// Only take directory matches as value if they are above the 'guessed' ones.
291292
// Otherwise we end up with seemingly matched entries in the parent directory which
292293
// affects proper folding.
293-
dir_info
294-
.pathspec_match
295-
.filter(|m| matches!(m, PathspecMatch::WildcardMatch | PathspecMatch::Verbatim))
294+
filter_dir_pathspec(dir_info.pathspec_match)
296295
});
297296
let mut removed_without_emitting = 0;
298297
let mut action = Action::Continue;
@@ -317,6 +316,15 @@ impl Mark {
317316
}
318317
}
319318

319+
fn filter_dir_pathspec(current: Option<PathspecMatch>) -> Option<PathspecMatch> {
320+
current.filter(|m| {
321+
matches!(
322+
m,
323+
PathspecMatch::Always | PathspecMatch::WildcardMatch | PathspecMatch::Verbatim
324+
)
325+
})
326+
}
327+
320328
impl Options {
321329
fn should_hold(&self, status: entry::Status) -> bool {
322330
if status.is_pruned() {

gix-dir/tests/fixtures/many.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ cp -R ignored-dir ignored-dir-with-nested-bare-repository
5757
git init --bare bare
5858
)
5959

60+
cp -R ignored-dir-with-nested-bare-repository ignored-dir-nested-minimal
61+
(cd ignored-dir-nested-minimal
62+
(cd bare
63+
rm -Rf hooks config description
64+
)
65+
(cd dir/subdir/nested-bare
66+
rm -Rf refs hooks config description
67+
)
68+
)
69+
6070
mkdir untracked-hidden-bare
6171
(cd untracked-hidden-bare
6272
mkdir subdir

gix-dir/tests/walk/mod.rs

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ fn untracked_and_ignored_pathspec_guidance() -> crate::Result {
578578
}
579579

580580
#[test]
581-
fn untracked_and_ignored_for_deletion_negative_spec() -> crate::Result {
581+
fn untracked_and_ignored_for_deletion_negative_wildcard_spec() -> crate::Result {
582582
let root = fixture("subdir-untracked-and-ignored");
583583
let (out, entries) = collect_filtered(
584584
&root,
@@ -632,6 +632,188 @@ fn untracked_and_ignored_for_deletion_negative_spec() -> crate::Result {
632632
Ok(())
633633
}
634634

635+
#[test]
636+
fn untracked_and_ignored_for_deletion_positive_wildcard_spec() -> crate::Result {
637+
let root = fixture("subdir-untracked-and-ignored");
638+
let (out, entries) = collect_filtered(
639+
&root,
640+
|keep, ctx| {
641+
walk(
642+
&root,
643+
&root,
644+
ctx,
645+
walk::Options {
646+
emit_ignored: Some(CollapseDirectory),
647+
emit_untracked: CollapseDirectory,
648+
emit_pruned: true,
649+
for_deletion: Some(Default::default()),
650+
..options()
651+
},
652+
keep,
653+
)
654+
},
655+
Some("*generated*"),
656+
);
657+
assert_eq!(
658+
out,
659+
walk::Outcome {
660+
read_dir_calls: 8,
661+
returned_entries: entries.len(),
662+
seen_entries: 27,
663+
},
664+
);
665+
assert_eq!(
666+
&entries,
667+
&[
668+
entry_nokind(".git", DotGit),
669+
entry_nomatch(".gitignore", Pruned, File),
670+
entry_nomatch("a.o", Ignored(Expendable), File),
671+
entry_nomatch("b.o", Ignored(Expendable), File),
672+
entry_nomatch("c.o", Ignored(Expendable), File),
673+
entry_nomatch("d/a.o", Ignored(Expendable), File),
674+
entry_nomatch("d/b.o", Ignored(Expendable), File),
675+
entry_nomatch("d/d/a", Pruned, File),
676+
entry_nomatch("d/d/a.o", Ignored(Expendable), File),
677+
entry_nomatch("d/d/b.o", Ignored(Expendable), File),
678+
entryps("d/d/generated", Ignored(Expendable), Directory, WildcardMatch),
679+
entryps("d/generated", Ignored(Expendable), Directory, WildcardMatch),
680+
entryps("generated", Ignored(Expendable), Directory, WildcardMatch),
681+
entry_nomatch("objs", Ignored(Expendable), Directory),
682+
],
683+
"'generated' folders are included, and collapsing is done where possible"
684+
);
685+
Ok(())
686+
}
687+
688+
#[test]
689+
fn untracked_and_ignored_for_deletion_nonmatching_wildcard_spec() -> crate::Result {
690+
let root = fixture("subdir-untracked-and-ignored");
691+
let (out, entries) = collect_filtered(
692+
&root,
693+
|keep, ctx| {
694+
walk(
695+
&root,
696+
&root,
697+
ctx,
698+
walk::Options {
699+
emit_ignored: Some(CollapseDirectory),
700+
emit_untracked: CollapseDirectory,
701+
emit_pruned: true,
702+
for_deletion: Some(Default::default()),
703+
..options()
704+
},
705+
keep,
706+
)
707+
},
708+
Some("*foo*"),
709+
);
710+
assert_eq!(
711+
out,
712+
walk::Outcome {
713+
read_dir_calls: 8,
714+
returned_entries: entries.len(),
715+
seen_entries: 28,
716+
},
717+
);
718+
assert_eq!(
719+
&entries,
720+
&[
721+
entry_nokind(".git", DotGit),
722+
entry_nomatch(".gitignore", Pruned, File),
723+
entry_nomatch("a.o", Ignored(Expendable), File),
724+
entry_nomatch("b.o", Ignored(Expendable), File),
725+
entry_nomatch("c.o", Ignored(Expendable), File),
726+
entry_nomatch("d/a.o", Ignored(Expendable), File),
727+
entry_nomatch("d/b.o", Ignored(Expendable), File),
728+
entry_nomatch("d/d", Ignored(Expendable), Directory),
729+
entry_nomatch("d/d/a", Pruned, File),
730+
entry_nomatch("d/generated", Ignored(Expendable), Directory),
731+
entry_nomatch("generated", Ignored(Expendable), Directory),
732+
entry_nomatch("objs", Ignored(Expendable), Directory),
733+
],
734+
"'generated' folders are included, and collapsing is done where possible"
735+
);
736+
Ok(())
737+
}
738+
739+
#[test]
740+
fn nested_ignored_dirs_for_deletion_nonmatching_wildcard_spec() -> crate::Result {
741+
let root = fixture("ignored-dir-nested-minimal");
742+
let (out, entries) = collect_filtered(
743+
&root,
744+
|keep, ctx| {
745+
walk(
746+
&root,
747+
&root,
748+
ctx,
749+
walk::Options {
750+
emit_ignored: Some(CollapseDirectory),
751+
emit_untracked: CollapseDirectory,
752+
emit_pruned: false,
753+
for_deletion: Some(Default::default()),
754+
..options()
755+
},
756+
keep,
757+
)
758+
},
759+
Some("*foo*"),
760+
);
761+
assert_eq!(
762+
out,
763+
walk::Outcome {
764+
read_dir_calls: 16,
765+
returned_entries: entries.len(),
766+
seen_entries: 21,
767+
},
768+
);
769+
assert_eq!(
770+
&entries,
771+
&[],
772+
"it figures out that nothing actually matches, even though it has to check everything"
773+
);
774+
775+
let (out, entries) = collect_filtered(
776+
&root,
777+
|keep, ctx| {
778+
walk(
779+
&root,
780+
&root,
781+
ctx,
782+
walk::Options {
783+
emit_ignored: Some(CollapseDirectory),
784+
emit_untracked: CollapseDirectory,
785+
emit_pruned: true,
786+
for_deletion: Some(Default::default()),
787+
..options()
788+
},
789+
keep,
790+
)
791+
},
792+
Some("*foo*"),
793+
);
794+
assert_eq!(
795+
out,
796+
walk::Outcome {
797+
read_dir_calls: 16,
798+
returned_entries: entries.len(),
799+
seen_entries: 21,
800+
},
801+
);
802+
assert_eq!(
803+
&entries,
804+
&[
805+
entry_nokind(".git", DotGit),
806+
entry_nomatch(".gitignore", Pruned, File),
807+
entry_nomatch("bare", Untracked, Directory),
808+
entry_nomatch("bare/HEAD", Pruned, File),
809+
entry_nomatch("bare/info/exclude", Pruned, File),
810+
entry_nomatch("dir", Ignored(Expendable), Directory),
811+
],
812+
"it's possible to observe pruned entries like before"
813+
);
814+
Ok(())
815+
}
816+
635817
#[test]
636818
fn untracked_and_ignored() -> crate::Result {
637819
let root = fixture("subdir-untracked-and-ignored");

0 commit comments

Comments
 (0)