Skip to content

Commit 5b47567

Browse files
committed
fix: assure empty paths are always matching a Search
This improvement was triggered by [this question](rust-lang/cargo#13777 (review)).
1 parent 8d610ab commit 5b47567

File tree

2 files changed

+42
-10
lines changed

2 files changed

+42
-10
lines changed

gix-pathspec/src/search/matching.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ impl Search {
3131
is_dir: Option<bool>,
3232
attributes: &mut dyn FnMut(&BStr, Case, bool, &mut gix_attributes::search::Outcome) -> bool,
3333
) -> Option<Match<'_>> {
34+
static MATCH_ALL_STAND_IN: Pattern = Pattern {
35+
path: BString::new(Vec::new()),
36+
signature: MagicSignature::empty(),
37+
search_mode: SearchMode::ShellGlob,
38+
attributes: Vec::new(),
39+
prefix_len: 0,
40+
nil: true,
41+
};
42+
if relative_path.is_empty() {
43+
return Some(Match {
44+
pattern: &MATCH_ALL_STAND_IN,
45+
sequence_number: 0,
46+
kind: Always,
47+
});
48+
}
3449
let basename_not_important = None;
3550
if relative_path
3651
.get(..self.common_prefix_len)
@@ -106,14 +121,6 @@ impl Search {
106121
});
107122

108123
if res.is_none() && self.all_patterns_are_excluded {
109-
static MATCH_ALL_STAND_IN: Pattern = Pattern {
110-
path: BString::new(Vec::new()),
111-
signature: MagicSignature::empty(),
112-
search_mode: SearchMode::ShellGlob,
113-
attributes: Vec::new(),
114-
prefix_len: 0,
115-
nil: true,
116-
};
117124
Some(Match {
118125
pattern: &MATCH_ALL_STAND_IN,
119126
sequence_number: patterns_len,
@@ -133,7 +140,7 @@ impl Search {
133140
/// is ignored.
134141
/// Returns `false` if this pathspec has no chance of ever matching `relative_path`.
135142
pub fn can_match_relative_path(&self, relative_path: &BStr, is_dir: Option<bool>) -> bool {
136-
if self.patterns.is_empty() {
143+
if self.patterns.is_empty() || relative_path.is_empty() {
137144
return true;
138145
}
139146
let common_prefix_len = self.common_prefix_len.min(relative_path.len());
@@ -194,7 +201,7 @@ impl Search {
194201
/// When `leading` is `true`, then `d` matches `d/d` as well. Thus, `relative_path` must may be
195202
/// partially included in `pathspec`, otherwise it has to be fully included.
196203
pub fn directory_matches_prefix(&self, relative_path: &BStr, leading: bool) -> bool {
197-
if self.patterns.is_empty() {
204+
if self.patterns.is_empty() || relative_path.is_empty() {
198205
return true;
199206
}
200207
let common_prefix_len = self.common_prefix_len.min(relative_path.len());

gix-pathspec/tests/search/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ fn directory_matches_prefix_starting_wildcards_always_match() -> crate::Result {
5757
Ok(())
5858
}
5959

60+
#[test]
61+
fn empty_dir_always_matches() -> crate::Result {
62+
for specs in [
63+
&["*ir"] as &[_],
64+
&[],
65+
&["included", ":!excluded"],
66+
&[":!all", ":!excluded"],
67+
] {
68+
let mut search = gix_pathspec::Search::from_specs(pathspecs(specs), None, Path::new(""))?;
69+
assert_eq!(
70+
search
71+
.pattern_matching_relative_path("".into(), None, &mut no_attrs)
72+
.map(|m| m.kind),
73+
Some(Always),
74+
"{specs:?}"
75+
);
76+
assert!(search.directory_matches_prefix("".into(), false));
77+
assert!(search.directory_matches_prefix("".into(), false));
78+
for is_dir in [Some(true), Some(false), None] {
79+
assert!(search.can_match_relative_path("".into(), is_dir));
80+
}
81+
}
82+
Ok(())
83+
}
84+
6085
#[test]
6186
fn directory_matches_prefix_leading() -> crate::Result {
6287
let search = gix_pathspec::Search::from_specs(pathspecs(&["d/d/generated/b"]), None, Path::new(""))?;

0 commit comments

Comments
 (0)