From 524812028d165887f8491e2fd4f4f15d3015e242 Mon Sep 17 00:00:00 2001 From: Quint Guvernator Date: Tue, 23 Jun 2020 13:43:55 +0200 Subject: [PATCH 1/5] add --no-environment switch to fix #78 --- completions/zsh | 1 + doc/watchexec.1.html | 1 + doc/watchexec.1.ronn | 3 +++ src/cli.rs | 7 +++++++ src/process.rs | 22 ++++++++++++++++------ src/run.rs | 2 +- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/completions/zsh b/completions/zsh index cdf8328..2eb13da 100644 --- a/completions/zsh +++ b/completions/zsh @@ -13,6 +13,7 @@ args=( '(-h --help)'{-h,--help}'[Prints help information]' '(-k --kill)'{-k,--kill}'[Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)]' '(-n --no-shell)'{-n,--no-shell}'[Do not wrap command in ''sh -c'' resp. ''cmd.exe /C'']' + '--no-environment[Do not set WATCHEXEC_*_PATH environment variables for child process]' '(-p --postpone)'{-p,--postpone}'[Wait until first change to execute command]' '(-r --restart)'{-r,--restart}'[Restart the process if it''s still running]' '(-V --version)'{-V,--version}'[Prints version information]' diff --git a/doc/watchexec.1.html b/doc/watchexec.1.html index c9b55de..1f9f242 100644 --- a/doc/watchexec.1.html +++ b/doc/watchexec.1.html @@ -90,6 +90,7 @@
-f, --filter pattern

Ignores modifications from paths that do not match pattern. This option can be specified multiple times, where a match on any given pattern causes the path to trigger command.

-s, --signal

Sends the specified signal (e.g. SIGKILL) to the child process. Defaults to SIGTERM.

-n, --no-shell

Execute command directly, do not wrap it in sh -c resp. cmd.exe /C. This is especially useful in combination with --signal, as the signal is then send directly to the specified command. While --no-shell is a little more performant than the default, it prevents using shell-features like pipes and redirects.

+
--no-environment

Do not set WATCHEXEC_*_PATH environment variables for child process.

-i, --ignore pattern

Ignores modifications from paths that match pattern. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.

-w, --watch path

Monitor a specific path for changes. By default, the current working directory is watched. This may be specified multiple times, where a change in any watched directory (and subdirectories) causes command to be executed.

-r, --restart

Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends SIGTERM; use --kill to send SIGKILL.

