From e42e831a40efc0fb7ffb6d7694d6818a18dccb85 Mon Sep 17 00:00:00 2001 From: Henrique Ferreiro Date: Thu, 16 Apr 2026 13:22:30 +0100 Subject: [PATCH] git_store: Coalesce refresh pathspecs Coalesce repo-relative paths before refreshing git status and diff state. The worktree scanner can report both a directory and its descendants in the same repository bucket, which makes git receive redundant pathspecs. This keeps refresh commands smaller for large repositories such as Chromium, reducing git pathspec matching work and improving status refresh performance during large worktree updates. Partially fixes #43861 Release notes: - Reduce git CPU usage on large repos --- crates/project/src/git_store.rs | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 509d694885b1ad..c9c9ade975e0f9 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -3584,6 +3584,23 @@ impl GitStore { .collect() } + fn coalesce_repo_paths(mut paths: Vec) -> Vec { + paths.sort(); + + let mut coalesced = Vec::with_capacity(paths.len()); + for path in paths { + if coalesced + .last() + .is_some_and(|ancestor: &RepoPath| path.starts_with(ancestor)) + { + continue; + } + coalesced.push(path); + } + + coalesced + } + fn process_updated_entries( &self, worktree: &Entity, @@ -3660,6 +3677,10 @@ impl GitStore { } } + for paths in paths_by_git_repo.values_mut() { + *paths = Self::coalesce_repo_paths(mem::take(paths)); + } + paths_by_git_repo }) } @@ -8664,6 +8685,7 @@ mod tests { use super::*; use crate::Project; use fs::FakeFs; + use git::repository::{RepoPath, repo_path}; use gpui::TestAppContext; use gpui::proptest::prelude::*; use rand::{SeedableRng, rngs::StdRng}; @@ -9008,6 +9030,43 @@ mod tests { ); }); } + + fn repo_paths(paths: &[&str]) -> Vec { + paths.iter().map(repo_path).collect() + } + + #[test] + fn coalesce_repo_paths_keeps_root_only() { + let coalesced = GitStore::coalesce_repo_paths(repo_paths(&["", "src", "src/lib.rs"])); + + assert_eq!(coalesced, repo_paths(&[""])); + } + + #[test] + fn coalesce_repo_paths_keeps_existing_ancestors() { + let coalesced = GitStore::coalesce_repo_paths(repo_paths(&[ + "src", + "src/lib.rs", + "src/nested/file.rs", + "tests/test.rs", + ])); + + assert_eq!(coalesced, repo_paths(&["src", "tests/test.rs"])); + } + + #[test] + fn coalesce_repo_paths_does_not_invent_missing_parents() { + let coalesced = GitStore::coalesce_repo_paths(repo_paths(&[ + "submodule/a.txt", + "submodule/nested/b.txt", + "top_level.rs", + ])); + + assert_eq!( + coalesced, + repo_paths(&["submodule/a.txt", "submodule/nested/b.txt", "top_level.rs"]) + ); + } } /// This snapshot computes the repository state on the foreground thread while