From d5159be24e61663061441ed5abfbd647f469d140 Mon Sep 17 00:00:00 2001 From: "zed-zippy[bot]" <234243425+zed-zippy[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:26:05 +0000 Subject: [PATCH] Set active repository when picking a root folder in recent project menu (#53645) (cherry-pick to preview) (#53649) Cherry-pick of #53645 to preview ---- This changes the behavior of the recent project picker in multi-folder projects, so that when you select a folder that is a repository, it sets the active repository in the Git panel as well. Release Notes: - N/A Co-authored-by: Max Brunsfeld --- crates/git_ui/src/branch_picker.rs | 4 +- crates/git_ui/src/git_picker.rs | 2 +- crates/git_ui/src/git_ui.rs | 27 --------- crates/git_ui/src/project_diff.rs | 19 ++++--- crates/project/src/git_store.rs | 43 ++++++++++++-- crates/recent_projects/src/recent_projects.rs | 27 ++++----- crates/title_bar/src/title_bar.rs | 56 ++----------------- crates/workspace/src/workspace.rs | 23 -------- 8 files changed, 72 insertions(+), 129 deletions(-) diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index f46eb08ef9caf35b3e8fab1ce65c449f76ea2ed4..7269a14ab3c0931e71feb83673172b301c6f1087 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -22,7 +22,7 @@ use util::ResultExt; use workspace::notifications::DetachAndPromptErr; use workspace::{ModalView, Workspace}; -use crate::{branch_picker, git_panel::show_error_toast, resolve_active_repository}; +use crate::{branch_picker, git_panel::show_error_toast}; actions!( branch_picker, @@ -59,7 +59,7 @@ pub fn open( cx: &mut Context, ) { let workspace_handle = workspace.weak_handle(); - let repository = resolve_active_repository(workspace, cx); + let repository = workspace.project().read(cx).active_repository(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 bf9d122a7ec16b11c56fc45f59ff8c5f85f7fded..1a1ea84aaa16ba0a015d3079e4ff647e4d05c917 100644 --- a/crates/git_ui/src/git_picker.rs +++ b/crates/git_ui/src/git_picker.rs @@ -582,7 +582,7 @@ fn open_with_tab( cx: &mut Context, ) { let workspace_handle = workspace.weak_handle(); - let repository = crate::resolve_active_repository(workspace, cx); + let repository = workspace.project().read(cx).active_repository(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 7d73760e34d1b2923a247f71b04fc8b5218f380b..1e7391178d2473a173a1503b4f2c724191c06a60 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -281,33 +281,6 @@ 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() - .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()) - .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 8fa4680593a7565c84efd7503f6cf9d188d3be35..a0708cae36cafd733c711df5bbab93af508510c1 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -2,7 +2,6 @@ use crate::{ conflict_view::ConflictAddon, git_panel::{GitPanel, GitPanelAddon, GitStatusEntry}, git_panel_settings::GitPanelSettings, - resolve_active_repository, }; use agent_settings::AgentSettings; use anyhow::{Context as _, Result, anyhow}; @@ -205,7 +204,7 @@ impl ProjectDiff { "Action" } ); - let intended_repo = resolve_active_repository(workspace, cx); + let intended_repo = workspace.project().read(cx).active_repository(cx); let existing = workspace .items_of_type::(cx) @@ -2708,7 +2707,7 @@ mod tests { } #[gpui::test] - async fn test_deploy_at_respects_worktree_override(cx: &mut TestAppContext) { + async fn test_deploy_at_respects_active_repository_selection(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); @@ -2759,9 +2758,12 @@ mod tests { let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); cx.run_until_parked(); - // Select project A via the dropdown override and open the diff. + // Select project A explicitly and open the diff. workspace.update(cx, |workspace, cx| { - workspace.set_active_worktree_override(Some(worktree_a_id), cx); + let git_store = workspace.project().read(cx).git_store().clone(); + git_store.update(cx, |git_store, cx| { + git_store.set_active_repo_for_worktree(worktree_a_id, cx); + }); }); cx.focus(&workspace); cx.update(|window, cx| { @@ -2776,9 +2778,12 @@ mod tests { 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. + // Switch the explicit active repository to project B and re-run the diff action. workspace.update(cx, |workspace, cx| { - workspace.set_active_worktree_override(Some(worktree_b_id), cx); + let git_store = workspace.project().read(cx).git_store().clone(); + git_store.update(cx, |git_store, cx| { + git_store.set_active_repo_for_worktree(worktree_b_id, cx); + }); }); cx.focus(&workspace); cx.update(|window, cx| { diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index a9e516fadc148757fad85f7243a5aaefb78bc16e..3a5522a60188bd2fd89003a6f484fb80e70c5405 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -594,16 +594,49 @@ impl GitStore { pub fn is_local(&self) -> bool { matches!(self.state, GitStoreState::Local { .. }) } + + fn set_active_repo_id(&mut self, repo_id: RepositoryId, cx: &mut Context) { + if self.active_repo_id != Some(repo_id) { + self.active_repo_id = Some(repo_id); + cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(repo_id))); + } + } + pub fn set_active_repo_for_path(&mut self, project_path: &ProjectPath, cx: &mut Context) { if let Some((repo, _)) = self.repository_and_path_for_project_path(project_path, cx) { - let id = repo.read(cx).id; - if self.active_repo_id != Some(id) { - self.active_repo_id = Some(id); - cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id))); - } + self.set_active_repo_id(repo.read(cx).id, cx); } } + pub fn set_active_repo_for_worktree( + &mut self, + worktree_id: WorktreeId, + cx: &mut Context, + ) { + let Some(worktree) = self + .worktree_store + .read(cx) + .worktree_for_id(worktree_id, cx) + else { + return; + }; + let worktree_abs_path = worktree.read(cx).abs_path(); + let Some(repo_id) = self + .repositories + .values() + .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()) + .map(|repo| repo.read(cx).id) + else { + return; + }; + + self.set_active_repo_id(repo_id, cx); + } + pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context) { match &mut self.state { GitStoreState::Remote { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 9bf496817103246abece6891ead8fd32196cef3b..4600500c5fda093d4258ba5d9b804faf9a499f19 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -155,22 +155,20 @@ fn get_open_folders(workspace: &Workspace, cx: &App) -> Vec { return Vec::new(); } - let active_worktree_id = workspace.active_worktree_override().or_else(|| { - if let Some(repo) = project.active_repository(cx) { - let repo = repo.read(cx); - let repo_path = &repo.work_directory_abs_path; - for worktree in project.visible_worktrees(cx) { - let worktree_path = worktree.read(cx).abs_path(); - if worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref()) { - return Some(worktree.read(cx).id()); - } - } - } + let active_worktree_id = if let Some(repo) = project.active_repository(cx) { + let repo = repo.read(cx); + let repo_path = &repo.work_directory_abs_path; + project.visible_worktrees(cx).find_map(|worktree| { + let worktree_path = worktree.read(cx).abs_path(); + (worktree_path == *repo_path || worktree_path.starts_with(repo_path.as_ref())) + .then(|| worktree.read(cx).id()) + }) + } else { project .visible_worktrees(cx) .next() .map(|wt| wt.read(cx).id()) - }); + }; let git_store = project.git_store().read(cx); let repositories: Vec<_> = git_store.repositories().values().cloned().collect(); @@ -1091,7 +1089,10 @@ impl PickerDelegate for RecentProjectsDelegate { let worktree_id = folder.worktree_id; if let Some(workspace) = self.workspace.upgrade() { workspace.update(cx, |workspace, cx| { - workspace.set_active_worktree_override(Some(worktree_id), cx); + let git_store = workspace.project().read(cx).git_store().clone(); + git_store.update(cx, |git_store, cx| { + git_store.set_active_repo_for_worktree(worktree_id, cx); + }); }); } cx.emit(DismissEvent); diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 164cefc296e0a618e8698f1da5e387b84648ff96..ce0ec76e485dff42db0c730c841bcbd396e280ea 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -36,7 +36,6 @@ use onboarding_banner::OnboardingBanner; use project::{Project, git_store::GitStoreEvent, trusted_worktrees::TrustedWorktrees}; use remote::RemoteConnectionOptions; use settings::Settings; -use settings::WorktreeId; use std::sync::Arc; use std::time::Duration; @@ -383,27 +382,13 @@ impl TitleBar { cx.notify() }), ); - subscriptions.push( - cx.subscribe(&project, |this, _, event: &project::Event, cx| { - if let project::Event::BufferEdited = event { - // Clear override when user types in any editor, - // so the title bar reflects the project they're actually working in - this.clear_active_worktree_override(cx); - cx.notify(); - } - }), - ); + subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx))); subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed)); subscriptions.push( - cx.subscribe(&git_store, move |this, _, event, cx| match event { - GitStoreEvent::ActiveRepositoryChanged(_) => { - // Clear override when focus-derived active repo changes - // (meaning the user focused a file from a different project) - this.clear_active_worktree_override(cx); - cx.notify(); - } - GitStoreEvent::RepositoryUpdated(_, _, true) => { + cx.subscribe(&git_store, move |_, _, event, cx| match event { + GitStoreEvent::ActiveRepositoryChanged(_) + | GitStoreEvent::RepositoryUpdated(_, _, true) => { cx.notify(); } _ => {} @@ -457,20 +442,11 @@ impl TitleBar { } /// Returns the worktree to display in the title bar. - /// - If there's an override set on the workspace, use that (if still valid) - /// - Otherwise, derive from the active repository + /// - Prefer the worktree owning the project's active repository /// - Fall back to the first visible worktree pub fn effective_active_worktree(&self, cx: &App) -> Option> { let project = self.project.read(cx); - if let Some(workspace) = self.workspace.upgrade() { - if let Some(override_id) = workspace.read(cx).active_worktree_override() { - if let Some(worktree) = project.worktree_for_id(override_id, cx) { - return Some(worktree); - } - } - } - if let Some(repo) = project.active_repository(cx) { let repo = repo.read(cx); let repo_path = &repo.work_directory_abs_path; @@ -486,28 +462,6 @@ impl TitleBar { project.visible_worktrees(cx).next() } - pub fn set_active_worktree_override( - &mut self, - worktree_id: WorktreeId, - cx: &mut Context, - ) { - if let Some(workspace) = self.workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - workspace.set_active_worktree_override(Some(worktree_id), cx); - }); - } - cx.notify(); - } - - fn clear_active_worktree_override(&mut self, cx: &mut Context) { - if let Some(workspace) = self.workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - workspace.clear_active_worktree_override(cx); - }); - } - cx.notify(); - } - fn get_repository_for_worktree( &self, worktree: &Entity, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3d1f67bf69fb818e6155782fdb4779365098cb20..15b9cced9322426761d66f43a96fa10a695ae130 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1325,7 +1325,6 @@ pub struct Workspace { bottom_dock: Entity, right_dock: Entity, panes: Vec>, - active_worktree_override: Option, panes_by_item: HashMap>, active_pane: Entity, last_active_center_pane: Option>, @@ -1758,7 +1757,6 @@ impl Workspace { modal_layer, toast_layer, titlebar_item: None, - active_worktree_override: None, notifications: Notifications::default(), suppressed_notifications: HashSet::default(), left_dock, @@ -2951,27 +2949,6 @@ impl Workspace { self.titlebar_item.clone() } - /// Returns the worktree override set by the user (e.g., via the project dropdown). - /// When set, git-related operations should use this worktree instead of deriving - /// the active worktree from the focused file. - pub fn active_worktree_override(&self) -> Option { - self.active_worktree_override - } - - pub fn set_active_worktree_override( - &mut self, - worktree_id: Option, - cx: &mut Context, - ) { - self.active_worktree_override = worktree_id; - cx.notify(); - } - - pub fn clear_active_worktree_override(&mut self, cx: &mut Context) { - self.active_worktree_override = None; - cx.notify(); - } - /// Call the given callback with a workspace whose project is local or remote via WSL (allowing host access). /// /// If the given workspace has a local project, then it will be passed