mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-25 14:44:28 +00:00
Fix Linux watcher cleanup for recreated directories (#50412)
## Problem - On Linux, non-recursive watcher registrations remained path-cached after deleting and recreating a directory in the same session. - The recreated directory was not re-watched, so newly created child entries under that path could be missing. ## Summary - Remove directory watcher registrations when worktree paths are removed from snapshot state. - Ensure recreated directories can be watched again on Linux by allowing `scan_dir` to re-add fresh watches. - Add a Linux integration regression test for directory delete/recreate path reuse and child file creation. ## Testing - `cargo test -p project --features test-support --test integration test_recreated_directory_receives_child_events -- --exact` - `cargo test -p project --features test-support --test integration test_rescan_and_remote_updates -- --exact` ## Related - #46709 Release Notes: - Fixed Linux worktree file watching so child entries appear after deleting and recreating a directory at the same path.
This commit is contained in:
parent
38c7e63af3
commit
c19cc4c51e
2 changed files with 61 additions and 3 deletions
|
|
@ -5359,6 +5359,52 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[gpui::test(retries = 5)]
|
||||
async fn test_recreated_directory_receives_child_events(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let dir = TempTree::new(json!({}));
|
||||
let project = Project::test(Arc::new(RealFs::new(None, cx.executor())), [dir.path()], cx).await;
|
||||
let tree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
let repro_dir = dir.path().join("repro");
|
||||
std::fs::create_dir(&repro_dir).unwrap();
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(tree.read(cx).entry_for_path(rel_path("repro")).is_some());
|
||||
});
|
||||
|
||||
std::fs::remove_dir_all(&repro_dir).unwrap();
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(tree.read(cx).entry_for_path(rel_path("repro")).is_none());
|
||||
});
|
||||
|
||||
std::fs::create_dir(&repro_dir).unwrap();
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(tree.read(cx).entry_for_path(rel_path("repro")).is_some());
|
||||
});
|
||||
|
||||
std::fs::write(repro_dir.join("repro-marker"), "").unwrap();
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
assert!(
|
||||
tree.read(cx)
|
||||
.entry_for_path(rel_path("repro/repro-marker"))
|
||||
.is_some()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
|
|
|||
|
|
@ -2945,7 +2945,7 @@ impl BackgroundScannerState {
|
|||
self.snapshot.check_invariants(false);
|
||||
}
|
||||
|
||||
fn remove_path(&mut self, path: &RelPath) {
|
||||
fn remove_path(&mut self, path: &RelPath, watcher: &dyn Watcher) {
|
||||
log::trace!("background scanner removing path {path:?}");
|
||||
let mut new_entries;
|
||||
let removed_entries;
|
||||
|
|
@ -2961,7 +2961,12 @@ impl BackgroundScannerState {
|
|||
self.snapshot.entries_by_path = new_entries;
|
||||
|
||||
let mut removed_ids = Vec::with_capacity(removed_entries.summary().count);
|
||||
let mut removed_dir_abs_paths = Vec::new();
|
||||
for entry in removed_entries.cursor::<()>(()) {
|
||||
if entry.is_dir() {
|
||||
removed_dir_abs_paths.push(self.snapshot.absolutize(&entry.path));
|
||||
}
|
||||
|
||||
match self.removed_entries.entry(entry.inode) {
|
||||
hash_map::Entry::Occupied(mut e) => {
|
||||
let prev_removed_entry = e.get_mut();
|
||||
|
|
@ -2997,6 +3002,10 @@ impl BackgroundScannerState {
|
|||
.git_repositories
|
||||
.retain(|id, _| removed_ids.binary_search(id).is_err());
|
||||
|
||||
for removed_dir_abs_path in removed_dir_abs_paths {
|
||||
watcher.remove(&removed_dir_abs_path).log_err();
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
self.snapshot.check_invariants(false);
|
||||
}
|
||||
|
|
@ -4461,7 +4470,10 @@ impl BackgroundScanner {
|
|||
|
||||
if self.settings.is_path_excluded(&child_path) {
|
||||
log::debug!("skipping excluded child entry {child_path:?}");
|
||||
self.state.lock().await.remove_path(&child_path);
|
||||
self.state
|
||||
.lock()
|
||||
.await
|
||||
.remove_path(&child_path, self.watcher.as_ref());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -4651,7 +4663,7 @@ impl BackgroundScanner {
|
|||
// detected regardless of the order of the paths.
|
||||
for (path, metadata) in relative_paths.iter().zip(metadata.iter()) {
|
||||
if matches!(metadata, Ok(None)) || doing_recursive_update {
|
||||
state.remove_path(path);
|
||||
state.remove_path(path, self.watcher.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue