Fix the filtering of index.lock + COMMIT_MESSAGE FS events to work in linked worktrees (#57763)

Zed reloads a lot of data about a git repository any time any file
changes inside of the `.git` directory, with the exception of a few
known paths that we know do not warrant a reload, such as `index.lock`
and `COMMIT_MESSAGE`. Previously, we ignored FS events for those files,
but we used a specific path that only worked for the main worktree. This
caused a lot of unnecessary reloads when using linked worktrees. Now we
ignore those files in a general way, by their filename, so that the
optimization applies to linked worktrees as well.

@cole-miller Noticed this bug.

Release Notes:

- Fixed unnecessary reloading of Git state that could occur when editing
in linked worktrees.
This commit is contained in:
Max Brunsfeld 2026-05-26 17:04:30 -07:00 committed by GitHub
parent 53ed11b2af
commit 4129fc87d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 93 additions and 8 deletions

View file

@ -15,7 +15,7 @@ test = false
[[test]]
name = "integration"
required-features = ["test-support"]
path = "tests/integration/main.rs"
path = "tests/integration/fs_tests.rs"
[dependencies]
anyhow.workspace = true

View file

@ -1,3 +1,5 @@
mod fake_git_repo_tests;
use std::{
collections::BTreeSet,
ffi::OsString,

View file

@ -1,2 +0,0 @@
mod fake_git_repo;
mod fs;

View file

@ -13,7 +13,7 @@ test = false
[[test]]
name = "integration"
required-features = ["test-support"]
path = "tests/integration/main.rs"
path = "tests/integration/worktree_tests.rs"
[lints]
workspace = true

View file

@ -4391,7 +4391,7 @@ impl BackgroundScanner {
//
// Certain directories may have FS changes, but do not lead to git data changes that Zed cares about.
// Ignore these, to avoid Zed unnecessarily rescanning git metadata.
let skipped_files_in_dot_git = [COMMIT_MESSAGE, INDEX_LOCK];
let skipped_file_names_in_dot_git = [COMMIT_MESSAGE, INDEX_LOCK];
let skipped_dirs_in_dot_git = [FSMONITOR_DAEMON, LFS_DIR];
let mut dot_git_abs_paths = Vec::new();
@ -4421,8 +4421,10 @@ impl BackgroundScanner {
}
if let Some((dot_git_abs_path, path_in_git_dir)) = dot_git_paths {
let is_ignored = skipped_files_in_dot_git.iter().any(|skipped| {
OsStr::new(skipped) == path_in_git_dir.as_path().as_os_str()
let is_ignored = skipped_file_names_in_dot_git.iter().any(|skipped| {
path_in_git_dir
.file_name()
.is_some_and(|file_name| file_name == OsStr::new(skipped))
}) || skipped_dirs_in_dot_git
.iter()
.any(|skipped_git_subdir| path_in_git_dir.starts_with(skipped_git_subdir));

View file

@ -1,4 +1,4 @@
mod worktree_settings;
mod worktree_settings_tests;
use anyhow::Result;
use encoding_rs;
@ -3404,6 +3404,89 @@ async fn test_linked_worktree_git_file_event_does_not_panic(
});
}
#[gpui::test]
async fn test_linked_worktree_index_lock_event_does_not_emit_git_repo_update(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
// Regression test: in a linked worktree, git operations like `git status`
// can touch the worktree-specific `index.lock` under the main repo's
// `.git/worktrees/<name>/`. We intend to ignore those events so they do not
// spuriously emit `UpdatedGitRepositories`.
init_test(cx);
use git::repository::Worktree as GitWorktree;
let fs = FakeFs::new(executor);
fs.insert_tree(
path!("/main_repo"),
json!({
".git": {},
"file.txt": "content",
}),
)
.await;
fs.add_linked_worktree_for_repo(
Path::new(path!("/main_repo/.git")),
false,
GitWorktree {
path: PathBuf::from(path!("/linked_worktree")),
ref_name: Some("refs/heads/feature".into()),
sha: "abc123".into(),
is_main: false,
is_bare: false,
},
)
.await;
fs.write(
path!("/linked_worktree/file.txt").as_ref(),
"content".as_bytes(),
)
.await
.unwrap();
let tree = Worktree::local(
path!("/linked_worktree").as_ref(),
true,
fs.clone(),
Arc::default(),
true,
WorktreeId::from_proto(0),
&mut cx.to_async(),
)
.await
.unwrap();
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
cx.run_until_parked();
let repo_update_count: Rc<Cell<usize>> = Rc::new(Cell::new(0));
tree.update(cx, {
let repo_update_count = repo_update_count.clone();
|_, cx| {
cx.subscribe(&cx.entity(), move |_, _, event, _| {
if matches!(event, Event::UpdatedGitRepositories(_)) {
repo_update_count.set(repo_update_count.get() + 1);
}
})
.detach();
}
});
fs.emit_fs_event(
path!("/main_repo/.git/worktrees/feature/index.lock"),
Some(PathEventKind::Changed),
);
cx.run_until_parked();
assert_eq!(
repo_update_count.get(),
0,
"linked-worktree index.lock events should not emit UpdatedGitRepositories"
);
}
#[gpui::test]
async fn test_linked_worktree_event_in_unregistered_common_git_dir_does_not_panic(
executor: BackgroundExecutor,