From 86322a186faa0756da0dcc4facb23d314ba5beb3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Oct 2025 15:12:24 +0200 Subject: [PATCH] worktree: Prevent background scanner from trying to scan file worktrees (#39277) Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/fs/src/fs.rs | 10 +++-- crates/project/src/project_tests.rs | 2 +- crates/worktree/src/worktree.rs | 67 +++++++++++++++++------------ 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index c7e7380febceac365e72a59f1609507caee22a81..36c182d444390b31c4358bbc0cab40a52bf8f0dc 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -791,11 +791,15 @@ impl Fs for RealFs { let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone())); // If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created. - if watcher.add(path).is_err() + if let Err(e) = watcher.add(path) && let Some(parent) = path.parent() - && let Err(e) = watcher.add(parent) + && let Err(parent_e) = watcher.add(parent) { - log::warn!("Failed to watch: {e}"); + log::warn!( + "Failed to watch {} and its parent directory {}:\n{e}\n{parent_e}", + path.display(), + parent.display() + ); } // Check if path is a symlink and follow the target parent diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 8d7da92f6b988a21c3879a0f0cbdcd9b2a616e66..d2f8b843a11e7e22473f482c080481573985e2f9 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1380,7 +1380,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon cx.executor().run_until_parked(); assert_eq!(mem::take(&mut *file_changes.lock()), &[]); - assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 5); + assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); let mut new_watched_paths = fs.watched_paths(); new_watched_paths.retain(|path| { diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 807aa9943129cd18eb8f5cede47adf58b6993491..3daf83a3a12935587a9c3554c4d0a00171690b9d 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -158,6 +158,7 @@ pub struct RemoteWorktree { #[derive(Clone)] pub struct Snapshot { id: WorktreeId, + /// The absolute path of the worktree root. abs_path: Arc, path_style: PathStyle, root_name: Arc, @@ -235,7 +236,7 @@ pub struct LocalSnapshot { /// All of the git repositories in the worktree, indexed by the project entry /// id of their parent directory. git_repositories: TreeMap, - /// The file handle of the root dir + /// The file handle of the worktree root. `None` if the worktree is a directory. /// (so we can find it after it's been moved) root_file_handle: Option>, } @@ -370,11 +371,19 @@ impl Worktree { true }); - let root_file_handle = fs - .open_handle(&abs_path) - .await - .context("failed to open local worktree root") - .log_err(); + let root_file_handle = if metadata.as_ref().is_some() { + fs.open_handle(&abs_path) + .await + .with_context(|| { + format!( + "failed to open local worktree root at {}", + abs_path.display() + ) + }) + .log_err() + } else { + None + }; cx.new(move |cx: &mut Context| { let mut snapshot = LocalSnapshot { @@ -3572,25 +3581,25 @@ impl BackgroundScanner { log::trace!("containing git repository: {containing_git_repository:?}"); - let global_gitignore_path = paths::global_gitignore_path(); - self.state.lock().snapshot.global_gitignore = - if let Some(global_gitignore_path) = global_gitignore_path.as_ref() { - build_gitignore(global_gitignore_path, self.fs.as_ref()) + let mut global_gitignore_events = + if let Some(global_gitignore_path) = &paths::global_gitignore_path() { + self.state.lock().snapshot.global_gitignore = + if self.fs.is_file(&global_gitignore_path).await { + build_gitignore(global_gitignore_path, self.fs.as_ref()) + .await + .ok() + .map(Arc::new) + } else { + None + }; + self.fs + .watch(global_gitignore_path, FS_WATCH_LATENCY) .await - .ok() - .map(Arc::new) + .0 } else { - None + self.state.lock().snapshot.global_gitignore = None; + Box::pin(futures::stream::empty()) }; - let mut global_gitignore_events = if let Some(global_gitignore_path) = global_gitignore_path - { - self.fs - .watch(&global_gitignore_path, FS_WATCH_LATENCY) - .await - .0 - } else { - Box::pin(futures::stream::empty()) - }; let (scan_job_tx, scan_job_rx) = channel::unbounded(); { @@ -3606,12 +3615,14 @@ impl BackgroundScanner { root_entry.is_ignored = true; state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref()); } - state.enqueue_scan_dir( - root_abs_path.as_path().into(), - &root_entry, - &scan_job_tx, - self.fs.as_ref(), - ); + if root_entry.is_dir() { + state.enqueue_scan_dir( + root_abs_path.as_path().into(), + &root_entry, + &scan_job_tx, + self.fs.as_ref(), + ); + } } };