Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions cli/util/file_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ where
let initial_cwd = std::env::current_dir()
.ok()
.and_then(|path| deno_path_util::url_from_directory_path(&path).ok());
let exclude_set = flags.resolve_watch_exclude_set()?;
let exclude_set = Arc::new(flags.resolve_watch_exclude_set()?);
let (paths_to_watch_tx, mut paths_to_watch_rx) =
tokio::sync::mpsc::unbounded_channel();
let (restart_tx, mut restart_rx) = tokio::sync::mpsc::unbounded_channel();
Expand Down Expand Up @@ -354,7 +354,7 @@ where
tokio::task::yield_now().await;
}

let mut watcher = new_watcher(watcher_sender.clone())?;
let mut watcher = new_watcher(watcher_sender.clone(), exclude_set.clone())?;
consume_paths_to_watch(&mut watcher, &mut paths_to_watch_rx, &exclude_set);

let receiver_future = async {
Expand Down Expand Up @@ -433,6 +433,7 @@ where

fn new_watcher(
sender: Arc<mpsc::UnboundedSender<Vec<PathBuf>>>,
exclude_set: Arc<PathOrPatternSet>,
) -> Result<RecommendedWatcher, AnyError> {
Ok(Watcher::new(
move |res: Result<NotifyEvent, NotifyError>| {
Expand All @@ -447,10 +448,25 @@ fn new_watcher(
return;
}

let paths = event
// Check if all event paths are excluded before canonicalization,
// so we can distinguish "empty because excluded" from "empty
// because file was removed and canonicalize_path failed".
let all_excluded = event.paths.iter().all(|path| {
exclude_set.matches_path(path)
|| canonicalize_path(path)
.map(|p| exclude_set.matches_path(&p))
.unwrap_or(false)
});

if all_excluded {
return;
}

let paths: Vec<PathBuf> = event
.paths
.iter()
.filter_map(|path| canonicalize_path(path).ok())
.filter(|path| !exclude_set.matches_path(path))
.collect();

sender.send(paths).unwrap();
Expand Down
127 changes: 127 additions & 0 deletions tests/integration/watcher_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,133 @@ async fn run_watch_with_excluded_paths() {
check_alive_then_kill(child);
}

/// Regression test for https://github.com/denoland/deno/issues/26217
/// When using `--watch=.` (directory), excluded files within that directory
/// should not trigger restarts.
#[test(flaky)]
async fn run_watch_directory_with_excluded_file() {
let t = TempDir::new();

let file_to_watch = t.path().join("file_to_watch.js");
file_to_watch.write("console.log('hello');");

let file_to_exclude = t.path().join("file_to_exclude.txt");
file_to_exclude.write("original content");

let mut child = util::deno_cmd()
.current_dir(t.path())
.arg("run")
.arg("--watch=.")
.arg("--watch-exclude=file_to_exclude.txt")
.arg("-L")
.arg("debug")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.piped_output()
.spawn()
.unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);

wait_contains("hello", &mut stdout_lines).await;
wait_contains("finished", &mut stderr_lines).await;

// Modify excluded file - should NOT trigger restart
file_to_exclude.write("modified content");

// Modify watched file - should trigger restart
file_to_watch.write("console.log('restarted');");
wait_contains("Restarting", &mut stderr_lines).await;
wait_contains("restarted", &mut stdout_lines).await;

check_alive_then_kill(child);
}

/// When using `--watch=.`, excluded glob patterns should filter out matching
/// files from triggering restarts.
#[test(flaky)]
async fn run_watch_directory_with_excluded_glob_pattern() {
let t = TempDir::new();

let file_to_watch = t.path().join("app.js");
file_to_watch.write("console.log('hello');");

let log_file = t.path().join("debug.log");
log_file.write("log line 1");

let tmp_file = t.path().join("temp.log");
tmp_file.write("tmp data");

let mut child = util::deno_cmd()
.current_dir(t.path())
.arg("run")
.arg("--watch=.")
.arg("--watch-exclude=*.log")
.arg("-L")
.arg("debug")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.piped_output()
.spawn()
.unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);

wait_contains("hello", &mut stdout_lines).await;
wait_contains("finished", &mut stderr_lines).await;

// Modify .log files - should NOT trigger restart
log_file.write("log line 2");
tmp_file.write("tmp data updated");

// Modify watched file - should trigger restart
file_to_watch.write("console.log('updated');");
wait_contains("Restarting", &mut stderr_lines).await;
wait_contains("updated", &mut stdout_lines).await;

check_alive_then_kill(child);
}

/// When using `--watch=.`, excluded subdirectories should not trigger restarts
/// for any file changes within them.
#[test(flaky)]
async fn run_watch_directory_with_excluded_subdirectory() {
let t = TempDir::new();

let file_to_watch = t.path().join("main.js");
file_to_watch.write("console.log('start');");

let sub_dir = t.path().join("ignored_dir");
sub_dir.create_dir_all();
let sub_file = sub_dir.join("data.txt");
sub_file.write("data");

let mut child = util::deno_cmd()
.current_dir(t.path())
.arg("run")
.arg("--watch=.")
.arg("--watch-exclude=ignored_dir")
.arg("-L")
.arg("debug")
.arg(&file_to_watch)
.env("NO_COLOR", "1")
.piped_output()
.spawn()
.unwrap();
let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child);

wait_contains("start", &mut stdout_lines).await;
wait_contains("finished", &mut stderr_lines).await;

// Modify file inside excluded subdirectory - should NOT trigger restart
sub_file.write("data updated");

// Modify watched file - should trigger restart
file_to_watch.write("console.log('restarted');");
wait_contains("Restarting", &mut stderr_lines).await;
wait_contains("restarted", &mut stdout_lines).await;

check_alive_then_kill(child);
}

#[test(flaky)]
async fn run_hmr_server() {
let t = TempDir::new();
Expand Down
Loading