diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 6746133a5bf80e..93f53e44972f97 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -2029,11 +2029,48 @@ impl GitRepository for RealGitRepository { } fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> { - let git_binary = self.git_binary(); + let git_binary = self.git_binary_in_worktree(); self.executor .spawn(async move { - git_binary.run(&["checkout", &name]).await?; - anyhow::Ok(()) + let git_binary = git_binary?; + let local_ref = format!("refs/heads/{name}"); + if git_binary + .run(&["show-ref", "--verify", "--quiet", &local_ref]) + .await + .is_ok() + { + git_binary.run(&["checkout", &name]).await?; + return anyhow::Ok(()); + } + + let remote_ref = format!("refs/remotes/{name}"); + if git_binary + .run(&["show-ref", "--verify", "--quiet", &remote_ref]) + .await + .is_ok() + { + let (_, branch_name) = + name.split_once('/').context("Unexpected branch format")?; + let local_branch_ref = format!("refs/heads/{branch_name}"); + if git_binary + .run(&["show-ref", "--verify", "--quiet", &local_branch_ref]) + .await + .is_ok() + { + git_binary + .run(&["branch", "--set-upstream-to", &name, branch_name]) + .await?; + } else { + git_binary + .run(&["branch", "--track", branch_name, &name]) + .await?; + } + + git_binary.run(&["checkout", branch_name]).await?; + return anyhow::Ok(()); + } + + anyhow::bail!("Branch '{}' not found", name); }) .boxed() } @@ -3934,6 +3971,118 @@ mod tests { ); } + #[gpui::test] + async fn test_change_branch_creates_local_tracking_branch_from_remote(cx: &mut TestAppContext) { + disable_git_global_config(); + cx.executor().allow_parking(); + + let temp_dir = tempfile::tempdir().unwrap(); + let remote_dir = temp_dir.path().join("remote.git"); + let seed_dir = temp_dir.path().join("seed"); + let clone_dir = temp_dir.path().join("clone"); + + git_command( + temp_dir.path(), + [ + OsString::from("init"), + OsString::from("--bare"), + OsString::from("-b"), + OsString::from("main"), + remote_dir.as_os_str().into(), + ], + ); + git_init_repo(&seed_dir); + fs::write(seed_dir.join("file.txt"), "main").unwrap(); + git_command(&seed_dir, ["add", "file.txt"]); + git_command(&seed_dir, ["commit", "-m", "initial"]); + git_command(&seed_dir, ["switch", "-c", "feature"]); + fs::write(seed_dir.join("feature.txt"), "feature").unwrap(); + git_command(&seed_dir, ["add", "feature.txt"]); + git_command(&seed_dir, ["commit", "-m", "feature"]); + git_command( + &seed_dir, + [ + OsString::from("remote"), + OsString::from("add"), + OsString::from("origin"), + remote_dir.as_os_str().into(), + ], + ); + git_command(&seed_dir, ["push", "-u", "origin", "main"]); + git_command(&seed_dir, ["push", "-u", "origin", "feature"]); + git_command( + temp_dir.path(), + [ + OsString::from("clone"), + remote_dir.as_os_str().into(), + clone_dir.as_os_str().into(), + ], + ); + + let repository = RealGitRepository::new( + &clone_dir.join(".git"), + None, + Some("git".into()), + cx.executor(), + ) + .unwrap(); + let git = repository.git_binary_in_worktree().unwrap(); + assert!( + git.run(&[ + "show-ref", + "--verify", + "--quiet", + "refs/remotes/origin/feature" + ]) + .await + .is_ok() + ); + assert!( + git.run(&["show-ref", "--verify", "--quiet", "refs/heads/feature"]) + .await + .is_err() + ); + + repository + .change_branch("origin/feature".to_string()) + .await + .unwrap(); + + let git = repository.git_binary_in_worktree().unwrap(); + assert_eq!( + git.run(&["branch", "--show-current"]).await.unwrap(), + "feature" + ); + assert_eq!( + git.run(&["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}",]) + .await + .unwrap(), + "origin/feature" + ); + + git.run(&["checkout", "main"]).await.unwrap(); + git.run(&["branch", "--unset-upstream", "feature"]) + .await + .unwrap(); + + repository + .change_branch("origin/feature".to_string()) + .await + .unwrap(); + + let git = repository.git_binary_in_worktree().unwrap(); + assert_eq!( + git.run(&["branch", "--show-current"]).await.unwrap(), + "feature" + ); + assert_eq!( + git.run(&["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}",]) + .await + .unwrap(), + "origin/feature" + ); + } + #[gpui::test] fn test_real_git_repository_new_rejects_malformed_git_file(cx: &mut TestAppContext) { disable_git_global_config();