mirror of
https://github.com/watchexec/watchexec.git
synced 2024-11-14 08:11:11 +01:00
Find and load all ignores for watchexec cli
This commit is contained in:
parent
87b6729ab7
commit
ae6af17aea
5 changed files with 140 additions and 32 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2690,11 +2690,13 @@ dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"clap",
|
"clap",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
"dunce",
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"insta",
|
"insta",
|
||||||
"miette",
|
"miette",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"watchexec",
|
"watchexec",
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,12 +20,14 @@ name = "watchexec"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
watchexec = { path = "../lib" }
|
|
||||||
miette = { version = "3.2.0", features = ["fancy"] }
|
|
||||||
console-subscriber = { git = "https://github.com/tokio-rs/console", optional = true }
|
console-subscriber = { git = "https://github.com/tokio-rs/console", optional = true }
|
||||||
|
dunce = "1.0.2"
|
||||||
|
miette = { version = "3.2.0", features = ["fancy"] }
|
||||||
notify-rust = "4.5.2"
|
notify-rust = "4.5.2"
|
||||||
tokio = { version = "1.10.0", features = ["full"] }
|
tokio = { version = "1.10.0", features = ["full"] }
|
||||||
|
tracing = "0.1.26"
|
||||||
tracing-subscriber = "0.2.24"
|
tracing-subscriber = "0.2.24"
|
||||||
|
watchexec = { path = "../lib" }
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "2.33.3"
|
version = "2.33.3"
|
||||||
|
|
|
@ -41,11 +41,16 @@ fn runtime(args: &ArgMatches<'static>) -> Result<(RuntimeConfig, Arc<TaggedFilte
|
||||||
});
|
});
|
||||||
|
|
||||||
config.action_throttle(Duration::from_millis(
|
config.action_throttle(Duration::from_millis(
|
||||||
args.value_of("debounce").unwrap_or("100").parse().into_diagnostic()?,
|
args.value_of("debounce")
|
||||||
|
.unwrap_or("100")
|
||||||
|
.parse()
|
||||||
|
.into_diagnostic()?,
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(interval) = args.value_of("poll") {
|
if let Some(interval) = args.value_of("poll") {
|
||||||
config.file_watcher(Watcher::Poll(Duration::from_millis(interval.parse().into_diagnostic()?)));
|
config.file_watcher(Watcher::Poll(Duration::from_millis(
|
||||||
|
interval.parse().into_diagnostic()?,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
config.command_shell(if args.is_present("no-shell") {
|
config.command_shell(if args.is_present("no-shell") {
|
||||||
|
@ -81,7 +86,8 @@ fn runtime(args: &ArgMatches<'static>) -> Result<(RuntimeConfig, Arc<TaggedFilte
|
||||||
let mut signal = args
|
let mut signal = args
|
||||||
.value_of("signal")
|
.value_of("signal")
|
||||||
.map(|s| Signal::from_str(s))
|
.map(|s| Signal::from_str(s))
|
||||||
.transpose().into_diagnostic()?
|
.transpose()
|
||||||
|
.into_diagnostic()?
|
||||||
.unwrap_or(Signal::SIGTERM);
|
.unwrap_or(Signal::SIGTERM);
|
||||||
|
|
||||||
if args.is_present("kill") {
|
if args.is_present("kill") {
|
||||||
|
|
122
cli/src/main.rs
122
cli/src/main.rs
|
@ -1,7 +1,15 @@
|
||||||
use std::{env::var};
|
use std::{collections::HashSet, env::var, path::PathBuf};
|
||||||
|
|
||||||
|
use dunce::canonicalize;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use watchexec::{Watchexec, event::Event, filter::tagged::{Filter, Matcher, Op, TaggedFilterer}};
|
use tracing::{debug, warn};
|
||||||
|
use watchexec::{
|
||||||
|
event::Event,
|
||||||
|
filter::tagged::{Filter, Matcher, Op, Pattern, Regex},
|
||||||
|
ignore_files::{self, IgnoreFile},
|
||||||
|
project::{self, ProjectType},
|
||||||
|
Watchexec,
|
||||||
|
};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
mod config;
|
mod config;
|
||||||
|
@ -17,16 +25,93 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let args = args::get_args()?;
|
let args = args::get_args()?;
|
||||||
|
|
||||||
if args.is_present("verbose") {
|
tracing_subscriber::fmt()
|
||||||
tracing_subscriber::fmt()
|
.with_env_filter(match args.occurrences_of("verbose") {
|
||||||
.with_env_filter(match args.occurrences_of("verbose") {
|
0 => "watchexec-cli=warn",
|
||||||
0 => unreachable!(),
|
1 => "watchexec=debug,watchexec-cli=debug",
|
||||||
1 => "watchexec=debug",
|
2 => "watchexec=trace,watchexec-cli=trace",
|
||||||
2 => "watchexec=trace",
|
_ => "trace",
|
||||||
_ => "trace",
|
})
|
||||||
|
.try_init()
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
let mut origins = HashSet::new();
|
||||||
|
for path in args.values_of("paths").unwrap_or_default().into_iter() {
|
||||||
|
let path = canonicalize(path).into_diagnostic()?;
|
||||||
|
origins.extend(project::origins(&path).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(?origins, "resolved all project origins");
|
||||||
|
|
||||||
|
let project_origin = project::common_prefix(&origins).unwrap_or_else(|| PathBuf::from("."));
|
||||||
|
debug!(?project_origin, "resolved common/project origin");
|
||||||
|
|
||||||
|
let vcs_types = project::types(&project_origin)
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter(|pt| pt.is_vcs())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
debug!(?vcs_types, "resolved vcs types");
|
||||||
|
|
||||||
|
let (mut ignores, _errors) = ignore_files::from_origin(&project_origin).await;
|
||||||
|
// TODO: handle errors
|
||||||
|
debug!(?ignores, "discovered ignore files from project origin");
|
||||||
|
|
||||||
|
let mut skip_git_global_excludes = false;
|
||||||
|
if !vcs_types.is_empty() {
|
||||||
|
ignores = ignores
|
||||||
|
.into_iter()
|
||||||
|
.filter(|ig| match ig.applies_to {
|
||||||
|
Some(pt) if pt.is_vcs() => vcs_types.contains(&pt),
|
||||||
|
_ => true,
|
||||||
})
|
})
|
||||||
.try_init()
|
.inspect(|ig| {
|
||||||
.ok();
|
if let IgnoreFile {
|
||||||
|
applies_to: Some(ProjectType::Git),
|
||||||
|
applies_in: None,
|
||||||
|
..
|
||||||
|
} = ig
|
||||||
|
{
|
||||||
|
warn!("project git config overrides the global excludes");
|
||||||
|
skip_git_global_excludes = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
debug!(?ignores, "filtered ignores to only those for project vcs");
|
||||||
|
// TODO: use drain_ignore when that stabilises
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut global_ignores, _errors) = ignore_files::from_environment().await;
|
||||||
|
// TODO: handle errors
|
||||||
|
debug!(?global_ignores, "discovered ignore files from environment");
|
||||||
|
|
||||||
|
if skip_git_global_excludes {
|
||||||
|
global_ignores = global_ignores
|
||||||
|
.into_iter()
|
||||||
|
.filter(|gig| {
|
||||||
|
!matches!(
|
||||||
|
gig,
|
||||||
|
IgnoreFile {
|
||||||
|
applies_to: Some(ProjectType::Git),
|
||||||
|
applies_in: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
debug!(
|
||||||
|
?global_ignores,
|
||||||
|
"filtered global ignores to exclude global git ignores"
|
||||||
|
);
|
||||||
|
// TODO: use drain_ignore when that stabilises
|
||||||
|
}
|
||||||
|
|
||||||
|
if !vcs_types.is_empty() {
|
||||||
|
ignores.extend(global_ignores.into_iter().filter(|ig| match ig.applies_to {
|
||||||
|
Some(pt) if pt.is_vcs() => vcs_types.contains(&pt),
|
||||||
|
_ => true,
|
||||||
|
}));
|
||||||
|
debug!(?ignores, "combined and applied final filter over ignores");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut filters = Vec::new();
|
let mut filters = Vec::new();
|
||||||
|
@ -36,12 +121,17 @@ async fn main() -> Result<()> {
|
||||||
filters.push(filter.parse()?);
|
filters.push(filter.parse()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for ext in args.values_of("extensions").unwrap_or_default().map(|s| s.split(',').map(|s| s.trim())).flatten() {
|
for ext in args
|
||||||
|
.values_of("extensions")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.map(|s| s.split(',').map(|s| s.trim()))
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
filters.push(Filter {
|
filters.push(Filter {
|
||||||
in_path: None,
|
in_path: None,
|
||||||
on: Matcher::Path,
|
on: Matcher::Path,
|
||||||
op: Op::Glob,
|
op: Op::Regex,
|
||||||
pat: TaggedFilterer::glob(&format!("**/*.{}", ext))?,
|
pat: Pattern::Regex(Regex::new(&format!("[.]{}$", ext)).into_diagnostic()?),
|
||||||
negate: false,
|
negate: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -49,6 +139,10 @@ async fn main() -> Result<()> {
|
||||||
let (init, runtime, filterer) = config::new(&args)?;
|
let (init, runtime, filterer) = config::new(&args)?;
|
||||||
filterer.add_filters(&filters).await?;
|
filterer.add_filters(&filters).await?;
|
||||||
|
|
||||||
|
for ignore in &ignores {
|
||||||
|
filterer.add_ignore_file(ignore).await?;
|
||||||
|
}
|
||||||
|
|
||||||
let wx = Watchexec::new(init, runtime)?;
|
let wx = Watchexec::new(init, runtime)?;
|
||||||
|
|
||||||
if !args.is_present("postpone") {
|
if !args.is_present("postpone") {
|
||||||
|
|
|
@ -74,8 +74,8 @@ impl ProjectType {
|
||||||
/// present and indicative of the root or origin path of a project. It's entirely possible to have
|
/// present and indicative of the root or origin path of a project. It's entirely possible to have
|
||||||
/// multiple such origins show up: for example, a member of a Cargo workspace will list both the
|
/// multiple such origins show up: for example, a member of a Cargo workspace will list both the
|
||||||
/// member project and the workspace root as origins.
|
/// member project and the workspace root as origins.
|
||||||
pub async fn origins(path: impl AsRef<Path>) -> Vec<PathBuf> {
|
pub async fn origins(path: impl AsRef<Path>) -> HashSet<PathBuf> {
|
||||||
let mut origins = Vec::new();
|
let mut origins = HashSet::new();
|
||||||
|
|
||||||
fn check_list(list: DirList) -> bool {
|
fn check_list(list: DirList) -> bool {
|
||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
|
@ -134,13 +134,13 @@ pub async fn origins(path: impl AsRef<Path>) -> Vec<PathBuf> {
|
||||||
|
|
||||||
let mut current = path.as_ref();
|
let mut current = path.as_ref();
|
||||||
if check_list(DirList::obtain(current).await) {
|
if check_list(DirList::obtain(current).await) {
|
||||||
origins.push(current.to_owned());
|
origins.insert(current.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(parent) = current.parent() {
|
while let Some(parent) = current.parent() {
|
||||||
current = parent;
|
current = parent;
|
||||||
if check_list(DirList::obtain(current).await) {
|
if check_list(DirList::obtain(current).await) {
|
||||||
origins.push(current.to_owned());
|
origins.insert(current.to_owned());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,18 +195,22 @@ pub async fn types(path: impl AsRef<Path>) -> HashSet<ProjectType> {
|
||||||
/// This is a utility function which is useful for finding the common root of a set of origins.
|
/// This is a utility function which is useful for finding the common root of a set of origins.
|
||||||
///
|
///
|
||||||
/// Returns `None` if zero paths are given or paths share no common prefix.
|
/// Returns `None` if zero paths are given or paths share no common prefix.
|
||||||
pub fn common_prefix(paths: &[PathBuf]) -> Option<PathBuf> {
|
pub fn common_prefix<I, P>(paths: I) -> Option<PathBuf>
|
||||||
match paths.len() {
|
where
|
||||||
0 => return None,
|
I: IntoIterator<Item = P>,
|
||||||
1 => return Some(paths[0].to_owned()),
|
P: AsRef<Path>,
|
||||||
_ => {}
|
{
|
||||||
|
let mut paths = paths.into_iter();
|
||||||
|
let first_path = paths.next().map(|p| p.as_ref().to_owned());
|
||||||
|
let mut longest_path = if let Some(ref p) = first_path {
|
||||||
|
p.components().collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut longest_path: Vec<_> = paths[0].components().collect();
|
for path in paths {
|
||||||
|
|
||||||
for path in &paths[1..] {
|
|
||||||
let mut greatest_distance = 0;
|
let mut greatest_distance = 0;
|
||||||
for component_pair in path.components().zip(longest_path.iter()) {
|
for component_pair in path.as_ref().components().zip(longest_path.iter()) {
|
||||||
if component_pair.0 != *component_pair.1 {
|
if component_pair.0 != *component_pair.1 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue