git: Add base branch support to create_branch (#42151)

Closes [#41674](https://github.com/zed-industries/zed/issues/41674)

Description:
Creating a branch from a base requires switching to the base branch
first, then creating the new branch and checking out to it, which
requires multiple operations.

Add base_branch parameter to create_branch to allow a new branch from a
base branch in one operation which is synonymous to the command `git
switch -c <new-branch> <base-branch>`.

Below is the video after solving the issue: 

(`master` branch is the default branch here, and I create a branch
`new-branch-2` based off the `master` branch. I also show the error
which used to appear before the fix.)

[Screencast from 2025-11-07
05-14-32.webm](https://github.com/user-attachments/assets/d37d1b58-af5f-44e8-b867-2aa5d4ef3d90)

Release Notes:

- Fixed the branch-picking error by replacing multiple sequential switch
operations with just one switch operation.

Signed-off-by: ayu-ch <ayu.chandekar@gmail.com>
This commit is contained in:
Ayush Chandekar
2025-11-09 21:35:29 -05:00
committed by GitHub
parent c60d31a726
commit e025ee6a11
7 changed files with 60 additions and 43 deletions
+1 -1
View File
@@ -7065,7 +7065,7 @@ async fn test_remote_git_branches(
// Also try creating a new branch
cx_b.update(|cx| {
repo_b.update(cx, |repository, _cx| {
repository.create_branch("totally-new-branch".to_string())
repository.create_branch("totally-new-branch".to_string(), None)
})
})
.await
@@ -326,7 +326,7 @@ async fn test_ssh_collaboration_git_branches(
// Also try creating a new branch
cx_b.update(|cx| {
repo_b.update(cx, |repo_b, _cx| {
repo_b.create_branch("totally-new-branch".to_string())
repo_b.create_branch("totally-new-branch".to_string(), None)
})
})
.await
+5 -1
View File
@@ -407,7 +407,11 @@ impl GitRepository for FakeGitRepository {
})
}
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
fn create_branch(
&self,
name: String,
_base_branch: Option<String>,
) -> BoxFuture<'_, Result<()>> {
self.with_state_async(true, move |state| {
state.branches.insert(name);
Ok(())
+22 -7
View File
@@ -431,7 +431,8 @@ pub trait GitRepository: Send + Sync {
fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>>;
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>;
fn create_branch(&self, name: String, base_branch: Option<String>)
-> BoxFuture<'_, Result<()>>;
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>>;
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>>;
@@ -1358,14 +1359,28 @@ impl GitRepository for RealGitRepository {
.boxed()
}
fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
let repo = self.repository.clone();
fn create_branch(
&self,
name: String,
base_branch: Option<String>,
) -> BoxFuture<'_, Result<()>> {
let git_binary_path = self.any_git_binary_path.clone();
let working_directory = self.working_directory();
let executor = self.executor.clone();
self.executor
.spawn(async move {
let repo = repo.lock();
let current_commit = repo.head()?.peel_to_commit()?;
repo.branch(&name, &current_commit, false)?;
Ok(())
let mut args = vec!["switch", "-c", &name];
let base_branch_str;
if let Some(ref base) = base_branch {
base_branch_str = base.clone();
args.push(&base_branch_str);
}
GitBinary::new(git_binary_path, working_directory?, executor)
.run(&args)
.await?;
anyhow::Ok(())
})
.boxed()
}
+2 -10
View File
@@ -241,18 +241,10 @@ impl BranchListDelegate {
return;
};
let new_branch_name = new_branch_name.to_string().replace(' ', "-");
let base_branch = from_branch.map(|b| b.to_string());
cx.spawn(async move |_, cx| {
if let Some(based_branch) = from_branch {
repo.update(cx, |repo, _| repo.change_branch(based_branch.to_string()))?
.await??;
}
repo.update(cx, |repo, _| {
repo.create_branch(new_branch_name.to_string())
})?
.await??;
repo.update(cx, |repo, _| {
repo.change_branch(new_branch_name.to_string())
repo.create_branch(new_branch_name, base_branch)
})?
.await??;
+28 -22
View File
@@ -2094,7 +2094,7 @@ impl GitStore {
repository_handle
.update(&mut cx, |repository_handle, _| {
repository_handle.create_branch(branch_name)
repository_handle.create_branch(branch_name, None)
})?
.await??;
@@ -4747,29 +4747,35 @@ impl Repository {
})
}
pub fn create_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
pub fn create_branch(
&mut self,
branch_name: String,
base_branch: Option<String>,
) -> oneshot::Receiver<Result<()>> {
let id = self.id;
self.send_job(
Some(format!("git switch -c {branch_name}").into()),
move |repo, _cx| async move {
match repo {
RepositoryState::Local { backend, .. } => {
backend.create_branch(branch_name).await
}
RepositoryState::Remote { project_id, client } => {
client
.request(proto::GitCreateBranch {
project_id: project_id.0,
repository_id: id.to_proto(),
branch_name,
})
.await?;
Ok(())
}
let status_msg = if let Some(ref base) = base_branch {
format!("git switch -c {branch_name} {base}").into()
} else {
format!("git switch -c {branch_name}").into()
};
self.send_job(Some(status_msg), move |repo, _cx| async move {
match repo {
RepositoryState::Local { backend, .. } => {
backend.create_branch(branch_name, base_branch).await
}
},
)
RepositoryState::Remote { project_id, client } => {
client
.request(proto::GitCreateBranch {
project_id: project_id.0,
repository_id: id.to_proto(),
branch_name,
})
.await?;
Ok(())
}
}
})
}
pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
@@ -1662,7 +1662,7 @@ async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestA
// Also try creating a new branch
cx.update(|cx| {
repository.update(cx, |repo, _cx| {
repo.create_branch("totally-new-branch".to_string())
repo.create_branch("totally-new-branch".to_string(), None)
})
})
.await