exclude whole directories when a @source not glob matches them
`@source not "./src/ba*"` is normalized to base `src` + pattern `/ba*` (the wildcard can't be hoisted into the base), but the pattern-walker's not-directive check only glob-matched the file path itself: `/ba*` never matches `/bar/index.html` since `*` doesn't cross `/`, and the directory pruning only recognized the normalized `/**/*` shape. So wildcard directory exclusions were silently ignored by pattern sources. Follow gitignore semantics instead: a pattern that matches a directory excludes the whole subtree. `NotRule::matches` now tests the path itself and every ancestor directory up to the directive's base, and replaces both the file-level check and the directory pruning check (the `/**/*` sentinel special case falls out naturally). The auto/external walker already behaved correctly because on-disk gitignore semantics apply there natively.
This commit is contained in:
parent
fb43708997
commit
d78a138ca3
1 changed files with 18 additions and 13 deletions
|
|
@ -920,19 +920,24 @@ struct NotRule {
|
|||
}
|
||||
|
||||
impl NotRule {
|
||||
/// Whether this directive excludes the given file.
|
||||
fn matches_file(&self, path: &Path) -> bool {
|
||||
/// Whether this directive excludes the given path: a file, or a directory and thereby
|
||||
/// everything inside of it.
|
||||
///
|
||||
/// Like a gitignore rule, the pattern excludes a whole subtree when it matches a
|
||||
/// directory, so besides the path itself every ancestor directory (up to the directive's
|
||||
/// base) is tested as well. E.g. `@source not "./src/ba*"` excludes `src/bar/index.html`
|
||||
/// because `/ba*` matches the `src/bar` directory. Note that directory-shaped directives
|
||||
/// (`@source not "./some/dir"`) are normalized to a `/**/*` pattern with the directory as
|
||||
/// its base, which matches everything inside the directory directly.
|
||||
fn matches(&self, path: &Path) -> bool {
|
||||
let Ok(remainder) = path.strip_prefix(&self.base) else {
|
||||
return false;
|
||||
};
|
||||
glob_match(&self.pattern, rooted_posix(remainder).as_bytes())
|
||||
}
|
||||
|
||||
/// Whether this directive excludes the entire directory (and everything inside).
|
||||
fn covers_dir(&self, path: &Path) -> bool {
|
||||
// Directory-shaped `@source not "./some/dir"` directives are normalized to a `/**/*`
|
||||
// pattern with the directory as its base.
|
||||
self.pattern == "/**/*" && path.starts_with(&self.base)
|
||||
remainder.ancestors().any(|prefix| {
|
||||
!prefix.as_os_str().is_empty()
|
||||
&& glob_match(&self.pattern, rooted_posix(prefix).as_bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1096,9 +1101,9 @@ fn create_pattern_walkers(sources: &Sources) -> Vec<WalkBuilder> {
|
|||
// …and no later `@source not` directive excludes the directory for all of
|
||||
// those patterns. When a pattern comes after the `not`, keep walking; the
|
||||
// file-level check below resolves the conflict exactly.
|
||||
return !filter_nots.iter().any(|not| {
|
||||
not.covers_dir(path) && relevant.iter().all(|p| p.idx < not.idx)
|
||||
});
|
||||
return !filter_nots
|
||||
.iter()
|
||||
.any(|not| not.matches(path) && relevant.iter().all(|p| p.idx < not.idx));
|
||||
}
|
||||
|
||||
let rel = rooted_posix(remainder);
|
||||
|
|
@ -1106,7 +1111,7 @@ fn create_pattern_walkers(sources: &Sources) -> Vec<WalkBuilder> {
|
|||
glob_match(&p.pattern, rel.as_bytes())
|
||||
&& !filter_nots
|
||||
.iter()
|
||||
.any(|not| not.idx > p.idx && not.matches_file(path))
|
||||
.any(|not| not.idx > p.idx && not.matches(path))
|
||||
&& (p.pins_extension || !is_ignored_by_default_file_rules(path))
|
||||
})
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue