Worktree paths in git panel, take 2 (#26047)

Modified version of #25950. We still use worktree paths, but repo paths
with a status that lie outside the worktree are not excluded; instead,
we relativize them by adding `..`. This makes the list in the git panel
match what you'd get from running `git status` (with the repo's worktree
root as the working directory).

- [x] Implement + test new unrelativization logic
- [x] ~~When collecting repositories, dedup by .git abs path, so
worktrees can share a repo at the project level~~ dedup repos at the
repository selector layer, with repos coming from larger worktrees being
preferred
- [x] Open single-file worktree with diff when activating a path not in
the worktree

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-03-06 17:55:28 -05:00 committed by GitHub
parent 330e799293
commit 1763dd714b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 724 additions and 184 deletions

View file

@ -3,7 +3,10 @@ use gpui::{
};
use itertools::Itertools;
use picker::{Picker, PickerDelegate};
use project::{git::Repository, Project};
use project::{
git::{GitStore, Repository},
Project,
};
use std::sync::Arc;
use ui::{prelude::*, ListItem, ListItemSpacing};
@ -17,12 +20,14 @@ impl RepositorySelector {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let git_store = project_handle.read(cx).git_store().clone();
let repository_entries = git_store.update(cx, |git_store, cx| {
filtered_repository_entries(git_store, cx)
});
let project = project_handle.read(cx);
let git_store = project.git_store().clone();
let all_repositories = git_store.read(cx).all_repositories();
let filtered_repositories = all_repositories.clone();
let filtered_repositories = repository_entries.clone();
let widest_item_ix = all_repositories.iter().position_max_by(|a, b| {
let widest_item_ix = repository_entries.iter().position_max_by(|a, b| {
a.read(cx)
.display_name(project, cx)
.len()
@ -32,7 +37,7 @@ impl RepositorySelector {
let delegate = RepositorySelectorDelegate {
project: project_handle.downgrade(),
repository_selector: cx.entity().downgrade(),
repository_entries: all_repositories.clone(),
repository_entries,
filtered_repositories,
selected_index: 0,
};
@ -47,6 +52,35 @@ impl RepositorySelector {
}
}
pub(crate) fn filtered_repository_entries(
git_store: &GitStore,
cx: &App,
) -> Vec<Entity<Repository>> {
let mut repository_entries = git_store.all_repositories();
repository_entries.sort_by_key(|repo| {
let repo = repo.read(cx);
(
repo.dot_git_abs_path.clone(),
repo.worktree_abs_path.clone(),
)
});
// Remove any entry that comes from a single file worktree and represents a repository that is also represented by a non-single-file worktree.
repository_entries
.chunk_by(|a, b| a.read(cx).dot_git_abs_path == b.read(cx).dot_git_abs_path)
.flat_map(|chunk| {
let has_non_single_file_worktree = chunk
.iter()
.any(|repo| !repo.read(cx).is_from_single_file_worktree);
chunk
.iter()
.filter(move |repo| {
!repo.read(cx).is_from_single_file_worktree || !has_non_single_file_worktree
})
.cloned()
})
.collect()
}
impl EventEmitter<DismissEvent> for RepositorySelector {}
impl Focusable for RepositorySelector {