From 090c38d872cf1e120749a49957154bd188a76090 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 5 Mar 2025 13:16:46 -0800 Subject: [PATCH] Fix git branches in non-active repository (#26148) Release Notes: - Git Beta: Fixed a bug where the branch selector would only show for the first repository opened. --------- Co-authored-by: Conrad Irwin Co-authored-by: Richard Feldman --- crates/collab/src/rpc.rs | 4 +- crates/collab/src/tests/integration_tests.rs | 38 ++-- .../remote_editing_collaboration_tests.rs | 31 ++- crates/git_ui/src/branch_picker.rs | 95 +++++---- crates/git_ui/src/commit_modal.rs | 8 +- crates/git_ui/src/git_panel.rs | 7 +- crates/project/src/git.rs | 144 +++++++++++++ crates/project/src/project.rs | 17 +- crates/project/src/worktree_store.rs | 199 +----------------- crates/proto/proto/zed.proto | 28 ++- crates/proto/src/proto.rs | 12 +- .../remote_server/src/remote_editing_tests.rs | 34 +-- 12 files changed, 317 insertions(+), 300 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 3538c601a932b43616fb1f1ef6e5dccd78ae2ec0..042364ac84ae830a7ef188ec6de1135dcff7dbbe 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -308,7 +308,7 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) - .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler( @@ -405,6 +405,8 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) .add_message_handler(broadcast_project_message_from_host::) .add_message_handler(update_context) .add_request_handler({ diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 18777fd3db001f772220585d1ee9007fc8d9e4fe..4e99b67dbaca57b59db8549e43f721904c13bec8 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -6741,19 +6741,24 @@ async fn test_remote_git_branches( .collect::>(); let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; + let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); let project_b = client_b.join_remote_project(project_id, cx_b).await; - let root_path = ProjectPath::root_path(worktree_id); - // Client A sees that a guest has joined. + // Client A sees that a guest has joined and the repo has been populated executor.run_until_parked(); + let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap()); + + let root_path = ProjectPath::root_path(worktree_id); + let branches_b = cx_b - .update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx))) + .update(|cx| repo_b.update(cx, |repository, _| repository.branches())) .await + .unwrap() .unwrap(); let new_branch = branches[2]; @@ -6765,13 +6770,10 @@ async fn test_remote_git_branches( assert_eq!(branches_b, branches_set); - cx_b.update(|cx| { - project_b.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx) - }) - }) - .await - .unwrap(); + cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string())) + .await + .unwrap() + .unwrap(); executor.run_until_parked(); @@ -6789,11 +6791,21 @@ async fn test_remote_git_branches( // Also try creating a new branch cx_b.update(|cx| { - project_b.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx) - }) + repo_b + .read(cx) + .create_branch("totally-new-branch".to_string()) + }) + .await + .unwrap() + .unwrap(); + + cx_b.update(|cx| { + repo_b + .read(cx) + .change_branch("totally-new-branch".to_string()) }) .await + .unwrap() .unwrap(); executor.run_until_parked(); diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index ec132a0c07c2fee047591af3995ef694942d88cb..fbc25cbf03a2975cfd5bf12b92c091e37636a90a 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -276,11 +276,13 @@ async fn test_ssh_collaboration_git_branches( // has some git repositories executor.run_until_parked(); + let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap()); let root_path = ProjectPath::root_path(worktree_id); let branches_b = cx_b - .update(|cx| project_b.update(cx, |project, cx| project.branches(root_path.clone(), cx))) + .update(|cx| repo_b.read(cx).branches()) .await + .unwrap() .unwrap(); let new_branch = branches[2]; @@ -292,13 +294,10 @@ async fn test_ssh_collaboration_git_branches( assert_eq!(&branches_b, &branches_set); - cx_b.update(|cx| { - project_b.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx) - }) - }) - .await - .unwrap(); + cx_b.update(|cx| repo_b.read(cx).change_branch(new_branch.to_string())) + .await + .unwrap() + .unwrap(); executor.run_until_parked(); @@ -318,11 +317,21 @@ async fn test_ssh_collaboration_git_branches( // Also try creating a new branch cx_b.update(|cx| { - project_b.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx) - }) + repo_b + .read(cx) + .create_branch("totally-new-branch".to_string()) + }) + .await + .unwrap() + .unwrap(); + + cx_b.update(|cx| { + repo_b + .read(cx) + .change_branch("totally-new-branch".to_string()) }) .await + .unwrap() .unwrap(); executor.run_until_parked(); diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 317928356da1516e9570e1b3de5218e034d15539..bd98c5af0d137e38fe663d0b948f8b8c338a56f7 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -1,4 +1,4 @@ -use anyhow::Context as _; +use anyhow::{anyhow, Context as _}; use fuzzy::{StringMatch, StringMatchCandidate}; use git::repository::Branch; @@ -8,7 +8,7 @@ use gpui::{ Task, Window, }; use picker::{Picker, PickerDelegate}; -use project::{Project, ProjectPath}; +use project::git::Repository; use std::sync::Arc; use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle}; use util::ResultExt; @@ -28,16 +28,20 @@ pub fn open( window: &mut Window, cx: &mut Context, ) { - let project = workspace.project().clone(); + let repository = workspace.project().read(cx).active_repository(cx).clone(); let style = BranchListStyle::Modal; workspace.toggle_modal(window, cx, |window, cx| { - BranchList::new(project, style, 34., window, cx) + BranchList::new(repository, style, 34., window, cx) }) } -pub fn popover(project: Entity, window: &mut Window, cx: &mut App) -> Entity { +pub fn popover( + repository: Option>, + window: &mut Window, + cx: &mut App, +) -> Entity { cx.new(|cx| { - let list = BranchList::new(project, BranchListStyle::Popover, 15., window, cx); + let list = BranchList::new(repository, BranchListStyle::Popover, 15., window, cx); list.focus_handle(cx).focus(window); list }) @@ -58,22 +62,21 @@ pub struct BranchList { impl BranchList { fn new( - project_handle: Entity, + repository: Option>, style: BranchListStyle, rem_width: f32, window: &mut Window, cx: &mut Context, ) -> Self { let popover_handle = PopoverMenuHandle::default(); - let project = project_handle.read(cx); - let all_branches_request = project - .visible_worktrees(cx) - .next() - .map(|worktree| project.branches(ProjectPath::root_path(worktree.read(cx).id()), cx)) - .context("No worktrees found"); + let all_branches_request = repository + .clone() + .map(|repository| repository.read(cx).branches()); cx.spawn_in(window, |this, mut cx| async move { - let all_branches = all_branches_request?.await?; + let all_branches = all_branches_request + .context("No active repository")? + .await??; this.update_in(&mut cx, |this, window, cx| { this.picker.update(cx, |picker, cx| { @@ -86,7 +89,7 @@ impl BranchList { }) .detach_and_log_err(cx); - let delegate = BranchListDelegate::new(project_handle.clone(), style, 20); + let delegate = BranchListDelegate::new(repository.clone(), style, 20); let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx)); let _subscription = cx.subscribe(&picker, |_, _, _, cx| { @@ -145,7 +148,7 @@ impl BranchEntry { pub struct BranchListDelegate { matches: Vec, all_branches: Option>, - project: Entity, + repo: Option>, style: BranchListStyle, selected_index: usize, last_query: String, @@ -155,13 +158,13 @@ pub struct BranchListDelegate { impl BranchListDelegate { fn new( - project: Entity, + repo: Option>, style: BranchListStyle, branch_name_trailoff_after: usize, ) -> Self { Self { matches: vec![], - project, + repo, style, all_branches: None, selected_index: 0, @@ -280,14 +283,16 @@ impl PickerDelegate for BranchListDelegate { return; }; - let current_branch = self.project.update(cx, |project, cx| { - project - .active_repository(cx) - .and_then(|repo| repo.read(cx).current_branch()) - .map(|branch| branch.name.to_string()) + let current_branch = self.repo.as_ref().map(|repo| { + repo.update(cx, |repo, _| { + repo.current_branch().map(|branch| branch.name.clone()) + }) }); - if current_branch == Some(branch.name().to_string()) { + if current_branch + .flatten() + .is_some_and(|current_branch| current_branch == branch.name()) + { cx.emit(DismissEvent); return; } @@ -296,19 +301,33 @@ impl PickerDelegate for BranchListDelegate { let branch = branch.clone(); |picker, mut cx| async move { let branch_change_task = picker.update(&mut cx, |this, cx| { - let project = this.delegate.project.read(cx); - let branch_to_checkout = match branch { - BranchEntry::Branch(branch) => branch.string, - BranchEntry::History(string) => string, - BranchEntry::NewBranch { name: branch_name } => branch_name, - }; - let worktree = project - .visible_worktrees(cx) - .next() - .context("worktree disappeared")?; - let repository = ProjectPath::root_path(worktree.read(cx).id()); - - anyhow::Ok(project.update_or_create_branch(repository, branch_to_checkout, cx)) + let repo = this + .delegate + .repo + .as_ref() + .ok_or_else(|| anyhow!("No active repository"))? + .clone(); + + let cx = cx.to_async(); + + anyhow::Ok(async move { + match branch { + BranchEntry::Branch(StringMatch { + string: branch_name, + .. + }) + | BranchEntry::History(branch_name) => { + cx.update(|cx| repo.read(cx).change_branch(branch_name))? + .await? + } + BranchEntry::NewBranch { name: branch_name } => { + cx.update(|cx| repo.read(cx).create_branch(branch_name.clone()))? + .await??; + cx.update(|cx| repo.read(cx).change_branch(branch_name))? + .await? + } + } + }) })??; branch_change_task.await?; @@ -316,7 +335,7 @@ impl PickerDelegate for BranchListDelegate { picker.update(&mut cx, |_, cx| { cx.emit(DismissEvent); - Ok::<(), anyhow::Error>(()) + anyhow::Ok(()) }) } }) diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index 6c1543ec53cf46c56d31ad7f37ec93c528ccdc0d..0cd76049e654c6bda29e9efdc5ccf2790c5a9e18 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/crates/git_ui/src/commit_modal.rs @@ -4,7 +4,6 @@ use crate::branch_picker::{self, BranchList}; use crate::git_panel::{commit_message_editor, GitPanel}; use git::{Commit, ShowCommitEditor}; use panel::{panel_button, panel_editor_style, panel_filled_button}; -use project::Project; use ui::{prelude::*, KeybindingHint, PopoverMenu, Tooltip}; use editor::{Editor, EditorElement}; @@ -129,10 +128,9 @@ impl CommitModal { active_index, }; - let project = workspace.project().clone(); workspace.open_panel::(window, cx); workspace.toggle_modal(window, cx, move |window, cx| { - CommitModal::new(git_panel, restore_dock_position, project, window, cx) + CommitModal::new(git_panel, restore_dock_position, window, cx) }) }); } @@ -140,11 +138,11 @@ impl CommitModal { fn new( git_panel: Entity, restore_dock: RestoreDock, - project: Entity, window: &mut Window, cx: &mut Context, ) -> Self { let panel = git_panel.read(cx); + let active_repository = panel.active_repository.clone(); let suggested_commit_message = panel.suggest_commit_message(); let commit_editor = git_panel.update(cx, |git_panel, cx| { @@ -188,7 +186,7 @@ impl CommitModal { let properties = ModalContainerProperties::new(window, 50); Self { - branch_list: branch_picker::popover(project.clone(), window, cx), + branch_list: branch_picker::popover(active_repository.clone(), window, cx), git_panel, commit_editor, restore_dock, diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 3d3952732816b56cff66d22eb8308acfc2d32eb7..2f62c22e65e329c41e9f88a19b8c36a0b3dd9e06 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3327,6 +3327,11 @@ impl RenderOnce for PanelRepoFooter { .as_ref() .map(|panel| panel.read(cx).project.clone()); + let repo = self + .git_panel + .as_ref() + .and_then(|panel| panel.read(cx).active_repository.clone()); + let single_repo = project .as_ref() .map(|project| project.read(cx).all_repositories(cx).len() == 1) @@ -3366,7 +3371,7 @@ impl RenderOnce for PanelRepoFooter { }); let branch_selector = PopoverMenu::new("popover-button") - .menu(move |window, cx| Some(branch_picker::popover(project.clone()?, window, cx))) + .menu(move |window, cx| Some(branch_picker::popover(repo.clone(), window, cx))) .trigger_with_tooltip( branch_selector_button, Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch), diff --git a/crates/project/src/git.rs b/crates/project/src/git.rs index 7263027179e823146ca64ca967048abe02070d18..d5d266ac9355b7c9ccd9980fbafa8e5a4b5418ea 100644 --- a/crates/project/src/git.rs +++ b/crates/project/src/git.rs @@ -96,6 +96,9 @@ impl GitStore { pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_get_remotes); + client.add_entity_request_handler(Self::handle_get_branches); + client.add_entity_request_handler(Self::handle_change_branch); + client.add_entity_request_handler(Self::handle_create_branch); client.add_entity_request_handler(Self::handle_push); client.add_entity_request_handler(Self::handle_pull); client.add_entity_request_handler(Self::handle_fetch); @@ -459,6 +462,67 @@ impl GitStore { }) } + async fn handle_get_branches( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); + let repository_handle = + Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; + + let branches = repository_handle + .update(&mut cx, |repository_handle, _| repository_handle.branches())? + .await??; + + Ok(proto::GitBranchesResponse { + branches: branches + .into_iter() + .map(|branch| worktree::branch_to_proto(&branch)) + .collect::>(), + }) + } + async fn handle_create_branch( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); + let repository_handle = + Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; + let branch_name = envelope.payload.branch_name; + + repository_handle + .update(&mut cx, |repository_handle, _| { + repository_handle.create_branch(branch_name) + })? + .await??; + + Ok(proto::Ack {}) + } + + async fn handle_change_branch( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); + let repository_handle = + Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; + let branch_name = envelope.payload.branch_name; + + repository_handle + .update(&mut cx, |repository_handle, _| { + repository_handle.change_branch(branch_name) + })? + .await??; + + Ok(proto::Ack {}) + } + async fn handle_show( this: Entity, envelope: TypedEnvelope, @@ -1279,4 +1343,84 @@ impl Repository { } }) } + + pub fn branches(&self) -> oneshot::Receiver>> { + self.send_job(|repo| async move { + match repo { + GitRepo::Local(git_repository) => git_repository.branches(), + GitRepo::Remote { + project_id, + client, + worktree_id, + work_directory_id, + } => { + let response = client + .request(proto::GitGetBranches { + project_id: project_id.0, + worktree_id: worktree_id.to_proto(), + work_directory_id: work_directory_id.to_proto(), + }) + .await?; + + let branches = response + .branches + .into_iter() + .map(|branch| worktree::proto_to_branch(&branch)) + .collect(); + + Ok(branches) + } + } + }) + } + + pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver> { + self.send_job(|repo| async move { + match repo { + GitRepo::Local(git_repository) => git_repository.create_branch(&branch_name), + GitRepo::Remote { + project_id, + client, + worktree_id, + work_directory_id, + } => { + client + .request(proto::GitCreateBranch { + project_id: project_id.0, + worktree_id: worktree_id.to_proto(), + work_directory_id: work_directory_id.to_proto(), + branch_name, + }) + .await?; + + Ok(()) + } + } + }) + } + + pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver> { + self.send_job(|repo| async move { + match repo { + GitRepo::Local(git_repository) => git_repository.change_branch(&branch_name), + GitRepo::Remote { + project_id, + client, + worktree_id, + work_directory_id, + } => { + client + .request(proto::GitChangeBranch { + project_id: project_id.0, + worktree_id: worktree_id.to_proto(), + work_directory_id: work_directory_id.to_proto(), + branch_name, + }) + .await?; + + Ok(()) + } + } + }) + } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b61466747bdfb99c1177a45b5017cc6ae89d9fdb..252ef2493e2923b0cadd6148179975c7c14bcf0a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -48,7 +48,7 @@ use image_store::{ImageItemEvent, ImageStoreEvent}; use ::git::{ blame::Blame, - repository::{Branch, GitRepository, RepoPath}, + repository::{GitRepository, RepoPath}, status::FileStatus, }; use gpui::{ @@ -3655,21 +3655,6 @@ impl Project { worktree.get_local_repo(&root_entry)?.repo().clone().into() } - pub fn branches(&self, project_path: ProjectPath, cx: &App) -> Task>> { - self.worktree_store().read(cx).branches(project_path, cx) - } - - pub fn update_or_create_branch( - &self, - repository: ProjectPath, - new_branch: String, - cx: &App, - ) -> Task> { - self.worktree_store() - .read(cx) - .update_or_create_branch(repository, new_branch, cx) - } - pub fn blame_buffer( &self, buffer: &Entity, diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index dc283db3a3c76fe46cfe5753399ffcd702d5c0ec..800d0e68e737d16cd404b30119b8f81e18462592 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -27,10 +27,7 @@ use smol::{ }; use text::ReplicaId; use util::{paths::SanitizedPath, ResultExt}; -use worktree::{ - branch_to_proto, Entry, ProjectEntryId, UpdatedEntriesSet, Worktree, WorktreeId, - WorktreeSettings, -}; +use worktree::{Entry, ProjectEntryId, UpdatedEntriesSet, Worktree, WorktreeId, WorktreeSettings}; use crate::{search::SearchQuery, ProjectPath}; @@ -83,8 +80,6 @@ impl WorktreeStore { client.add_entity_request_handler(Self::handle_delete_project_entry); client.add_entity_request_handler(Self::handle_expand_project_entry); client.add_entity_request_handler(Self::handle_expand_all_for_project_entry); - client.add_entity_request_handler(Self::handle_git_branches); - client.add_entity_request_handler(Self::handle_update_branch); } pub fn local(retain_worktrees: bool, fs: Arc) -> Self { @@ -890,150 +885,6 @@ impl WorktreeStore { Ok(()) } - pub fn branches( - &self, - project_path: ProjectPath, - cx: &App, - ) -> Task>> { - let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) else { - return Task::ready(Err(anyhow!("No worktree found for ProjectPath"))); - }; - - match worktree.read(cx) { - Worktree::Local(local_worktree) => { - let branches = util::maybe!({ - let worktree_error = |error| { - format!( - "{} for worktree {}", - error, - local_worktree.abs_path().to_string_lossy() - ) - }; - - let entry = local_worktree - .git_entry(project_path.path) - .with_context(|| worktree_error("No git entry found"))?; - - let repo = local_worktree - .get_local_repo(&entry) - .with_context(|| worktree_error("No repository found"))? - .repo() - .clone(); - - repo.branches() - }); - - Task::ready(branches) - } - Worktree::Remote(remote_worktree) => { - let request = remote_worktree.client().request(proto::GitBranches { - project_id: remote_worktree.project_id(), - repository: Some(proto::ProjectPath { - worktree_id: project_path.worktree_id.to_proto(), - path: project_path.path.to_proto(), // Root path - }), - }); - - cx.background_spawn(async move { - let response = request.await?; - - let branches = response - .branches - .into_iter() - .map(|proto_branch| git::repository::Branch { - is_head: proto_branch.is_head, - name: proto_branch.name.into(), - upstream: proto_branch.upstream.map(|upstream| { - git::repository::Upstream { - ref_name: upstream.ref_name.into(), - tracking: upstream - .tracking - .map(|tracking| { - git::repository::UpstreamTracking::Tracked( - git::repository::UpstreamTrackingStatus { - ahead: tracking.ahead as u32, - behind: tracking.behind as u32, - }, - ) - }) - .unwrap_or(git::repository::UpstreamTracking::Gone), - } - }), - most_recent_commit: proto_branch.most_recent_commit.map(|commit| { - git::repository::CommitSummary { - sha: commit.sha.into(), - subject: commit.subject.into(), - commit_timestamp: commit.commit_timestamp, - } - }), - }) - .collect(); - - Ok(branches) - }) - } - } - } - - pub fn update_or_create_branch( - &self, - repository: ProjectPath, - new_branch: String, - cx: &App, - ) -> Task> { - let Some(worktree) = self.worktree_for_id(repository.worktree_id, cx) else { - return Task::ready(Err(anyhow!("No worktree found for ProjectPath"))); - }; - - match worktree.read(cx) { - Worktree::Local(local_worktree) => { - let result = util::maybe!({ - let worktree_error = |error| { - format!( - "{} for worktree {}", - error, - local_worktree.abs_path().to_string_lossy() - ) - }; - - let entry = local_worktree - .git_entry(repository.path) - .with_context(|| worktree_error("No git entry found"))?; - - let repo = local_worktree - .get_local_repo(&entry) - .with_context(|| worktree_error("No repository found"))? - .repo() - .clone(); - - if !repo.branch_exits(&new_branch)? { - repo.create_branch(&new_branch)?; - } - - repo.change_branch(&new_branch)?; - Ok(()) - }); - - Task::ready(result) - } - Worktree::Remote(remote_worktree) => { - let request = remote_worktree.client().request(proto::UpdateGitBranch { - project_id: remote_worktree.project_id(), - repository: Some(proto::ProjectPath { - worktree_id: repository.worktree_id.to_proto(), - path: repository.path.to_proto(), // Root path - }), - branch_name: new_branch, - }); - - cx.background_spawn(async move { - request.await?; - Ok(()) - }) - } - } - } - async fn filter_paths( fs: &Arc, mut input: Receiver, @@ -1130,54 +981,6 @@ impl WorktreeStore { .ok_or_else(|| anyhow!("invalid request"))?; Worktree::handle_expand_all_for_entry(worktree, envelope.payload, cx).await } - - pub async fn handle_git_branches( - this: Entity, - branches: TypedEnvelope, - cx: AsyncApp, - ) -> Result { - let project_path = branches - .payload - .repository - .clone() - .context("Invalid GitBranches call")?; - let project_path = ProjectPath { - worktree_id: WorktreeId::from_proto(project_path.worktree_id), - path: Arc::::from_proto(project_path.path), - }; - - let branches = this - .read_with(&cx, |this, cx| this.branches(project_path, cx))? - .await?; - - Ok(proto::GitBranchesResponse { - branches: branches.iter().map(branch_to_proto).collect(), - }) - } - - pub async fn handle_update_branch( - this: Entity, - update_branch: TypedEnvelope, - cx: AsyncApp, - ) -> Result { - let project_path = update_branch - .payload - .repository - .clone() - .context("Invalid GitBranches call")?; - let project_path = ProjectPath { - worktree_id: WorktreeId::from_proto(project_path.worktree_id), - path: Arc::::from_proto(project_path.path), - }; - let new_branch = update_branch.payload.branch_name; - - this.read_with(&cx, |this, cx| { - this.update_or_create_branch(project_path, new_branch, cx) - })? - .await?; - - Ok(proto::Ack {}) - } } #[derive(Clone, Debug)] diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 9716fafcd45f828fdf8b6e439221eb7007610742..639583374f63b9abc2c64f096d8aa2988be89896 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -1,3 +1,4 @@ + syntax = "proto3"; package zed.messages; @@ -278,7 +279,6 @@ message Envelope { LanguageServerPromptRequest language_server_prompt_request = 268; LanguageServerPromptResponse language_server_prompt_response = 269; - GitBranches git_branches = 270; GitBranchesResponse git_branches_response = 271; UpdateGitBranch update_git_branch = 272; @@ -332,7 +332,10 @@ message Envelope { ApplyCodeActionKind apply_code_action_kind = 309; ApplyCodeActionKindResponse apply_code_action_kind_response = 310; - RemoteMessageResponse remote_message_response = 311; // current max + RemoteMessageResponse remote_message_response = 311; + GitGetBranches git_get_branches = 312; + GitCreateBranch git_create_branch = 313; + GitChangeBranch git_change_branch = 314; // current max } reserved 87 to 88; @@ -348,6 +351,7 @@ message Envelope { reserved 221; reserved 224 to 229; reserved 246; + reserved 270; reserved 247 to 254; reserved 255 to 256; } @@ -2841,3 +2845,23 @@ message RemoteMessageResponse { string stdout = 1; string stderr = 2; } + +message GitGetBranches { + uint64 project_id = 1; + uint64 worktree_id = 2; + uint64 work_directory_id = 3; +} + +message GitCreateBranch { + uint64 project_id = 1; + uint64 worktree_id = 2; + uint64 work_directory_id = 3; + string branch_name = 4; +} + +message GitChangeBranch { + uint64 project_id = 1; + uint64 worktree_id = 2; + uint64 work_directory_id = 3; + string branch_name = 4; +} diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index e2cd46c4b0d1d3adf10ba8a18ff86dbc532b931d..d887c6f83be7d68b00186c5206e9f3813de24dd0 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -424,7 +424,7 @@ messages!( (FlushBufferedMessages, Foreground), (LanguageServerPromptRequest, Foreground), (LanguageServerPromptResponse, Foreground), - (GitBranches, Background), + (GitGetBranches, Background), (GitBranchesResponse, Background), (UpdateGitBranch, Background), (ListToolchains, Foreground), @@ -452,6 +452,8 @@ messages!( (GetRemotesResponse, Background), (Pull, Background), (RemoteMessageResponse, Background), + (GitCreateBranch, Background), + (GitChangeBranch, Background), ); request_messages!( @@ -575,7 +577,7 @@ request_messages!( (GetPermalinkToLine, GetPermalinkToLineResponse), (FlushBufferedMessages, Ack), (LanguageServerPromptRequest, LanguageServerPromptResponse), - (GitBranches, GitBranchesResponse), + (GitGetBranches, GitBranchesResponse), (UpdateGitBranch, Ack), (ListToolchains, ListToolchainsResponse), (ActivateToolchain, Ack), @@ -594,6 +596,8 @@ request_messages!( (Fetch, RemoteMessageResponse), (GetRemotes, GetRemotesResponse), (Pull, RemoteMessageResponse), + (GitCreateBranch, Ack), + (GitChangeBranch, Ack), ); entity_messages!( @@ -679,7 +683,7 @@ entity_messages!( OpenServerSettings, GetPermalinkToLine, LanguageServerPromptRequest, - GitBranches, + GitGetBranches, UpdateGitBranch, ListToolchains, ActivateToolchain, @@ -695,6 +699,8 @@ entity_messages!( Fetch, GetRemotes, Pull, + GitChangeBranch, + GitCreateBranch, ); entity_messages!( diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 7cc6cea1dffdfcc8cb923997f719a6e9c5f73650..c45251c6a44ff13b55daf0e0e955ef4791d42bbe 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1328,9 +1328,12 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA // Give the worktree a bit of time to index the file system cx.run_until_parked(); - let remote_branches = project - .update(cx, |project, cx| project.branches(root_path.clone(), cx)) + let repository = project.update(cx, |project, cx| project.active_repository(cx).unwrap()); + + let remote_branches = repository + .update(cx, |repository, _| repository.branches()) .await + .unwrap() .unwrap(); let new_branch = branches[2]; @@ -1342,13 +1345,10 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA assert_eq!(&remote_branches, &branches_set); - cx.update(|cx| { - project.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), new_branch.to_string(), cx) - }) - }) - .await - .unwrap(); + cx.update(|cx| repository.read(cx).change_branch(new_branch.to_string())) + .await + .unwrap() + .unwrap(); cx.run_until_parked(); @@ -1368,11 +1368,21 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA // Also try creating a new branch cx.update(|cx| { - project.update(cx, |project, cx| { - project.update_or_create_branch(root_path.clone(), "totally-new-branch".to_string(), cx) - }) + repository + .read(cx) + .create_branch("totally-new-branch".to_string()) + }) + .await + .unwrap() + .unwrap(); + + cx.update(|cx| { + repository + .read(cx) + .change_branch("totally-new-branch".to_string()) }) .await + .unwrap() .unwrap(); cx.run_until_parked();