Skip to content

Commit dbf3be5

Browse files
committed
fix: Search::can_match_relative_path() won't report false-negatives anymore.
It does this by ignoring exclude patterns, preferring false-positives that way.
1 parent 4ccf39b commit dbf3be5

File tree

2 files changed

+57
-6
lines changed

2 files changed

+57
-6
lines changed

gix-pathspec/src/search/matching.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,14 @@ impl Search {
176176
matches!(pattern.path.get(common_len), None | Some(&b'/'))
177177
} else {
178178
relative_path.get(common_len) == Some(&b'/')
179-
}
179+
};
180180
}
181181
}
182182
}
183183
if is_match {
184-
return !pattern.is_excluded();
184+
if !pattern.is_excluded() || pattern.always_matches() {
185+
return !pattern.is_excluded();
186+
}
185187
}
186188
}
187189

@@ -191,7 +193,7 @@ impl Search {
191193
/// Returns `true` if `relative_path` matches the prefix of this pathspec.
192194
///
193195
/// For example, the relative path `d` matches `d/`, `d*/`, `d/` and `d/*`, but not `d/d/*` or `dir`.
194-
/// When `leading` is `true`, then `d` matches `d/d` as well. Thus `relative_path` must may be
196+
/// When `leading` is `true`, then `d` matches `d/d` as well. Thus, `relative_path` must may be
195197
/// partially included in `pathspec`, otherwise it has to be fully included.
196198
pub fn directory_matches_prefix(&self, relative_path: &BStr, leading: bool) -> bool {
197199
if self.patterns.is_empty() {
@@ -234,7 +236,9 @@ impl Search {
234236
}
235237
}
236238
if is_match {
237-
return !pattern.is_excluded();
239+
if !pattern.is_excluded() || pattern.always_matches() {
240+
return !pattern.is_excluded();
241+
}
238242
}
239243
}
240244

gix-pathspec/tests/search/mod.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,47 @@ fn no_pathspecs_match_everything() -> crate::Result {
135135
Ok(())
136136
}
137137

138+
#[test]
139+
fn included_directory_and_excluded_subdir_top_level_with_prefix() -> crate::Result {
140+
let mut search = gix_pathspec::Search::from_specs(pathspecs(&[":/foo", ":!/foo/target/"]), None, Path::new("foo"))?;
141+
let m = search
142+
.pattern_matching_relative_path("foo".into(), Some(true), &mut no_attrs)
143+
.expect("matches");
144+
assert_eq!(m.kind, Verbatim);
145+
146+
let m = search
147+
.pattern_matching_relative_path("foo/bar".into(), Some(false), &mut no_attrs)
148+
.expect("matches");
149+
assert_eq!(m.kind, Prefix);
150+
151+
let m = search
152+
.pattern_matching_relative_path("foo/target".into(), Some(false), &mut no_attrs)
153+
.expect("matches");
154+
assert_eq!(m.kind, Prefix, "files named `target` are allowed");
155+
156+
let m = search
157+
.pattern_matching_relative_path("foo/target".into(), Some(true), &mut no_attrs)
158+
.expect("matches");
159+
assert!(m.is_excluded(), "directories named `target` are excluded");
160+
assert_eq!(m.kind, Verbatim);
161+
162+
let m = search
163+
.pattern_matching_relative_path("foo/target/file".into(), Some(false), &mut no_attrs)
164+
.expect("matches");
165+
assert!(m.is_excluded(), "everything below `target/` is also excluded");
166+
assert_eq!(m.kind, Prefix);
167+
168+
assert!(search.directory_matches_prefix("foo/bar".into(), false));
169+
assert!(search.directory_matches_prefix("foo/bar".into(), true));
170+
assert!(search.directory_matches_prefix("foo".into(), false));
171+
assert!(search.directory_matches_prefix("foo".into(), true));
172+
assert!(search.can_match_relative_path("foo".into(), Some(true)));
173+
assert!(search.can_match_relative_path("foo".into(), Some(false)));
174+
assert!(search.can_match_relative_path("foo/hi".into(), Some(true)));
175+
assert!(search.can_match_relative_path("foo/hi".into(), Some(false)));
176+
Ok(())
177+
}
178+
138179
#[test]
139180
fn starts_with() -> crate::Result {
140181
let mut search = gix_pathspec::Search::from_specs(pathspecs(&["a/*"]), None, Path::new(""))?;
@@ -261,8 +302,14 @@ fn simplified_search_respects_all_excluded() -> crate::Result {
261302
None,
262303
Path::new(""),
263304
)?;
264-
assert!(!search.can_match_relative_path("b".into(), None));
265-
assert!(!search.can_match_relative_path("a".into(), None));
305+
assert!(
306+
search.can_match_relative_path("b".into(), None),
307+
"non-trivial excludes are ignored in favor of false-positives"
308+
);
309+
assert!(
310+
search.can_match_relative_path("a".into(), None),
311+
"non-trivial excludes are ignored in favor of false-positives"
312+
);
266313
assert!(search.can_match_relative_path("c".into(), None));
267314
assert!(search.can_match_relative_path("c/".into(), None));
268315

0 commit comments

Comments
 (0)