2021-09-23 11:59:35 +02:00
|
|
|
use std::path::PathBuf;
|
2021-09-28 11:21:13 +02:00
|
|
|
use std::sync::Arc;
|
2023-01-06 14:53:49 +01:00
|
|
|
use std::{collections::HashMap, convert::Into};
|
2021-09-23 11:59:35 +02:00
|
|
|
|
2023-01-06 14:53:49 +01:00
|
|
|
use futures::{stream::FuturesOrdered, TryStreamExt};
|
2022-06-15 05:25:05 +02:00
|
|
|
use ignore::{
|
|
|
|
gitignore::{Gitignore, GitignoreBuilder},
|
|
|
|
Match,
|
|
|
|
};
|
|
|
|
use ignore_files::{IgnoreFile, IgnoreFilter};
|
2023-01-06 14:53:49 +01:00
|
|
|
use tokio::fs::canonicalize;
|
2022-06-15 05:25:05 +02:00
|
|
|
use tracing::{debug, trace, trace_span};
|
|
|
|
use watchexec::{
|
|
|
|
error::RuntimeError,
|
|
|
|
event::{Event, FileType, Priority, ProcessEnd, Tag},
|
|
|
|
filter::Filterer,
|
|
|
|
};
|
|
|
|
use watchexec_filterer_ignore::IgnoreFilterer;
|
2023-03-18 09:32:24 +01:00
|
|
|
use watchexec_signals::Signal;
|
2022-06-15 05:25:05 +02:00
|
|
|
|
|
|
|
use crate::{swaplock::SwapLock, Filter, Matcher, Op, Pattern, TaggedFiltererError};
|
|
|
|
|
|
|
|
/// A complex filterer that can match any event tag and supports different matching operators.
|
2021-10-16 12:14:57 +02:00
|
|
|
///
|
2022-06-15 05:25:05 +02:00
|
|
|
/// See the crate-level documentation for more information.
|
2021-09-29 17:03:46 +02:00
|
|
|
#[derive(Debug)]
|
2021-09-23 11:59:35 +02:00
|
|
|
pub struct TaggedFilterer {
|
2021-10-09 07:45:32 +02:00
|
|
|
/// The directory the project is in, its origin.
|
2021-09-27 13:54:33 +02:00
|
|
|
///
|
|
|
|
/// This is used to resolve absolute paths without an `in_path` context.
|
2021-10-09 07:45:32 +02:00
|
|
|
origin: PathBuf,
|
2021-09-27 13:54:33 +02:00
|
|
|
|
|
|
|
/// Where the program is running from.
|
|
|
|
///
|
|
|
|
/// This is used to resolve relative paths without an `in_path` context.
|
2021-09-28 11:21:13 +02:00
|
|
|
workdir: PathBuf,
|
2021-09-27 13:54:33 +02:00
|
|
|
|
|
|
|
/// All filters that are applied, in order, by matcher.
|
2022-01-28 11:32:54 +01:00
|
|
|
filters: SwapLock<HashMap<Matcher, Vec<Filter>>>,
|
2021-10-12 14:49:38 +02:00
|
|
|
|
2022-01-16 02:51:35 +01:00
|
|
|
/// Sub-filterer for ignore files.
|
2022-01-28 11:32:54 +01:00
|
|
|
ignore_filterer: SwapLock<IgnoreFilterer>,
|
2022-01-16 02:51:35 +01:00
|
|
|
|
2021-10-12 14:49:38 +02:00
|
|
|
/// Compiled matcher for Glob filters.
|
2022-01-28 11:32:54 +01:00
|
|
|
glob_compiled: SwapLock<Option<Gitignore>>,
|
2021-10-12 14:49:38 +02:00
|
|
|
|
|
|
|
/// Compiled matcher for NotGlob filters.
|
2022-01-28 11:32:54 +01:00
|
|
|
not_glob_compiled: SwapLock<Option<Gitignore>>,
|
2021-09-23 11:59:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Filterer for TaggedFilterer {
|
2022-06-11 08:43:11 +02:00
|
|
|
fn check_event(&self, event: &Event, priority: Priority) -> Result<bool, RuntimeError> {
|
2023-01-06 14:53:49 +01:00
|
|
|
self.check(event, priority).map_err(Into::into)
|
2021-10-09 07:41:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TaggedFilterer {
|
2022-06-11 08:43:11 +02:00
|
|
|
fn check(&self, event: &Event, priority: Priority) -> Result<bool, TaggedFiltererError> {
|
2021-10-27 14:01:35 +02:00
|
|
|
let _span = trace_span!("filterer_check").entered();
|
2022-06-11 08:43:11 +02:00
|
|
|
trace!(?event, ?priority, "checking event");
|
|
|
|
|
|
|
|
{
|
|
|
|
trace!("checking priority");
|
|
|
|
if let Some(filters) = self.filters.borrow().get(&Matcher::Priority).cloned() {
|
|
|
|
trace!(filters=%filters.len(), "found some filters for priority");
|
|
|
|
//
|
|
|
|
let mut pri_match = true;
|
|
|
|
for filter in &filters {
|
|
|
|
let _span = trace_span!("checking filter against priority", ?filter).entered();
|
|
|
|
let applies = filter.matches(match priority {
|
|
|
|
Priority::Low => "low",
|
|
|
|
Priority::Normal => "normal",
|
|
|
|
Priority::High => "high",
|
|
|
|
Priority::Urgent => unreachable!("urgent by-passes filtering"),
|
|
|
|
})?;
|
|
|
|
if filter.negate {
|
|
|
|
if applies {
|
|
|
|
trace!(prev=%pri_match, now=%true, "negate filter passes, passing this priority");
|
|
|
|
pri_match = true;
|
|
|
|
break;
|
|
|
|
}
|
2023-01-06 14:53:49 +01:00
|
|
|
|
|
|
|
trace!(prev=%pri_match, now=%pri_match, "negate filter fails, ignoring");
|
2022-06-11 08:43:11 +02:00
|
|
|
} else {
|
|
|
|
trace!(prev=%pri_match, this=%applies, now=%(pri_match&applies), "filter applies to priority");
|
|
|
|
pri_match &= applies;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !pri_match {
|
|
|
|
trace!("priority fails check, failing entire event");
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
trace!("no filters for priority, skipping (pass)");
|
|
|
|
}
|
|
|
|
}
|
2021-10-12 17:06:39 +02:00
|
|
|
|
2022-01-16 02:51:35 +01:00
|
|
|
{
|
|
|
|
trace!("checking internal ignore filterer");
|
|
|
|
let igf = self.ignore_filterer.borrow();
|
2022-06-11 08:43:11 +02:00
|
|
|
if !igf
|
|
|
|
.check_event(event, priority)
|
|
|
|
.expect("IgnoreFilterer never errors")
|
|
|
|
{
|
2022-01-16 02:51:35 +01:00
|
|
|
trace!("internal ignore filterer matched (fail)");
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:21:13 +02:00
|
|
|
if self.filters.borrow().is_empty() {
|
|
|
|
trace!("no filters, skipping entire check (pass)");
|
2021-09-27 13:54:33 +02:00
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:21:13 +02:00
|
|
|
trace!(tags=%event.tags.len(), "checking all tags on the event");
|
2021-09-27 13:54:33 +02:00
|
|
|
for tag in &event.tags {
|
2021-10-27 14:01:35 +02:00
|
|
|
let _span = trace_span!("check_tag", ?tag).entered();
|
|
|
|
|
|
|
|
trace!("checking tag");
|
2021-10-13 14:26:15 +02:00
|
|
|
for matcher in Matcher::from_tag(tag) {
|
2021-10-27 14:01:35 +02:00
|
|
|
let _span = trace_span!("check_matcher", ?matcher).entered();
|
|
|
|
|
2021-10-13 14:26:15 +02:00
|
|
|
let filters = self.filters.borrow().get(matcher).cloned();
|
|
|
|
if let Some(tag_filters) = filters {
|
|
|
|
if tag_filters.is_empty() {
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("no filters for this matcher, skipping (pass)");
|
2021-10-13 14:26:15 +02:00
|
|
|
continue;
|
|
|
|
}
|
2021-10-12 17:06:39 +02:00
|
|
|
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!(filters=%tag_filters.len(), "found some filters for this matcher");
|
2021-10-13 14:26:15 +02:00
|
|
|
|
|
|
|
let mut tag_match = true;
|
|
|
|
|
|
|
|
if let (Matcher::Path, Tag::Path { path, file_type }) = (matcher, tag) {
|
2021-10-19 14:18:43 +02:00
|
|
|
let is_dir = file_type.map_or(false, |ft| matches!(ft, FileType::Dir));
|
2021-10-13 14:26:15 +02:00
|
|
|
|
2022-01-16 02:51:11 +01:00
|
|
|
{
|
2022-01-16 02:51:35 +01:00
|
|
|
let gc = self.glob_compiled.borrow();
|
|
|
|
if let Some(igs) = gc.as_ref() {
|
2022-01-16 02:51:11 +01:00
|
|
|
let _span =
|
|
|
|
trace_span!("checking_compiled_filters", compiled=%"Glob")
|
2022-01-16 02:51:35 +01:00
|
|
|
.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) => {
|
2022-01-16 02:51:11 +01:00
|
|
|
if glob
|
|
|
|
.from()
|
|
|
|
.map_or(true, |f| path.strip_prefix(f).is_ok())
|
|
|
|
{
|
2022-01-16 02:51:35 +01:00
|
|
|
trace!(?glob, "positive match (pass)");
|
|
|
|
tag_match &= true;
|
|
|
|
} else {
|
2022-01-16 02:51:11 +01:00
|
|
|
trace!(
|
|
|
|
?glob,
|
|
|
|
"positive match, but not in scope (ignore)"
|
|
|
|
);
|
2022-01-16 02:51:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Match::Whitelist(glob) => {
|
|
|
|
trace!(?glob, "negative match (ignore)");
|
2021-12-02 09:33:48 +01:00
|
|
|
}
|
2021-10-13 14:26:15 +02:00
|
|
|
}
|
2021-10-12 17:06:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 02:51:11 +01:00
|
|
|
{
|
2022-01-16 02:51:35 +01:00
|
|
|
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) => {
|
2022-01-16 02:51:11 +01:00
|
|
|
if glob
|
|
|
|
.from()
|
|
|
|
.map_or(true, |f| path.strip_prefix(f).is_ok())
|
|
|
|
{
|
2022-01-16 02:51:35 +01:00
|
|
|
trace!(?glob, "positive match (fail)");
|
|
|
|
tag_match &= false;
|
|
|
|
} else {
|
2022-01-16 02:51:11 +01:00
|
|
|
trace!(
|
|
|
|
?glob,
|
|
|
|
"positive match, but not in scope (ignore)"
|
|
|
|
);
|
2022-01-16 02:51:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Match::Whitelist(glob) => {
|
|
|
|
trace!(?glob, "negative match (pass)");
|
|
|
|
tag_match = true;
|
2021-12-02 09:33:48 +01:00
|
|
|
}
|
2021-10-13 14:26:15 +02:00
|
|
|
}
|
2021-10-12 17:06:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-13 14:26:15 +02:00
|
|
|
// those are handled with the compiled ignore filters above
|
|
|
|
let tag_filters = tag_filters
|
|
|
|
.into_iter()
|
|
|
|
.filter(|f| {
|
|
|
|
!matches!(
|
|
|
|
(tag, matcher, f),
|
|
|
|
(
|
|
|
|
Tag::Path { .. },
|
|
|
|
Matcher::Path,
|
|
|
|
Filter {
|
|
|
|
on: Matcher::Path,
|
|
|
|
op: Op::Glob | Op::NotGlob,
|
|
|
|
pat: Pattern::Glob(_),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2021-11-27 14:22:07 +01:00
|
|
|
if tag_filters.is_empty() && tag_match {
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("no more filters for this matcher, skipping (pass)");
|
2021-10-13 14:26:15 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!(filters=%tag_filters.len(), "got some filters to check still");
|
2021-10-13 14:26:15 +02:00
|
|
|
|
|
|
|
for filter in &tag_filters {
|
2022-01-15 11:46:06 +01:00
|
|
|
let _span = trace_span!("checking filter against tag", ?filter).entered();
|
2021-10-13 14:26:15 +02:00
|
|
|
if let Some(app) = self.match_tag(filter, tag)? {
|
|
|
|
if filter.negate {
|
|
|
|
if app {
|
2021-10-16 12:14:57 +02:00
|
|
|
trace!(prev=%tag_match, now=%true, "negate filter passes, passing this matcher");
|
2021-10-13 14:26:15 +02:00
|
|
|
tag_match = true;
|
2021-10-16 12:14:57 +02:00
|
|
|
break;
|
2021-10-13 14:26:15 +02:00
|
|
|
}
|
2023-01-06 14:53:49 +01:00
|
|
|
|
|
|
|
trace!(prev=%tag_match, now=%tag_match, "negate filter fails, ignoring");
|
2021-09-28 11:21:13 +02:00
|
|
|
} else {
|
2021-10-13 14:26:15 +02:00
|
|
|
trace!(prev=%tag_match, this=%app, now=%(tag_match&app), "filter applies to this tag");
|
|
|
|
tag_match &= app;
|
2021-09-27 13:54:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-13 14:26:15 +02:00
|
|
|
if !tag_match {
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("matcher fails check, failing entire event");
|
2021-10-13 14:26:15 +02:00
|
|
|
return Ok(false);
|
|
|
|
}
|
2021-09-28 11:21:13 +02:00
|
|
|
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("matcher passes check, continuing");
|
2021-10-13 14:26:15 +02:00
|
|
|
} else {
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("no filters for this matcher, skipping (pass)");
|
2021-10-13 14:26:15 +02:00
|
|
|
}
|
2021-09-27 13:54:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!("passing event");
|
2021-09-27 13:54:33 +02:00
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
|
2021-10-16 12:14:57 +02:00
|
|
|
/// Initialise a new tagged filterer with no filters.
|
|
|
|
///
|
|
|
|
/// This takes two paths: the project origin, and the current directory. The current directory
|
|
|
|
/// is not obtained from the environment so you can customise it; generally you should use
|
|
|
|
/// [`std::env::current_dir()`] though.
|
|
|
|
///
|
|
|
|
/// 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
|
2022-01-16 02:51:35 +01:00
|
|
|
/// from text), and for ignore file based filtering.
|
2021-10-16 12:14:57 +02:00
|
|
|
///
|
|
|
|
/// The workdir is used to resolve relative paths given in filters without an `in_path` field.
|
|
|
|
///
|
|
|
|
/// So, if origin is `/path/to/project` and workdir is `/path/to/project/subtree`:
|
|
|
|
/// - `path=foo.bar` is resolved to `/path/to/project/subtree/foo.bar`
|
|
|
|
/// - `path=/foo.bar` is resolved to `/path/to/project/foo.bar`
|
2023-01-06 14:53:49 +01:00
|
|
|
pub async fn new(origin: PathBuf, workdir: PathBuf) -> Result<Arc<Self>, TaggedFiltererError> {
|
|
|
|
let origin = canonicalize(origin)
|
|
|
|
.await
|
|
|
|
.map_err(|err| TaggedFiltererError::IoError {
|
|
|
|
about: "canonicalise origin on new tagged filterer",
|
|
|
|
err,
|
|
|
|
})?;
|
2021-09-29 17:03:46 +02:00
|
|
|
Ok(Arc::new(Self {
|
2022-01-28 11:32:54 +01:00
|
|
|
filters: SwapLock::new(HashMap::new()),
|
2022-06-15 05:25:05 +02:00
|
|
|
ignore_filterer: SwapLock::new(IgnoreFilterer(IgnoreFilter::empty(&origin))),
|
2022-01-28 11:32:54 +01:00
|
|
|
glob_compiled: SwapLock::new(None),
|
|
|
|
not_glob_compiled: SwapLock::new(None),
|
2023-01-06 14:53:49 +01:00
|
|
|
workdir: canonicalize(workdir)
|
|
|
|
.await
|
|
|
|
.map_err(|err| TaggedFiltererError::IoError {
|
|
|
|
about: "canonicalise workdir on new tagged filterer",
|
|
|
|
err,
|
|
|
|
})?,
|
2022-01-16 02:51:35 +01:00
|
|
|
origin,
|
2021-09-29 17:03:46 +02:00
|
|
|
}))
|
2021-09-28 11:21:51 +02:00
|
|
|
}
|
|
|
|
|
2021-09-29 17:03:46 +02:00
|
|
|
// filter ctx event path filter outcome
|
|
|
|
// /foo/bar /foo/bar/baz.txt baz.txt pass
|
|
|
|
// /foo/bar /foo/bar/baz.txt /baz.txt pass
|
|
|
|
// /foo/bar /foo/bar/baz.txt /baz.* pass
|
|
|
|
// /foo/bar /foo/bar/baz.txt /blah fail
|
|
|
|
// /foo/quz /foo/bar/baz.txt /baz.* skip
|
|
|
|
|
|
|
|
// Ok(Some(bool)) => the match was applied, bool is the result
|
|
|
|
// Ok(None) => for some precondition, the match was not done (mismatched tag, out of context, …)
|
2021-10-12 14:49:11 +02:00
|
|
|
fn match_tag(&self, filter: &Filter, tag: &Tag) -> Result<Option<bool>, TaggedFiltererError> {
|
2021-10-27 14:01:35 +02:00
|
|
|
trace!(matcher=?filter.on, "matching filter to tag");
|
2023-03-18 09:32:24 +01:00
|
|
|
|
|
|
|
fn sig_match(sig: Signal) -> (&'static str, i32) {
|
|
|
|
match sig {
|
|
|
|
Signal::Hangup | Signal::Custom(1) => ("HUP", 1),
|
|
|
|
Signal::ForceStop | Signal::Custom(9) => ("KILL", 9),
|
|
|
|
Signal::Interrupt | Signal::Custom(2) => ("INT", 2),
|
|
|
|
Signal::Quit | Signal::Custom(3) => ("QUIT", 3),
|
|
|
|
Signal::Terminate | Signal::Custom(15) => ("TERM", 15),
|
|
|
|
Signal::User1 | Signal::Custom(10) => ("USR1", 10),
|
|
|
|
Signal::User2 | Signal::Custom(12) => ("USR2", 12),
|
|
|
|
Signal::Custom(n) => ("UNK", n),
|
|
|
|
_ => ("UNK", 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-27 13:54:33 +02:00
|
|
|
match (tag, filter.on) {
|
|
|
|
(tag, Matcher::Tag) => filter.matches(tag.discriminant_name()),
|
2021-10-12 17:06:39 +02:00
|
|
|
(Tag::Path { path, .. }, Matcher::Path) => {
|
2021-09-29 17:03:46 +02:00
|
|
|
let resolved = if let Some(ctx) = &filter.in_path {
|
|
|
|
if let Ok(suffix) = path.strip_prefix(ctx) {
|
|
|
|
suffix.strip_prefix("/").unwrap_or(suffix)
|
|
|
|
} else {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
} else if let Ok(suffix) = path.strip_prefix(&self.workdir) {
|
|
|
|
suffix.strip_prefix("/").unwrap_or(suffix)
|
2021-10-09 07:45:32 +02:00
|
|
|
} else if let Ok(suffix) = path.strip_prefix(&self.origin) {
|
2021-09-29 17:03:46 +02:00
|
|
|
suffix.strip_prefix("/").unwrap_or(suffix)
|
|
|
|
} else {
|
|
|
|
path.strip_prefix("/").unwrap_or(path)
|
|
|
|
};
|
|
|
|
|
|
|
|
trace!(?resolved, "resolved path to match filter against");
|
2021-10-12 14:49:38 +02:00
|
|
|
|
|
|
|
if matches!(filter.op, Op::Glob | Op::NotGlob) {
|
2021-10-13 13:13:48 +02:00
|
|
|
trace!("path glob match with match_tag is already handled");
|
|
|
|
return Ok(None);
|
2021-10-12 14:49:38 +02:00
|
|
|
}
|
2023-01-06 14:53:49 +01:00
|
|
|
|
|
|
|
filter.matches(resolved.to_string_lossy())
|
2021-09-29 17:03:46 +02:00
|
|
|
}
|
2021-10-12 17:06:55 +02:00
|
|
|
(
|
|
|
|
Tag::Path {
|
|
|
|
file_type: Some(ft),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
Matcher::FileType,
|
2021-10-19 14:18:43 +02:00
|
|
|
) => filter.matches(ft.to_string()),
|
2021-09-28 11:21:13 +02:00
|
|
|
(Tag::FileEventKind(kind), Matcher::FileEventKind) => {
|
2023-01-06 14:53:49 +01:00
|
|
|
filter.matches(format!("{kind:?}"))
|
2021-09-28 11:21:13 +02:00
|
|
|
}
|
2021-09-27 13:54:33 +02:00
|
|
|
(Tag::Source(src), Matcher::Source) => filter.matches(src.to_string()),
|
|
|
|
(Tag::Process(pid), Matcher::Process) => filter.matches(pid.to_string()),
|
2021-12-21 04:38:57 +01:00
|
|
|
(Tag::Signal(sig), Matcher::Signal) => {
|
2023-03-18 09:32:24 +01:00
|
|
|
let (text, int) = sig_match(*sig);
|
2021-12-21 04:38:57 +01:00
|
|
|
Ok(filter.matches(text)?
|
2023-01-06 14:53:49 +01:00
|
|
|
|| filter.matches(format!("SIG{text}"))?
|
2021-12-21 04:38:57 +01:00
|
|
|
|| filter.matches(int.to_string())?)
|
|
|
|
}
|
2021-12-21 05:56:14 +01:00
|
|
|
(Tag::ProcessCompletion(ope), Matcher::ProcessCompletion) => match ope {
|
|
|
|
None => filter.matches("_"),
|
|
|
|
Some(ProcessEnd::Success) => filter.matches("success"),
|
2023-01-06 14:53:49 +01:00
|
|
|
Some(ProcessEnd::ExitError(int)) => filter.matches(format!("error({int})")),
|
2021-12-21 06:05:52 +01:00
|
|
|
Some(ProcessEnd::ExitSignal(sig)) => {
|
2023-03-18 09:32:24 +01:00
|
|
|
let (text, int) = sig_match(*sig);
|
2023-01-06 14:53:49 +01:00
|
|
|
Ok(filter.matches(format!("signal({text})"))?
|
|
|
|
|| filter.matches(format!("signal(SIG{text})"))?
|
|
|
|
|| filter.matches(format!("signal({int})"))?)
|
2021-12-21 06:05:52 +01:00
|
|
|
}
|
2023-01-06 14:53:49 +01:00
|
|
|
Some(ProcessEnd::ExitStop(int)) => filter.matches(format!("stop({int})")),
|
|
|
|
Some(ProcessEnd::Exception(int)) => filter.matches(format!("exception({int:X})")),
|
2021-12-21 05:56:14 +01:00
|
|
|
Some(ProcessEnd::Continued) => filter.matches("continued"),
|
|
|
|
},
|
2021-10-27 14:01:35 +02:00
|
|
|
(_, _) => {
|
|
|
|
trace!("no match for tag, skipping");
|
2021-09-28 11:21:13 +02:00
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.map(Some)
|
|
|
|
}
|
|
|
|
|
2021-10-16 12:14:57 +02:00
|
|
|
/// Add some filters to the filterer.
|
|
|
|
///
|
|
|
|
/// This is async as it submits the new filters to the live filterer, which may be holding a
|
|
|
|
/// read lock. It takes a slice of filters so it can efficiently add a large number of filters
|
|
|
|
/// 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
|
2022-01-16 02:51:35 +01:00
|
|
|
/// 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.
|
2021-10-12 14:49:11 +02:00
|
|
|
pub async fn add_filters(&self, filters: &[Filter]) -> Result<(), TaggedFiltererError> {
|
2021-10-12 13:48:42 +02:00
|
|
|
debug!(?filters, "adding filters to filterer");
|
2021-09-29 17:03:46 +02:00
|
|
|
|
2021-10-12 13:48:42 +02:00
|
|
|
let mut recompile_globs = false;
|
|
|
|
let mut recompile_not_globs = false;
|
|
|
|
|
2023-01-06 14:53:49 +01:00
|
|
|
#[allow(clippy::from_iter_instead_of_collect)]
|
|
|
|
let filters = FuturesOrdered::from_iter(
|
|
|
|
filters
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.inspect(|f| match f.op {
|
|
|
|
Op::Glob => {
|
|
|
|
recompile_globs = true;
|
|
|
|
}
|
|
|
|
Op::NotGlob => {
|
|
|
|
recompile_not_globs = true;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
})
|
|
|
|
.map(Filter::canonicalised),
|
|
|
|
)
|
|
|
|
.try_collect::<Vec<_>>()
|
|
|
|
.await?;
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?filters, "canonicalised filters");
|
2021-10-12 13:48:42 +02:00
|
|
|
// TODO: use miette's related and issue canonicalisation errors for all of them
|
2021-09-29 17:03:46 +02:00
|
|
|
|
2021-09-28 11:21:51 +02:00
|
|
|
self.filters
|
2021-10-12 13:48:42 +02:00
|
|
|
.change(|fs| {
|
|
|
|
for filter in filters {
|
|
|
|
fs.entry(filter.on).or_default().push(filter);
|
|
|
|
}
|
2021-09-28 11:21:51 +02:00
|
|
|
})
|
2021-10-12 14:49:38 +02:00
|
|
|
.map_err(|err| TaggedFiltererError::FilterChange { action: "add", err })?;
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!("inserted filters into swaplock");
|
2021-10-12 13:48:42 +02:00
|
|
|
|
|
|
|
if recompile_globs {
|
2023-01-06 14:53:49 +01:00
|
|
|
self.recompile_globs(Op::Glob)?;
|
2021-10-12 13:48:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if recompile_not_globs {
|
2023-01-06 14:53:49 +01:00
|
|
|
self.recompile_globs(Op::NotGlob)?;
|
2021-10-12 13:48:42 +02:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:21:51 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-06 14:53:49 +01:00
|
|
|
fn recompile_globs(&self, op_filter: Op) -> Result<(), TaggedFiltererError> {
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, "recompiling globs");
|
2021-10-12 14:49:38 +02:00
|
|
|
let target = match op_filter {
|
|
|
|
Op::Glob => &self.glob_compiled,
|
|
|
|
Op::NotGlob => &self.not_glob_compiled,
|
|
|
|
_ => unreachable!("recompile_globs called with invalid op"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let globs = {
|
|
|
|
let filters = self.filters.borrow();
|
|
|
|
if let Some(fs) = filters.get(&Matcher::Path) {
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, "pulling filters from swaplock");
|
2021-10-12 14:49:38 +02:00
|
|
|
// we want to hold the lock as little as possible, so we clone the filters
|
|
|
|
fs.iter()
|
2023-08-30 05:43:57 +02:00
|
|
|
.filter(|&f| f.op == op_filter)
|
2021-10-12 14:49:38 +02:00
|
|
|
.cloned()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
} else {
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, "no filters, erasing compiled glob");
|
2021-10-12 14:49:38 +02:00
|
|
|
return target
|
|
|
|
.replace(None)
|
|
|
|
.map_err(TaggedFiltererError::GlobsetChange);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut builder = GitignoreBuilder::new(&self.origin);
|
|
|
|
for filter in globs {
|
2021-12-14 16:57:17 +01:00
|
|
|
if let Pattern::Glob(mut glob) = filter.pat {
|
|
|
|
if filter.negate {
|
|
|
|
glob.insert(0, '!');
|
|
|
|
}
|
|
|
|
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, in_path=?filter.in_path, ?glob, "adding new glob line");
|
2021-10-12 14:49:38 +02:00
|
|
|
builder
|
|
|
|
.add_line(filter.in_path, &glob)
|
|
|
|
.map_err(TaggedFiltererError::GlobParse)?;
|
|
|
|
}
|
|
|
|
}
|
2021-10-12 13:48:42 +02:00
|
|
|
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, "finalising compiled glob");
|
2021-10-12 14:49:38 +02:00
|
|
|
let compiled = builder.build().map_err(TaggedFiltererError::GlobParse)?;
|
|
|
|
|
2021-11-29 14:11:50 +01:00
|
|
|
trace!(?op_filter, "swapping in new compiled glob");
|
2021-10-12 14:49:38 +02:00
|
|
|
target
|
|
|
|
.replace(Some(compiled))
|
|
|
|
.map_err(TaggedFiltererError::GlobsetChange)
|
2021-10-11 12:34:14 +02:00
|
|
|
}
|
2021-09-29 17:03:46 +02:00
|
|
|
|
2022-01-16 02:51:35 +01:00
|
|
|
/// Reads a gitignore-style [`IgnoreFile`] and adds it to the filterer.
|
2021-10-12 14:49:11 +02:00
|
|
|
pub async fn add_ignore_file(&self, file: &IgnoreFile) -> Result<(), TaggedFiltererError> {
|
2022-01-16 02:51:35 +01:00
|
|
|
let mut new = { self.ignore_filterer.borrow().clone() };
|
2021-10-12 13:48:42 +02:00
|
|
|
|
2022-06-15 05:25:05 +02:00
|
|
|
new.0
|
|
|
|
.add_file(file)
|
2022-01-16 02:51:35 +01:00
|
|
|
.await
|
|
|
|
.map_err(TaggedFiltererError::Ignore)?;
|
|
|
|
self.ignore_filterer
|
|
|
|
.replace(new)
|
|
|
|
.map_err(TaggedFiltererError::IgnoreSwap)?;
|
|
|
|
Ok(())
|
2021-09-28 11:21:51 +02:00
|
|
|
}
|
|
|
|
|
2021-10-16 12:14:57 +02:00
|
|
|
/// Clears all filters from the filterer.
|
|
|
|
///
|
|
|
|
/// This also recompiles the glob matchers, so essentially it resets the entire filterer state.
|
2023-01-06 14:53:49 +01:00
|
|
|
pub fn clear_filters(&self) -> Result<(), TaggedFiltererError> {
|
2021-09-28 11:21:51 +02:00
|
|
|
debug!("removing all filters from filterer");
|
2023-01-06 14:53:49 +01:00
|
|
|
self.filters.replace(Default::default()).map_err(|err| {
|
|
|
|
TaggedFiltererError::FilterChange {
|
2021-09-28 11:21:51 +02:00
|
|
|
action: "clear all",
|
|
|
|
err,
|
2023-01-06 14:53:49 +01:00
|
|
|
}
|
|
|
|
})?;
|
2021-10-16 12:14:57 +02:00
|
|
|
|
2023-01-06 14:53:49 +01:00
|
|
|
self.recompile_globs(Op::Glob)?;
|
|
|
|
self.recompile_globs(Op::NotGlob)?;
|
2021-10-16 12:14:57 +02:00
|
|
|
|
2021-09-28 11:21:51 +02:00
|
|
|
Ok(())
|
2021-09-23 11:59:35 +02:00
|
|
|
}
|
2021-10-09 07:45:32 +02:00
|
|
|
}
|