Dependency update: update notify version to v5 (#8114)

# Description

Relative: #8060

While investigating, I found we need to update notify, which is a good
step to remove some duplicate dependencies.

As title, here are some goods and bads after updating:

## Good
keep dependency up to date, and remove duplidate dependency(cfg-if,
winapi) in Cargo.lock.

## Bad
Introduce some breaking changes:
After updating to notify v5, I found that we have to remove `Rename`
events.

But I've testing under notify v4, and it doesn't work good if we running
the following command on MacOS:
```
touch a
mv a b
```
It fires file create event, but no file rename event. So `rename` event
is not really reliable, so I think it's ok for us to remove `Rename`
events.

The reason to remove `--debounce-ms` flag:
It's not provided by defualt file watcher, we can use
[PollWatcher](https://docs.rs/notify/latest/notify/poll/struct.PollWatcher.html),
but it scans filesystem, which is really expensive. So I just remove the
flag.

# User-Facing Changes

1. `--debounce-ms` flag is removed
2. no longer watch `Rename` event.

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
WindSoilder
2023-02-23 05:35:09 +08:00
committed by GitHub
parent fc23c6721a
commit 0e86ba4b63
3 changed files with 159 additions and 310 deletions

View File

@ -61,7 +61,7 @@ lscolors = { version = "0.12.0", features = ["crossterm"], default-features = fa
md5 = { package = "md-5", version = "0.10.0" }
mime = "0.3.16"
mime_guess = "2.0.4"
notify = "4.0.17"
notify = "5.1.0"
num = { version = "0.4.0", optional = true }
num-traits = "0.2.14"
once_cell = "1.17"

View File

@ -2,7 +2,7 @@ use std::path::PathBuf;
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::time::Duration;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use notify::{recommended_watcher, EventKind, RecursiveMode, Watcher};
use nu_engine::{current_dir, eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack, StateWorkingSet};
@ -13,7 +13,6 @@ use nu_protocol::{
// durations chosen mostly arbitrarily
const CHECK_CTRL_C_FREQUENCY: Duration = Duration::from_millis(100);
const DEFAULT_WATCH_DEBOUNCE_DURATION: Duration = Duration::from_millis(100);
#[derive(Clone)]
pub struct Watch;
@ -38,12 +37,6 @@ impl Command for Watch {
.required("closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::String, SyntaxShape::String, SyntaxShape::String])),
"Some Nu code to run whenever a file changes. The closure will be passed `operation`, `path`, and `new_path` (for renames only) arguments in that order")
.named(
"debounce-ms",
SyntaxShape::Int,
"Debounce changes for this many milliseconds (default: 100). Adjust if you find that single writes are reported as multiple events",
Some('d'),
)
.named(
"glob",
SyntaxShape::String, // SyntaxShape::GlobPattern gets interpreted relative to cwd, so use String instead
@ -91,22 +84,6 @@ impl Command for Watch {
.clone();
let verbose = call.has_flag("verbose");
let debounce_duration_flag: Option<Spanned<i64>> =
call.get_flag(engine_state, stack, "debounce-ms")?;
let debounce_duration = match debounce_duration_flag {
Some(val) => match u64::try_from(val.item) {
Ok(val) => Duration::from_millis(val),
Err(_) => {
return Err(ShellError::TypeMismatch(
"Debounce duration is invalid".to_string(),
val.span,
))
}
},
None => DEFAULT_WATCH_DEBOUNCE_DURATION,
};
let glob_flag: Option<Spanned<String>> = call.get_flag(engine_state, stack, "glob")?;
let glob_pattern = match glob_flag {
Some(glob) => {
@ -144,7 +121,7 @@ impl Command for Watch {
let ctrlc_ref = &engine_state.ctrlc.clone();
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = match Watcher::new(tx, debounce_duration) {
let mut watcher = match recommended_watcher(tx) {
Ok(w) => w,
Err(e) => {
return Err(ShellError::IOError(format!(
@ -152,104 +129,83 @@ impl Command for Watch {
)))
}
};
if let Err(e) = watcher.watch(path.clone(), recursive_mode) {
if let Err(e) = watcher.watch(&path, recursive_mode) {
return Err(ShellError::IOError(format!("Failed to start watcher: {e}")));
}
eprintln!("Now watching files at {path:?}. Press ctrl+c to abort.");
let event_handler =
|operation: &str, path: PathBuf, new_path: Option<PathBuf>| -> Result<(), ShellError> {
let glob_pattern = glob_pattern.clone();
let matches_glob = match glob_pattern.clone() {
Some(glob) => glob.matches_path(&path),
None => true,
};
if verbose && glob_pattern.is_some() {
eprintln!("Matches glob: {matches_glob}");
}
if matches_glob {
let stack = &mut stack.clone();
if let Some(position) = block.signature.get_positional(0) {
if let Some(position_id) = &position.var_id {
stack.add_var(*position_id, Value::string(operation, call.span()));
}
}
if let Some(position) = block.signature.get_positional(1) {
if let Some(position_id) = &position.var_id {
stack.add_var(
*position_id,
Value::string(path.to_string_lossy(), call.span()),
);
}
}
if let Some(position) = block.signature.get_positional(2) {
if let Some(position_id) = &position.var_id {
stack.add_var(
*position_id,
Value::string(
new_path.unwrap_or_else(|| "".into()).to_string_lossy(),
call.span(),
),
);
}
}
let eval_result = eval_block(
engine_state,
stack,
&block,
Value::Nothing { span: call.span() }.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match eval_result {
Ok(val) => {
val.print(engine_state, stack, false, false)?;
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
eprintln!("{}", format_error(&working_set, &err));
}
}
}
Ok(())
let event_handler = |operation: &str, path: PathBuf| -> Result<(), ShellError> {
let glob_pattern = glob_pattern.clone();
let matches_glob = match glob_pattern.clone() {
Some(glob) => glob.matches_path(&path),
None => true,
};
if verbose && glob_pattern.is_some() {
eprintln!("Matches glob: {matches_glob}");
}
if matches_glob {
let stack = &mut stack.clone();
if let Some(position) = block.signature.get_positional(0) {
if let Some(position_id) = &position.var_id {
stack.add_var(*position_id, Value::string(operation, call.span()));
}
}
if let Some(position) = block.signature.get_positional(1) {
if let Some(position_id) = &position.var_id {
stack.add_var(
*position_id,
Value::string(path.to_string_lossy(), call.span()),
);
}
}
let eval_result = eval_block(
engine_state,
stack,
&block,
Value::Nothing { span: call.span() }.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match eval_result {
Ok(val) => {
val.print(engine_state, stack, false, false)?;
}
Err(err) => {
let working_set = StateWorkingSet::new(engine_state);
eprintln!("{}", format_error(&working_set, &err));
}
}
}
Ok(())
};
loop {
match rx.recv_timeout(CHECK_CTRL_C_FREQUENCY) {
Ok(event) => {
Ok(Ok(mut event)) => {
if verbose {
eprintln!("{event:?}");
}
let handler_result = match event {
DebouncedEvent::Create(path) => event_handler("Create", path, None),
DebouncedEvent::Write(path) => event_handler("Write", path, None),
DebouncedEvent::Remove(path) => event_handler("Remove", path, None),
DebouncedEvent::Rename(path, new_path) => {
event_handler("Rename", path, Some(new_path))
}
DebouncedEvent::Error(err, path) => match path {
Some(path) => Err(ShellError::IOError(format!(
"Error detected for {path:?}: {err:?}"
))),
None => Err(ShellError::IOError(format!("Error detected: {err:?}"))),
},
// These are less likely to be interesting events
DebouncedEvent::Chmod(_)
| DebouncedEvent::NoticeRemove(_)
| DebouncedEvent::NoticeWrite(_)
| DebouncedEvent::Rescan => Ok(()),
let path = match event.paths.pop() {
None => continue,
Some(p) => p,
};
handler_result?;
match event.kind {
EventKind::Create(_) => event_handler("Create", path),
EventKind::Modify(notify::event::ModifyKind::Data(_)) => {
event_handler("Write", path)
}
EventKind::Remove(_) => event_handler("Remove", path),
_ => Ok(()),
}?
}
Ok(Err(e)) => return Err(ShellError::IOError(format!("watch error: {e}"))),
Err(RecvTimeoutError::Disconnected) => {
return Err(ShellError::IOError(
"Unexpected disconnect from file watcher".into(),
@ -274,7 +230,7 @@ impl Command for Watch {
},
Example {
description: "Watch all changes in the current directory",
example: r#"watch . { |op, path, new_path| $"($op) ($path) ($new_path)"}"#,
example: r#"watch . { |op, path| $"($op) ($path)"}"#,
result: None,
},
Example {