From 806a72c54031a0445a4e3cfdbdc20fa5cbee9928 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Thu, 19 Mar 2026 12:52:12 -0300 Subject: [PATCH] title_bar: Prefer most specific repository for nested git repos (#51898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context I have a project with a nested git repo inside a parent repo (separate `.git` directory, not a submodule). The title bar shows the parent's branch instead of the nested repo's own branch. The issue is in `get_repository_for_worktree()` — it iterates `git_store.repositories()` (an `FxHashMap`) and returns the first `starts_with` match. Both the parent and nested repo match, and whichever one FxHashMap iterates first wins. There's no reason it should be one or the other. The fix already exists elsewhere in the codebase — `GitStore::repository_and_path_for_project_path()` at `git_store.rs:1826` uses `.max_by_key()` to pick the most specific (longest path) match. This PR applies the same approach to three functions that have the same problem: - `TitleBar::get_repository_for_worktree()` — branch display in title bar - `resolve_active_repository()` in `git_ui` — repository selection for the git panel - `get_branch_for_worktree()` in `recent_projects` — branch display in the project switcher Two other locations use a similar `starts_with` pattern (`effective_active_worktree()` in `title_bar.rs` and worktree selection in `recent_projects.rs`) but those iterate worktrees against a single known repo, not repos against a worktree — so first-match and longest-match give the same result. Left those unchanged. Closes #7566 ## How to Review All three changes are the same transformation: first-match loop (or `.find()`) → `.filter().max_by_key()` on path length. The reference is at `crates/project/src/git_store.rs:1826`. The primary fix is `get_repository_for_worktree()` in `title_bar.rs`. The other two are the same pattern. One difference from the reference: I used `.as_os_str().len()` instead of `.clone()` for the `max_by_key` key — avoids cloning an `Arc` per comparison. Among prefix-related paths (which is all that passes the filter), the longer path is always the more specific match, so length comparison is equivalent. `title_bar` has no existing test infrastructure. Happy to add a test if you'd like — the setup would follow the pattern in `test_git_traversal_with_nested_repos` (`crates/project/tests/integration/git_store.rs`). ``` cargo test -p title_bar -p git_ui -p recent_projects ./script/clippy ``` ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed branch picker showing parent repository's branch instead of the nested repository's branch when working in submodules or nested git repos. --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Smit Barmase --- crates/git_ui/src/git_ui.rs | 3 ++- crates/recent_projects/src/recent_projects.rs | 24 ++++++++++--------- crates/title_bar/src/title_bar.rs | 17 ++++++------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index 01375e600392d2b18b34ec3241aff45c5fad6e67..e12e9142d081c5f083a1f9ba414d7099776f327d 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -295,11 +295,12 @@ pub fn resolve_active_repository(workspace: &Workspace, cx: &App) -> Option Option { let worktree_abs_path = worktree.abs_path(); - for repo in repositories { - let repo = repo.read(cx); - if repo.work_directory_abs_path == worktree_abs_path - || worktree_abs_path.starts_with(&*repo.work_directory_abs_path) - { - if let Some(branch) = &repo.branch { - return Some(SharedString::from(branch.name().to_string())); - } - } - } - None + repositories + .iter() + .filter(|repo| { + let repo_path = &repo.read(cx).work_directory_abs_path; + *repo_path == worktree_abs_path || worktree_abs_path.starts_with(repo_path.as_ref()) + }) + .max_by_key(|repo| repo.read(cx).work_directory_abs_path.as_os_str().len()) + .and_then(|repo| { + repo.read(cx) + .branch + .as_ref() + .map(|branch| SharedString::from(branch.name().to_string())) + }) } pub fn init(cx: &mut App) { diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index c5232a67189949e703a282f9894c71302c2f223a..50d3db65b94040c494b369932a1ac05afc57314a 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -512,14 +512,15 @@ impl TitleBar { let git_store = project.git_store().read(cx); let worktree_path = worktree.read(cx).abs_path(); - for repo in git_store.repositories().values() { - let repo_path = &repo.read(cx).work_directory_abs_path; - if worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref()) { - return Some(repo.clone()); - } - } - - None + git_store + .repositories() + .values() + .filter(|repo| { + let repo_path = &repo.read(cx).work_directory_abs_path; + worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref()) + }) + .max_by_key(|repo| repo.read(cx).work_directory_abs_path.as_os_str().len()) + .cloned() } fn render_remote_project_connection(&self, cx: &mut Context) -> Option {