git: Fix pull failing when tracking remote with different branch name (#41768)

Mayank Verma created

Closes #31430

Release Notes:

- Fixed git pull failing when tracking remote with different branch name

Here's a before/after comparison when `dev` branch has upstream set to
`origin/main`:


https://github.com/user-attachments/assets/3a47e736-c7b7-4634-8cd1-aca7300c3a73

Change summary

crates/fs/src/fake_git_repo.rs  |  2 +-
crates/git/src/repository.rs    |  6 +++---
crates/git_ui/src/git_panel.rs  | 13 ++++++-------
crates/project/src/git_store.rs | 23 +++++++++++++----------
crates/proto/proto/git.proto    |  2 +-
5 files changed, 24 insertions(+), 22 deletions(-)

Detailed changes

crates/fs/src/fake_git_repo.rs 🔗

@@ -541,7 +541,7 @@ impl GitRepository for FakeGitRepository {
 
     fn pull(
         &self,
-        _branch: String,
+        _branch: Option<String>,
         _remote: String,
         _rebase: bool,
         _askpass: AskPassDelegate,

crates/git/src/repository.rs 🔗

@@ -531,7 +531,7 @@ pub trait GitRepository: Send + Sync {
 
     fn pull(
         &self,
-        branch_name: String,
+        branch_name: Option<String>,
         upstream_name: String,
         rebase: bool,
         askpass: AskPassDelegate,
@@ -1689,7 +1689,7 @@ impl GitRepository for RealGitRepository {
 
     fn pull(
         &self,
-        branch_name: String,
+        branch_name: Option<String>,
         remote_name: String,
         rebase: bool,
         ask_pass: AskPassDelegate,
@@ -1713,7 +1713,7 @@ impl GitRepository for RealGitRepository {
 
             command
                 .arg(remote_name)
-                .arg(branch_name)
+                .args(branch_name)
                 .stdout(smol::process::Stdio::piped())
                 .stderr(smol::process::Stdio::piped());
 

crates/git_ui/src/git_panel.rs 🔗

@@ -2250,14 +2250,13 @@ impl GitPanel {
                 this.askpass_delegate(format!("git pull {}", remote.name), window, cx)
             })?;
 
+            let branch_name = branch
+                .upstream
+                .is_none()
+                .then(|| branch.name().to_owned().into());
+
             let pull = repo.update(cx, |repo, cx| {
-                repo.pull(
-                    branch.name().to_owned().into(),
-                    remote.name.clone(),
-                    rebase,
-                    askpass,
-                    cx,
-                )
+                repo.pull(branch_name, remote.name.clone(), rebase, askpass, cx)
             })?;
 
             let remote_message = pull.await?;

crates/project/src/git_store.rs 🔗

@@ -1723,7 +1723,7 @@ impl GitStore {
             &mut cx,
         );
 
-        let branch_name = envelope.payload.branch_name.into();
+        let branch_name = envelope.payload.branch_name.map(|name| name.into());
         let remote_name = envelope.payload.remote_name.into();
         let rebase = envelope.payload.rebase;
 
@@ -4293,7 +4293,7 @@ impl Repository {
 
     pub fn pull(
         &mut self,
-        branch: SharedString,
+        branch: Option<SharedString>,
         remote: SharedString,
         rebase: bool,
         askpass: AskPassDelegate,
@@ -4303,13 +4303,16 @@ impl Repository {
         let askpass_id = util::post_inc(&mut self.latest_askpass_id);
         let id = self.id;
 
-        let status = if rebase {
-            Some(format!("git pull --rebase {} {}", remote, branch).into())
-        } else {
-            Some(format!("git pull {} {}", remote, branch).into())
-        };
+        let mut status = "git pull".to_string();
+        if rebase {
+            status.push_str(" --rebase");
+        }
+        status.push_str(&format!(" {}", remote));
+        if let Some(b) = &branch {
+            status.push_str(&format!(" {}", b));
+        }
 
-        self.send_job(status, move |git_repo, cx| async move {
+        self.send_job(Some(status.into()), move |git_repo, cx| async move {
             match git_repo {
                 RepositoryState::Local {
                     backend,
@@ -4318,7 +4321,7 @@ impl Repository {
                 } => {
                     backend
                         .pull(
-                            branch.to_string(),
+                            branch.as_ref().map(|b| b.to_string()),
                             remote.to_string(),
                             rebase,
                             askpass,
@@ -4339,7 +4342,7 @@ impl Repository {
                             repository_id: id.to_proto(),
                             askpass_id,
                             rebase,
-                            branch_name: branch.to_string(),
+                            branch_name: branch.as_ref().map(|b| b.to_string()),
                             remote_name: remote.to_string(),
                         })
                         .await

crates/proto/proto/git.proto 🔗

@@ -403,7 +403,7 @@ message Pull {
     reserved 2;
     uint64 repository_id = 3;
     string remote_name = 4;
-    string branch_name = 5;
+    optional string branch_name = 5;
     uint64 askpass_id = 6;
     bool rebase = 7;
 }