Use proper ignore API so path globs match correctly

This notably fixes the v1 "confusing" behaviour when matching folders,
where the expectation is for any of:

folder
folder/
/folder

to match the folder and all paths below it, but v1 would only do this
when *both* of these were added:

**/folder
**/folder/**

Which is very verbose and has caught literally everyone who's ever tried
to do this kinda thing.

The old behaviour is preserved in the globset filterer, for
compatibility, as there are other small behavioural changes that this
affects, even though the new behaviour in the tagged filterer is
arguably the most correct and the old is a bug.
This commit is contained in:
Félix Saparelli 2021-12-01 01:41:07 +13:00
parent 1ff3cbf455
commit 2d633d9177
No known key found for this signature in database
GPG Key ID: B948C4BAE44FC474
2 changed files with 71 additions and 131 deletions

View File

@ -137,7 +137,11 @@ impl TaggedFilterer {
let gc = self.glob_compiled.borrow();
if let Some(igs) = gc.as_ref() {
trace!("checking against compiled Glob filters");
match igs.matched(path, is_dir) {
match if path.strip_prefix(&self.origin).is_ok() {
igs.matched_path_or_any_parents(path, is_dir)
} else {
igs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (fail)");
tag_match = false;
@ -155,7 +159,11 @@ impl TaggedFilterer {
let ngc = self.not_glob_compiled.borrow();
if let Some(ngs) = ngc.as_ref() {
trace!("checking against compiled NotGlob filters");
match ngs.matched(path, is_dir) {
match if path.strip_prefix(&self.origin).is_ok() {
ngs.matched_path_or_any_parents(path, is_dir)
} else {
ngs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (pass)");
tag_match = true;

View File

@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{path::PathBuf, sync::Arc};
use watchexec::{
error::RuntimeError,
@ -12,22 +12,22 @@ use watchexec::{
trait Harness {
fn check_path(
&self,
path: &str,
path: PathBuf,
file_type: Option<FileType>,
) -> std::result::Result<bool, RuntimeError>;
fn path_pass(&self, path: &str, file_type: Option<FileType>, pass: bool) {
let path = if let Some(suf) = path.strip_prefix("/test/") {
let origin = dunce::canonicalize(".").unwrap();
origin.join(suf).to_string_lossy().to_string()
let full_path = if let Some(suf) = path.strip_prefix("/test/") {
origin.join(suf)
} else {
path.to_string()
origin.join(path)
};
tracing::info!(?path, ?file_type, ?pass, "check");
assert_eq!(
self.check_path(&path, file_type).unwrap(),
self.check_path(full_path, file_type).unwrap(),
pass,
"{} {:?} (expected {})",
match file_type {
@ -70,14 +70,11 @@ trait Harness {
impl Harness for TaggedFilterer {
fn check_path(
&self,
path: &str,
path: PathBuf,
file_type: Option<FileType>,
) -> std::result::Result<bool, RuntimeError> {
let event = Event {
tags: vec![Tag::Path {
path: path.into(),
file_type,
}],
tags: vec![Tag::Path { path, file_type }],
metadata: Default::default(),
};
@ -113,6 +110,18 @@ fn not_filter(pat: &str) -> Filter {
}
}
trait FilterExt {
fn in_path(self) -> Self;
}
impl FilterExt for Filter {
fn in_path(mut self) -> Self {
let origin = dunce::canonicalize(".").unwrap();
self.in_path = Some(origin);
self
}
}
#[tokio::test]
async fn empty_filter_passes_everything() {
let filterer = filt(&[]).await;
@ -424,152 +433,75 @@ async fn ignores_take_precedence() {
// The following tests check that the "buggy"/"confusing" watchexec v1 behaviour
// is no longer present.
fn watchexec_v1_confusing_suite(filterer: Arc<TaggedFilterer>) {
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
}
#[tokio::test]
#[ignore]
async fn ignore_folder_with_bare_match() {
let filterer = filt(&[not_filter("prunes")]).await;
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
let filterer = filt(&[not_filter("prunes").in_path()]).await;
filterer.file_doesnt_pass("prunes");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
filterer.dir_doesnt_pass("prunes");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
watchexec_v1_confusing_suite(filterer);
}
#[tokio::test]
#[ignore]
async fn ignore_folder_with_bare_and_leading_slash() {
let filterer = filt(&[not_filter("/prunes")]).await;
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
let filterer = filt(&[not_filter("/prunes").in_path()]).await;
filterer.file_doesnt_pass("prunes");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
filterer.dir_doesnt_pass("prunes");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
watchexec_v1_confusing_suite(filterer);
}
#[tokio::test]
#[ignore]
async fn ignore_folder_with_bare_and_trailing_slash() {
let filterer = filt(&[not_filter("prunes/")]).await;
let filterer = filt(&[not_filter("prunes/").in_path()]).await;
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
filterer.file_does_pass("prunes");
filterer.dir_doesnt_pass("prunes");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
watchexec_v1_confusing_suite(filterer);
}
#[tokio::test]
#[ignore]
async fn ignore_folder_with_only_double_double_glob() {
let filterer = filt(&[not_filter("**/prunes/**")]).await;
let filterer = filt(&[not_filter("**/prunes/**").in_path()]).await;
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
filterer.dir_doesnt_pass("prunes");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("prunes");
filterer.dir_does_pass("prunes");
watchexec_v1_confusing_suite(filterer);
}
#[tokio::test]
#[ignore]
async fn ignore_folder_with_double_and_double_double_globs() {
let filterer = filt(&[not_filter("**/prunes"), not_filter("**/prunes/**")]).await;
filterer.file_does_pass("apples");
filterer.file_does_pass("apples/carrots/cauliflowers/oranges");
filterer.file_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("apples/oranges/bananas");
filterer.dir_does_pass("apples");
filterer.dir_does_pass("apples/carrots/cauliflowers/oranges");
filterer.dir_does_pass("apples/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes");
filterer.dir_does_pass("raw-prunes");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.file_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_does_pass("raw-prunes/oranges/bananas");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/oranges");
filterer.dir_does_pass("raw-prunes/carrots/cauliflowers/artichokes/oranges");
let filterer = filt(&[
not_filter("**/prunes").in_path(),
not_filter("**/prunes/**").in_path(),
])
.await;
filterer.file_doesnt_pass("prunes");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.file_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
filterer.file_doesnt_pass("prunes/oranges/bananas");
filterer.dir_doesnt_pass("prunes");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/oranges");
filterer.dir_doesnt_pass("prunes/carrots/cauliflowers/artichokes/oranges");
watchexec_v1_confusing_suite(filterer);
}