diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 2f8b618494fe8a206d0d62f8d0683fbff0738280..03f461177a77ad36f530fde05c77c572bf6315be 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -267,6 +267,7 @@ struct BackgroundScannerState { removed_entries: HashMap, changed_paths: Vec>, prev_snapshot: Snapshot, + scanning_enabled: bool, } #[derive(Debug, Clone)] @@ -447,7 +448,11 @@ impl Worktree { snapshot.root_char_bag, None, ); - if !metadata.is_dir { + if metadata.is_dir { + if !scanning_enabled { + entry.kind = EntryKind::UnloadedDir; + } + } else { if let Some(file_name) = abs_path.file_name() && let Some(file_name) = file_name.to_str() && let Ok(path) = RelPath::unix(file_name) @@ -1101,6 +1106,7 @@ impl LocalWorktree { prev_snapshot: snapshot.snapshot.clone(), snapshot, scanned_dirs: Default::default(), + scanning_enabled, path_prefixes_to_scan: Default::default(), paths_to_scan: Default::default(), removed_entries: Default::default(), @@ -1108,7 +1114,6 @@ impl LocalWorktree { }), phase: BackgroundScannerPhase::InitialScan, share_private_files, - scanning_enabled, settings, watcher, }; @@ -2777,7 +2782,7 @@ impl LocalSnapshot { impl BackgroundScannerState { fn should_scan_directory(&self, entry: &Entry) -> bool { - (!entry.is_external && (!entry.is_ignored || entry.is_always_included)) + (self.scanning_enabled && !entry.is_external && (!entry.is_ignored || entry.is_always_included)) || entry.path.file_name() == Some(DOT_GIT) || entry.path.file_name() == Some(local_settings_folder_name()) || entry.path.file_name() == Some(local_vscode_folder_name()) @@ -3726,7 +3731,6 @@ struct BackgroundScanner { watcher: Arc, settings: WorktreeSettings, share_private_files: bool, - scanning_enabled: bool, } #[derive(Copy, Clone, PartialEq)] @@ -3738,12 +3742,18 @@ enum BackgroundScannerPhase { impl BackgroundScanner { async fn run(&mut self, mut fs_events_rx: Pin>>>) { + let root_abs_path; + let scanning_enabled; + { + let state = self.state.lock().await; + root_abs_path = state.snapshot.abs_path.clone(); + scanning_enabled = state.scanning_enabled; + } + // If the worktree root does not contain a git repository, then find // the git repository in an ancestor directory. Find any gitignore files // in ancestor directories. - let root_abs_path = self.state.lock().await.snapshot.abs_path.clone(); - - let repo = if self.scanning_enabled { + let repo = if scanning_enabled { let (ignores, exclude, repo) = discover_ancestor_git_repo(self.fs.clone(), &root_abs_path).await; self.state @@ -3767,7 +3777,7 @@ impl BackgroundScanner { }; let containing_git_repository = if let Some((ancestor_dot_git, work_directory)) = repo - && self.scanning_enabled + && scanning_enabled { maybe!(async { self.state @@ -3792,7 +3802,7 @@ impl BackgroundScanner { let mut global_gitignore_events = if let Some(global_gitignore_path) = &paths::global_gitignore_path() - && self.scanning_enabled + && scanning_enabled { let is_file = self.fs.is_file(&global_gitignore_path).await; self.state.lock().await.snapshot.global_gitignore = if is_file { @@ -3835,7 +3845,7 @@ impl BackgroundScanner { .insert_entry(root_entry, self.fs.as_ref(), self.watcher.as_ref()) .await; } - if root_entry.is_dir() && self.scanning_enabled { + if root_entry.is_dir() && state.scanning_enabled { state .enqueue_scan_dir( root_abs_path.as_path().into(), diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 45d39710c6ea825aded4d29f447124ee4c2ecb33..e8c7180b47d890097004682668ee3c2c235fc01f 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -2843,3 +2843,84 @@ async fn test_write_file_encoding(cx: &mut gpui::TestAppContext) { ); } } + +#[gpui::test] +async fn test_refresh_entries_for_paths_creates_ancestors(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/root", + json!({ + "a": { + "b": { + "c": { + "deep_file.txt": "content", + "sibling.txt": "content" + }, + "d": { + "under_sibling_dir.txt": "content" + } + } + } + }), + ) + .await; + + let tree = Worktree::local( + Path::new("/root"), + true, + fs.clone(), + Default::default(), + false, // Disable scanning so the initial scan doesn't discover any entries + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.read_with(cx, |tree, _| { + assert_eq!( + tree.entries(true, 0) + .map(|e| e.path.as_ref()) + .collect::>(), + &[rel_path("")], + "Only root entry should exist when scanning is disabled" + ); + + assert!(tree.entry_for_path(rel_path("a")).is_none()); + assert!(tree.entry_for_path(rel_path("a/b")).is_none()); + assert!(tree.entry_for_path(rel_path("a/b/c")).is_none()); + assert!( + tree.entry_for_path(rel_path("a/b/c/deep_file.txt")) + .is_none() + ); + }); + + tree.read_with(cx, |tree, _| { + tree.as_local() + .unwrap() + .refresh_entries_for_paths(vec![rel_path("a/b/c/deep_file.txt").into()]) + }) + .recv() + .await; + + tree.read_with(cx, |tree, _| { + assert_eq!( + tree.entries(true, 0) + .map(|e| e.path.as_ref()) + .collect::>(), + &[ + rel_path(""), + rel_path("a"), + rel_path("a/b"), + rel_path("a/b/c"), + rel_path("a/b/c/deep_file.txt"), + rel_path("a/b/c/sibling.txt"), + rel_path("a/b/d"), + ], + "All ancestors should be created when refreshing a deeply nested path" + ); + }); +}