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 <conrad.irwin@gmail.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
This commit is contained in:
Mikayla Maki 2025-03-05 13:16:46 -08:00 committed by GitHub
parent 2a919ad1d0
commit 9d54e63a11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 316 additions and 299 deletions

View file

@ -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<Self>,
envelope: TypedEnvelope<proto::GitGetBranches>,
mut cx: AsyncApp,
) -> Result<proto::GitBranchesResponse> {
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::<Vec<_>>(),
})
}
async fn handle_create_branch(
this: Entity<Self>,
envelope: TypedEnvelope<proto::GitCreateBranch>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
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<Self>,
envelope: TypedEnvelope<proto::GitChangeBranch>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
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<Self>,
envelope: TypedEnvelope<proto::GitShow>,
@ -1279,4 +1343,84 @@ impl Repository {
}
})
}
pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
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<Result<()>> {
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<Result<()>> {
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(())
}
}
})
}
}

View file

@ -48,7 +48,7 @@ use image_store::{ImageItemEvent, ImageStoreEvent};
use ::git::{
blame::Blame,
repository::{Branch, GitRepository, RepoPath},
repository::{GitRepository, RepoPath},
status::FileStatus,
};
use gpui::{
@ -3705,21 +3705,6 @@ impl Project {
worktree.get_local_repo(&root_entry)?.repo().clone().into()
}
pub fn branches(&self, project_path: ProjectPath, cx: &App) -> Task<Result<Vec<Branch>>> {
self.worktree_store().read(cx).branches(project_path, cx)
}
pub fn update_or_create_branch(
&self,
repository: ProjectPath,
new_branch: String,
cx: &App,
) -> Task<Result<()>> {
self.worktree_store()
.read(cx)
.update_or_create_branch(repository, new_branch, cx)
}
pub fn blame_buffer(
&self,
buffer: &Entity<Buffer>,

View file

@ -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<dyn Fs>) -> Self {
@ -890,150 +885,6 @@ impl WorktreeStore {
Ok(())
}
pub fn branches(
&self,
project_path: ProjectPath,
cx: &App,
) -> Task<Result<Vec<git::repository::Branch>>> {
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<Result<()>> {
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<dyn Fs>,
mut input: Receiver<MatchingEntry>,
@ -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<Self>,
branches: TypedEnvelope<proto::GitBranches>,
cx: AsyncApp,
) -> Result<proto::GitBranchesResponse> {
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::<Path>::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<Self>,
update_branch: TypedEnvelope<proto::UpdateGitBranch>,
cx: AsyncApp,
) -> Result<proto::Ack> {
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::<Path>::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)]