Integrate IgnoreFilterer into TaggedFilterer

This commit is contained in:
Félix Saparelli 2022-01-16 14:51:35 +13:00
parent 8219e577d0
commit d6dfb87063
No known key found for this signature in database
GPG Key ID: B948C4BAE44FC474
2 changed files with 88 additions and 73 deletions

View File

@ -8,7 +8,6 @@ use dunce::canonicalize;
use globset::Glob;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use ignore::Match;
use tokio::fs::read_to_string;
use tracing::{debug, trace, trace_span, warn};
use unicase::UniCase;
@ -16,7 +15,7 @@ use crate::error::RuntimeError;
use crate::event::{Event, FileType, ProcessEnd, Tag};
use crate::filter::tagged::error::TaggedFiltererError;
use crate::filter::Filterer;
use crate::ignore::IgnoreFile;
use crate::ignore::{IgnoreFile, IgnoreFilterer};
use crate::signal::process::SubSignal;
use crate::signal::source::MainSignal;
@ -116,6 +115,9 @@ pub struct TaggedFilterer {
/// All filters that are applied, in order, by matcher.
filters: swaplock::SwapLock<HashMap<Matcher, Vec<Filter>>>,
/// Sub-filterer for ignore files.
ignore_filterer: swaplock::SwapLock<IgnoreFilterer>,
/// Compiled matcher for Glob filters.
glob_compiled: swaplock::SwapLock<Option<Gitignore>>,
@ -134,6 +136,15 @@ impl TaggedFilterer {
let _span = trace_span!("filterer_check").entered();
trace!(?event, "checking event");
{
trace!("checking internal ignore filterer");
let igf = self.ignore_filterer.borrow();
if !igf.check_event(event).expect("IgnoreFilterer never errors") {
trace!("internal ignore filterer matched (fail)");
return Ok(false);
}
}
if self.filters.borrow().is_empty() {
trace!("no filters, skipping entire check (pass)");
return Ok(true);
@ -154,8 +165,6 @@ impl TaggedFilterer {
continue;
}
// TODO: integrate ignore::Filter
trace!(filters=%tag_filters.len(), "found some filters for this matcher");
let mut tag_match = true;
@ -164,82 +173,82 @@ impl TaggedFilterer {
let is_dir = file_type.map_or(false, |ft| matches!(ft, FileType::Dir));
{
let gc = self.glob_compiled.borrow();
if let Some(igs) = gc.as_ref() {
let gc = self.glob_compiled.borrow();
if let Some(igs) = gc.as_ref() {
let _span =
trace_span!("checking_compiled_filters", compiled=%"Glob")
.entered();
match if path.strip_prefix(&self.origin).is_ok() {
trace!("checking against path or parents");
igs.matched_path_or_any_parents(path, is_dir)
} else {
trace!("checking against path only");
igs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (fail)");
tag_match &= false;
}
Match::Ignore(glob) => {
.entered();
match if path.strip_prefix(&self.origin).is_ok() {
trace!("checking against path or parents");
igs.matched_path_or_any_parents(path, is_dir)
} else {
trace!("checking against path only");
igs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (fail)");
tag_match &= false;
}
Match::Ignore(glob) => {
if glob
.from()
.map_or(true, |f| path.strip_prefix(f).is_ok())
{
trace!(?glob, "positive match (pass)");
tag_match &= true;
} else {
trace!(?glob, "positive match (pass)");
tag_match &= true;
} else {
trace!(
?glob,
"positive match, but not in scope (ignore)"
);
}
}
Match::Whitelist(glob) => {
trace!(?glob, "negative match (ignore)");
}
}
Match::Whitelist(glob) => {
trace!(?glob, "negative match (ignore)");
}
}
}
}
{
let ngc = self.not_glob_compiled.borrow();
if let Some(ngs) = ngc.as_ref() {
let _span =
trace_span!("checking_compiled_filters", compiled=%"NotGlob")
.entered();
match if path.strip_prefix(&self.origin).is_ok() {
trace!("checking against path or parents");
ngs.matched_path_or_any_parents(path, is_dir)
} else {
trace!("checking against path only");
ngs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (pass)");
tag_match &= true;
}
Match::Ignore(glob) => {
let ngc = self.not_glob_compiled.borrow();
if let Some(ngs) = ngc.as_ref() {
let _span =
trace_span!("checking_compiled_filters", compiled=%"NotGlob")
.entered();
match if path.strip_prefix(&self.origin).is_ok() {
trace!("checking against path or parents");
ngs.matched_path_or_any_parents(path, is_dir)
} else {
trace!("checking against path only");
ngs.matched(path, is_dir)
} {
Match::None => {
trace!("no match (pass)");
tag_match &= true;
}
Match::Ignore(glob) => {
if glob
.from()
.map_or(true, |f| path.strip_prefix(f).is_ok())
{
trace!(?glob, "positive match (fail)");
tag_match &= false;
} else {
trace!(?glob, "positive match (fail)");
tag_match &= false;
} else {
trace!(
?glob,
"positive match, but not in scope (ignore)"
);
}
}
Match::Whitelist(glob) => {
trace!(?glob, "negative match (pass)");
tag_match = true;
}
}
Match::Whitelist(glob) => {
trace!(?glob, "negative match (pass)");
tag_match = true;
}
}
}
}
}
// those are handled with the compiled ignore filters above
let tag_filters = tag_filters
@ -309,7 +318,7 @@ impl TaggedFilterer {
///
/// The origin is the directory the main project that is being watched is in. This is used to
/// resolve absolute paths given in filters without an `in_path` field (e.g. all filters parsed
/// from text).
/// from text), and for ignore file based filtering.
///
/// The workdir is used to resolve relative paths given in filters without an `in_path` field.
///
@ -320,12 +329,14 @@ impl TaggedFilterer {
origin: impl Into<PathBuf>,
workdir: impl Into<PathBuf>,
) -> Result<Arc<Self>, TaggedFiltererError> {
let origin = canonicalize(origin.into())?;
Ok(Arc::new(Self {
origin: canonicalize(origin.into())?,
workdir: canonicalize(workdir.into())?,
filters: swaplock::SwapLock::new(HashMap::new()),
ignore_filterer: swaplock::SwapLock::new(IgnoreFilterer::empty(&origin)),
glob_compiled: swaplock::SwapLock::new(None),
not_glob_compiled: swaplock::SwapLock::new(None),
workdir: canonicalize(workdir.into())?,
origin,
}))
}
@ -431,7 +442,8 @@ impl TaggedFilterer {
/// with a single write, without needing to acquire the lock repeatedly.
///
/// If filters with glob operations are added, the filterer's glob matchers are recompiled after
/// the new filters are added, in this method.
/// the new filters are added, in this method. This should not be used for inserting an
/// [`IgnoreFile`]: use [`add_ignore_file()`](Self::add_ignore_file) instead.
pub async fn add_filters(&self, filters: &[Filter]) -> Result<(), TaggedFiltererError> {
debug!(?filters, "adding filters to filterer");
@ -526,26 +538,18 @@ impl TaggedFilterer {
.map_err(TaggedFiltererError::GlobsetChange)
}
/// Reads a gitignore-style [`IgnoreFile`] and adds all of its contents to the filterer.
///
/// Empty lines and lines starting with `#` are ignored. The `applies_in` field of the
/// [`IgnoreFile`] is used for the `in_path` field of each [`Filter`].
///
/// This method reads the entire file into memory.
/// Reads a gitignore-style [`IgnoreFile`] and adds it to the filterer.
pub async fn add_ignore_file(&self, file: &IgnoreFile) -> Result<(), TaggedFiltererError> {
let content = read_to_string(&file.path).await?;
let lines = content.lines();
let mut ignores = Vec::with_capacity(lines.size_hint().0);
let mut new = { self.ignore_filterer.borrow().clone() };
for line in lines {
if line.is_empty() || line.starts_with('#') {
continue;
}
ignores.push(Filter::from_glob_ignore(file.applies_in.clone(), line));
}
self.add_filters(&ignores).await
new.add_file(file)
.await
.map_err(TaggedFiltererError::Ignore)?;
self.ignore_filterer
.replace(new)
.await
.map_err(TaggedFiltererError::IgnoreSwap)?;
Ok(())
}
/// Clears all filters from the filterer.

View File

@ -10,6 +10,7 @@ use tokio::sync::watch::error::SendError;
use crate::{
error::RuntimeError,
filter::tagged::{Filter, Matcher},
ignore::IgnoreFilterer,
};
/// Errors emitted by the TaggedFilterer.
@ -55,6 +56,16 @@ pub enum TaggedFiltererError {
#[error("cannot change compiled globset: {0:?}")]
#[diagnostic(code(watchexec::filter::tagged::globset_change))]
GlobsetChange(#[source] SendError<Option<Gitignore>>),
/// Error received about the internal ignore filterer.
#[error("ignore filterer: {0}")]
#[diagnostic(code(watchexec::filter::tagged::ignore))]
Ignore(#[source] RuntimeError),
/// Error received when a new ignore filterer cannot be swapped in.
#[error("cannot swap in new ignore filterer: {0:?}")]
#[diagnostic(code(watchexec::filter::tagged::ignore_swap))]
IgnoreSwap(#[source] SendError<IgnoreFilterer>),
}
impl From<TaggedFiltererError> for RuntimeError {