diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 05eed8f45eea7116c9f2336d624e0b57a68c4b92..0e4ea56ccfa1bcbd6534109bc439f35ba4c9b6ec 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -25,7 +25,7 @@ use util::ResultExt; use workspace::notifications::DetachAndPromptErr; use workspace::{ModalView, Workspace}; -use crate::{branch_picker, git_panel::show_error_toast}; +use crate::{branch_picker, git_panel::show_error_toast, resolve_active_repository}; actions!( branch_picker, @@ -62,33 +62,7 @@ pub fn open( cx: &mut Context, ) { let workspace_handle = workspace.weak_handle(); - let project = workspace.project().clone(); - - // Check if there's a worktree override from the project dropdown. - // This ensures the branch picker shows branches for the project the user - // explicitly selected in the title bar, not just the focused file's project. - // This is only relevant if for multi-projects workspaces. - let repository = workspace - .active_worktree_override() - .and_then(|override_id| { - let project_ref = project.read(cx); - project_ref - .worktree_for_id(override_id, cx) - .and_then(|worktree| { - let worktree_abs_path = worktree.read(cx).abs_path(); - let git_store = project_ref.git_store().read(cx); - git_store - .repositories() - .values() - .find(|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()) - }) - .cloned() - }) - }) - .or_else(|| project.read(cx).active_repository(cx)); + let repository = resolve_active_repository(workspace, cx); workspace.toggle_modal(window, cx, |window, cx| { BranchList::new( diff --git a/crates/git_ui/src/git_picker.rs b/crates/git_ui/src/git_picker.rs index 2371e74605d31c5066ffafcd26292814ec4ae9b9..82ef9c9516b7c145edbf26d6c5b8927189525cab 100644 --- a/crates/git_ui/src/git_picker.rs +++ b/crates/git_ui/src/git_picker.rs @@ -568,33 +568,7 @@ fn open_with_tab( cx: &mut Context, ) { let workspace_handle = workspace.weak_handle(); - let project = workspace.project().clone(); - - // Check if there's a worktree override from the project dropdown. - // This ensures the git picker shows info for the project the user - // explicitly selected in the title bar, not just the focused file's project. - // This is only relevant if for multi-projects workspaces. - let repository = workspace - .active_worktree_override() - .and_then(|override_id| { - let project_ref = project.read(cx); - project_ref - .worktree_for_id(override_id, cx) - .and_then(|worktree| { - let worktree_abs_path = worktree.read(cx).abs_path(); - let git_store = project_ref.git_store().read(cx); - git_store - .repositories() - .values() - .find(|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()) - }) - .cloned() - }) - }) - .or_else(|| project.read(cx).active_repository(cx)); + let repository = crate::resolve_active_repository(workspace, cx); workspace.toggle_modal(window, cx, |window, cx| { GitPicker::new(workspace_handle, repository, tab, rems(34.), window, cx) diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index a5c0278aa6c6799470e285a67364bd0fd3aa2b45..f767d5a6d801ebf7fd1aa2cbebd5f2dd765d1ca6 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -4,6 +4,7 @@ use anyhow::anyhow; use command_palette_hooks::CommandPaletteFilter; use commit_modal::CommitModal; use editor::{Editor, actions::DiffClipboardWithSelectionData}; + use project::ProjectPath; use ui::{ Headline, HeadlineSize, Icon, IconName, IconSize, IntoElement, ParentElement, Render, Styled, @@ -308,6 +309,32 @@ fn open_modified_files( } } +/// Resolves the repository for git operations, respecting the workspace's +/// active worktree override from the project dropdown. +pub fn resolve_active_repository(workspace: &Workspace, cx: &App) -> Option> { + let project = workspace.project().read(cx); + workspace + .active_worktree_override() + .and_then(|override_id| { + project + .worktree_for_id(override_id, cx) + .and_then(|worktree| { + let worktree_abs_path = worktree.read(cx).abs_path(); + let git_store = project.git_store().read(cx); + git_store + .repositories() + .values() + .find(|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()) + }) + .cloned() + }) + }) + .or_else(|| project.active_repository(cx)) +} + pub fn git_status_icon(status: FileStatus) -> impl IntoElement { GitStatusIcon::new(status) } diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 23e948aeaa85779ccf47d5f402b8de27c59d4e5b..90eeb853f2e561bdcd7cb30402cbe80400ed74f7 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -3,6 +3,7 @@ use crate::{ git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings, remote_button::{render_publish_button, render_push_button}, + resolve_active_repository, }; use anyhow::{Context as _, Result, anyhow}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; @@ -154,6 +155,8 @@ impl ProjectDiff { "Action" } ); + let intended_repo = resolve_active_repository(workspace, cx); + let existing = workspace .items_of_type::(cx) .find(|item| matches!(item.read(cx).diff_base(cx), DiffBase::Head)); @@ -177,6 +180,23 @@ impl ProjectDiff { ); project_diff }; + + if let Some(intended) = &intended_repo { + let needs_switch = project_diff + .read(cx) + .branch_diff + .read(cx) + .repo() + .map_or(true, |current| current.read(cx).id != intended.read(cx).id); + if needs_switch { + project_diff.update(cx, |project_diff, cx| { + project_diff.branch_diff.update(cx, |branch_diff, cx| { + branch_diff.set_repo(Some(intended.clone()), cx); + }); + }); + } + } + if let Some(entry) = entry { project_diff.update(cx, |project_diff, cx| { project_diff.move_to_entry(entry, window, cx); @@ -2619,4 +2639,92 @@ mod tests { cx.assert_excerpts_with_selections("[EXCERPT]\nˇ# My cool project\nDetails to come.\n"); } + + #[gpui::test] + async fn test_deploy_at_respects_worktree_override(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/project_a"), + json!({ + ".git": {}, + "a.txt": "CHANGED_A\n", + }), + ) + .await; + fs.insert_tree( + path!("/project_b"), + json!({ + ".git": {}, + "b.txt": "CHANGED_B\n", + }), + ) + .await; + + fs.set_head_and_index_for_repo( + Path::new(path!("/project_a/.git")), + &[("a.txt", "original_a\n".to_string())], + ); + fs.set_head_and_index_for_repo( + Path::new(path!("/project_b/.git")), + &[("b.txt", "original_b\n".to_string())], + ); + + let project = Project::test( + fs.clone(), + [ + Path::new(path!("/project_a")), + Path::new(path!("/project_b")), + ], + cx, + ) + .await; + + let (worktree_a_id, worktree_b_id) = project.read_with(cx, |project, cx| { + let mut worktrees: Vec<_> = project.worktrees(cx).collect(); + worktrees.sort_by_key(|w| w.read(cx).abs_path()); + (worktrees[0].read(cx).id(), worktrees[1].read(cx).id()) + }); + + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + cx.run_until_parked(); + + // Select project A via the dropdown override and open the diff. + workspace.update(cx, |workspace, cx| { + workspace.set_active_worktree_override(Some(worktree_a_id), cx); + }); + cx.focus(&workspace); + cx.update(|window, cx| { + window.dispatch_action(project_diff::Diff.boxed_clone(), cx); + }); + cx.run_until_parked(); + + let diff_item = workspace.update(cx, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + let paths_a = diff_item.read_with(cx, |diff, cx| diff.excerpt_paths(cx)); + assert_eq!(paths_a.len(), 1); + assert_eq!(*paths_a[0], *"a.txt"); + + // Switch the override to project B and re-run the diff action. + workspace.update(cx, |workspace, cx| { + workspace.set_active_worktree_override(Some(worktree_b_id), cx); + }); + cx.focus(&workspace); + cx.update(|window, cx| { + window.dispatch_action(project_diff::Diff.boxed_clone(), cx); + }); + cx.run_until_parked(); + + let same_diff_item = workspace.update(cx, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + assert_eq!(diff_item.entity_id(), same_diff_item.entity_id()); + + let paths_b = diff_item.read_with(cx, |diff, cx| diff.excerpt_paths(cx)); + assert_eq!(paths_b.len(), 1); + assert_eq!(*paths_b[0], *"b.txt"); + } } diff --git a/crates/project/src/git_store/branch_diff.rs b/crates/project/src/git_store/branch_diff.rs index 88f510f68402f91f864cf7777e8f4c9b3392d632..3b8324fce8ffea7049838aeac09e831463dbd34e 100644 --- a/crates/project/src/git_store/branch_diff.rs +++ b/crates/project/src/git_store/branch_diff.rs @@ -110,6 +110,15 @@ impl BranchDiff { &self.diff_base } + pub fn set_repo(&mut self, repo: Option>, cx: &mut Context) { + self.repo = repo; + self.tree_diff = None; + self.base_commit = None; + self.head_commit = None; + cx.emit(BranchDiffEvent::FileListChanged); + *self.update_needed.borrow_mut() = (); + } + pub async fn handle_status_updates( this: WeakEntity, mut recv: postage::watch::Receiver<()>,