Create project entries for entry ancestors when scanning is disabled (#46300)

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Agus Zubiaga 2026-01-07 21:04:27 -03:00 committed by GitHub
parent e2cd9ac9b9
commit fc770b6440
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 101 additions and 10 deletions

View file

@ -267,6 +267,7 @@ struct BackgroundScannerState {
removed_entries: HashMap<u64, Entry>,
changed_paths: Vec<Arc<RelPath>>,
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<dyn Watcher>,
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<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>) {
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(),

View file

@ -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::<Vec<_>>(),
&[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::<Vec<_>>(),
&[
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"
);
});
}