diff --git a/doc/watchexec.1.ronn b/doc/watchexec.1.ronn index 7912607..c7f4d11 100644 --- a/doc/watchexec.1.ronn +++ b/doc/watchexec.1.ronn @@ -28,6 +28,9 @@ Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `S * `-n`, `--no-shell`: Execute command directly, do not wrap it in `sh -c` resp. `cmd.exe /C`. This is especially useful in combination with `--signal`, as the signal is then send directly to the specified command. While `--no-shell` is a little more performant than the default, it prevents using shell-features like pipes and redirects. +* `--no-environment`: +Do not set WATCHEXEC_*_PATH environment variables for child process. + * `-i`, `--ignore` : Ignores modifications from paths that match . This option can be specified multiple times, and a match on any pattern causes the path to be ignored. diff --git a/src/cli.rs b/src/cli.rs index 90e4d89..dcef984 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -58,6 +58,9 @@ pub struct Args { /// Do not wrap the commands in a shell. #[builder(default)] pub no_shell: bool, + /// Do not set WATCHEXEC_*_PATH environment variables for child process. + #[builder(default)] + pub no_environment: bool, /// Skip auto-loading .gitignore files #[builder(default)] pub no_vcs_ignore: bool, @@ -207,6 +210,9 @@ where .help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'") .short("n") .long("no-shell")) + .arg(Arg::with_name("no-environment") + .help("Do not set WATCHEXEC_*_PATH environment variables for child process") + .long("no-environment")) .arg(Arg::with_name("once").short("1").hidden(true)) .arg(Arg::with_name("watch-when-idle") .help("Ignore events while the process is still running") @@ -302,6 +308,7 @@ where debug: args.is_present("verbose"), run_initially: !args.is_present("postpone"), no_shell: args.is_present("no-shell"), + no_environment: args.is_present("no-environment"), no_vcs_ignore: args.is_present("no-vcs-ignore"), no_ignore: args.is_present("no-ignore"), once: args.is_present("once"), diff --git a/src/process.rs b/src/process.rs index 9b9185f..72cea33 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,8 +5,8 @@ use crate::pathop::PathOp; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; -pub fn spawn(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result { - self::imp::Process::new(cmd, updated_paths, no_shell).map_err(|e| e.into()) +pub fn spawn(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result { + self::imp::Process::new(cmd, updated_paths, no_shell, no_environment).map_err(|e| e.into()) } pub use self::imp::Process; @@ -83,7 +83,7 @@ mod imp { #[allow(clippy::mutex_atomic)] impl Process { - pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result { + pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result { use nix::unistd::*; use std::convert::TryInto; use std::os::unix::process::CommandExt; @@ -107,7 +107,12 @@ mod imp { debug!("Assembled command {:?}", command); - let command_envs = super::collect_path_env_vars(updated_paths); + let command_envs = if no_environment { + Vec::new() + } else { + super::collect_path_env_vars(updated_paths) + }; + for &(ref name, ref val) in &command_envs { command.env(name, val); } @@ -229,7 +234,7 @@ mod imp { } impl Process { - pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool) -> Result { + pub fn new(cmd: &[String], updated_paths: &[PathOp], no_shell: bool, no_environment: bool) -> Result { use std::convert::TryInto; use std::os::windows::io::IntoRawHandle; use std::os::windows::process::CommandExt; @@ -304,7 +309,12 @@ mod imp { command.creation_flags(CREATE_SUSPENDED); debug!("Assembled command {:?}", command); - let command_envs = super::collect_path_env_vars(updated_paths); + let command_envs = if no_environment { + Vec::new() + } else { + super::collect_path_env_vars(updated_paths) + }; + for &(ref name, ref val) in &command_envs { command.env(name, val); } diff --git a/src/run.rs b/src/run.rs index 0a25e12..76e7ce0 100644 --- a/src/run.rs +++ b/src/run.rs @@ -185,7 +185,7 @@ impl ExecHandler { debug!("Launching child process"); let mut guard = self.child_process.write()?; - *guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell)?); + *guard = Some(process::spawn(&self.args.cmd, ops, self.args.no_shell, self.args.no_environment)?); Ok(()) } From d246114678ebe99fa293240cc1d27ae7a87cc3f9 Mon Sep 17 00:00:00 2001 From: Quint Guvernator Date: Tue, 23 Jun 2020 14:13:07 +0200 Subject: [PATCH 2/5] add --no-meta switch to avoid some redundant events for #78 --- completions/zsh | 1 + doc/watchexec.1.html | 1 + doc/watchexec.1.ronn | 3 +++ src/cli.rs | 7 +++++++ src/run.rs | 10 ++++++++-- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/completions/zsh b/completions/zsh index 2eb13da..048138b 100644 --- a/completions/zsh +++ b/completions/zsh @@ -14,6 +14,7 @@ args=( '(-k --kill)'{-k,--kill}'[Send SIGKILL to child processes (deprecated, use -s SIGKILL instead)]' '(-n --no-shell)'{-n,--no-shell}'[Do not wrap command in ''sh -c'' resp. ''cmd.exe /C'']' '--no-environment[Do not set WATCHEXEC_*_PATH environment variables for child process]' + '--no-meta[Ignore metadata changes]' '(-p --postpone)'{-p,--postpone}'[Wait until first change to execute command]' '(-r --restart)'{-r,--restart}'[Restart the process if it''s still running]' '(-V --version)'{-V,--version}'[Prints version information]' diff --git a/doc/watchexec.1.html b/doc/watchexec.1.html index 1f9f242..0cf7b28 100644 --- a/doc/watchexec.1.html +++ b/doc/watchexec.1.html @@ -90,6 +90,7 @@
-f, --filter pattern

Ignores modifications from paths that do not match pattern. This option can be specified multiple times, where a match on any given pattern causes the path to trigger command.

-s, --signal

Sends the specified signal (e.g. SIGKILL) to the child process. Defaults to SIGTERM.

-n, --no-shell

Execute command directly, do not wrap it in sh -c resp. cmd.exe /C. This is especially useful in combination with --signal, as the signal is then send directly to the specified command. While --no-shell is a little more performant than the default, it prevents using shell-features like pipes and redirects.

+
--no-meta

Ignore metadata changes

--no-environment

Do not set WATCHEXEC_*_PATH environment variables for child process.

-i, --ignore pattern

Ignores modifications from paths that match pattern. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.

-w, --watch path

Monitor a specific path for changes. By default, the current working directory is watched. This may be specified multiple times, where a change in any watched directory (and subdirectories) causes command to be executed.

diff --git a/doc/watchexec.1.ronn b/doc/watchexec.1.ronn index c7f4d11..b885001 100644 --- a/doc/watchexec.1.ronn +++ b/doc/watchexec.1.ronn @@ -28,6 +28,9 @@ Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `S * `-n`, `--no-shell`: Execute command directly, do not wrap it in `sh -c` resp. `cmd.exe /C`. This is especially useful in combination with `--signal`, as the signal is then send directly to the specified command. While `--no-shell` is a little more performant than the default, it prevents using shell-features like pipes and redirects. +* `--no-meta`: +Ignore metadata changes. + * `--no-environment`: Do not set WATCHEXEC_*_PATH environment variables for child process. diff --git a/src/cli.rs b/src/cli.rs index dcef984..51fe376 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -58,6 +58,9 @@ pub struct Args { /// Do not wrap the commands in a shell. #[builder(default)] pub no_shell: bool, + /// Ignore metadata changes. + #[builder(default)] + pub no_meta: bool, /// Do not set WATCHEXEC_*_PATH environment variables for child process. #[builder(default)] pub no_environment: bool, @@ -210,6 +213,9 @@ where .help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'") .short("n") .long("no-shell")) + .arg(Arg::with_name("no-meta") + .help("Ignore metadata changes") + .long("no-meta")) .arg(Arg::with_name("no-environment") .help("Do not set WATCHEXEC_*_PATH environment variables for child process") .long("no-environment")) @@ -308,6 +314,7 @@ where debug: args.is_present("verbose"), run_initially: !args.is_present("postpone"), no_shell: args.is_present("no-shell"), + no_meta: args.is_present("no-meta"), no_environment: args.is_present("no-environment"), no_vcs_ignore: args.is_present("no-vcs-ignore"), no_ignore: args.is_present("no-ignore"), diff --git a/src/run.rs b/src/run.rs index 76e7ce0..b6dea0c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -134,7 +134,7 @@ where loop { debug!("Waiting for filesystem activity"); - let paths = wait_fs(&rx, &filter, args.debounce); + let paths = wait_fs(&rx, &filter, args.debounce, args.no_meta); debug!("Paths updated: {:?}", paths); if !handler.on_update(&paths)? { @@ -282,7 +282,7 @@ pub fn run(args: Args) -> Result<()> { watch(&ExecHandler::new(args)?) } -fn wait_fs(rx: &Receiver, filter: &NotificationFilter, debounce: u64) -> Vec { +fn wait_fs(rx: &Receiver, filter: &NotificationFilter, debounce: u64, no_meta: bool) -> Vec { let mut paths = Vec::new(); let mut cache = HashMap::new(); @@ -291,6 +291,12 @@ fn wait_fs(rx: &Receiver, filter: &NotificationFilter, debounce: u64) -> if let Some(ref path) = e.path { let pathop = PathOp::new(path, e.op.ok(), e.cookie); + if let Some(op) = pathop.op { + if no_meta && PathOp::is_meta(op) { + continue; + } + } + // Ignore cache for the initial file. Otherwise, in // debug mode it's hard to track what's going on let excluded = filter.is_excluded(path); From 7770de8f7c6fdedd97962a628dbb4f99385012db Mon Sep 17 00:00:00 2001 From: Quint Guvernator Date: Tue, 23 Jun 2020 15:02:10 +0200 Subject: [PATCH 3/5] test: add new arguments to spawn(...) --- src/process.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process.rs b/src/process.rs index 72cea33..41d72af 100644 --- a/src/process.rs +++ b/src/process.rs @@ -518,7 +518,7 @@ mod tests { #[test] fn test_start() { - let _ = spawn(&["echo".into(), "hi".into()], &[], true); + let _ = spawn(&["echo".into(), "hi".into()], &[], true, false); } /* @@ -641,7 +641,7 @@ mod tests { #[test] fn test_start() { - let _ = spawn(&["echo".into(), "hi".into()], &[], true); + let _ = spawn(&["echo".into(), "hi".into()], &[], true, false); } /* From cca7b192832eabe5185e1336bef76f1e124397e0 Mon Sep 17 00:00:00 2001 From: Quint Guvernator Date: Wed, 24 Jun 2020 10:58:15 +0200 Subject: [PATCH 4/5] fix some clippy lints --- src/gitignore.rs | 5 +++-- src/ignore.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gitignore.rs b/src/gitignore.rs index 5b2f92f..a67185a 100644 --- a/src/gitignore.rs +++ b/src/gitignore.rs @@ -4,6 +4,7 @@ use std::fs; use std::io; use std::io::Read; use std::path::{Path, PathBuf}; +use std::borrow::{ToOwned}; use walkdir::WalkDir; pub struct Gitignore { @@ -58,7 +59,7 @@ pub fn load(paths: &[PathBuf]) -> Gitignore { } } - p = current.parent().map(|p| p.to_owned()); + p = current.parent().map(ToOwned::to_owned); } if let Some(root) = top_level_git_dir { @@ -70,7 +71,7 @@ pub fn load(paths: &[PathBuf]) -> Gitignore { .filter(|e| e.file_name() == ".gitignore") { let gitignore_path = entry.path(); - if let Ok(f) = GitignoreFile::new(&gitignore_path) { + if let Ok(f) = GitignoreFile::new(gitignore_path) { debug!("Loaded {:?}", gitignore_path); files.push(f); } else { diff --git a/src/ignore.rs b/src/ignore.rs index 7988b02..c9df5ee 100644 --- a/src/ignore.rs +++ b/src/ignore.rs @@ -80,7 +80,7 @@ pub fn load(paths: &[PathBuf]) -> Ignore { .filter(|e| e.file_name() == ".ignore") { let ignore_path = entry.path(); - if let Ok(f) = IgnoreFile::new(&ignore_path) { + if let Ok(f) = IgnoreFile::new(ignore_path) { debug!("Loaded {:?}", ignore_path); files.push(f); } else { From 2615a1de7c17abe265719577c51a525906206060 Mon Sep 17 00:00:00 2001 From: Quint Guvernator Date: Wed, 24 Jun 2020 13:40:11 +0200 Subject: [PATCH 5/5] wrap ioerror when it represents oserror E2BIG --- src/error.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 9a140e4..1986948 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,7 +33,18 @@ impl From for Error { impl From for Error { fn from(err: io::Error) -> Self { - Self::Io(err) + Self::Io( + match err.raw_os_error() { + Some(os_err) => match os_err { + 7 => { + let msg = "There are so many changed files that the environment variables of the child process have been overrun. Try running with --no-meta or --no-environment."; + io::Error::new(io::ErrorKind::Other, msg) + }, + _ => err, + } + None => err, + } + ) } }