From 79e4233c5677d9bb79e59505a5f38431074ed231 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sat, 31 Oct 2026 08:56:02 +0800 Subject: [PATCH] fix(vcs): push to an upstream with another name A plain git push refuses under push.default=simple when the tracked branch is named differently from the local one. Push now names the tracked ref explicitly in that case, which covers the Push dialog and Commit and Push. --- crates/ide-vcs/src/write.rs | 19 +++++++++++++++++++ crates/ide-vcs/tests/history_ops.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/crates/ide-vcs/src/write.rs b/crates/ide-vcs/src/write.rs index 8a74c4596..535be279d 100644 --- a/crates/ide-vcs/src/write.rs +++ b/crates/ide-vcs/src/write.rs @@ -723,6 +723,8 @@ pub(crate) fn push( }) .ok_or_else(|| IdeError::invalid("no remote configured"))?; args.extend(["--set-upstream".into(), remote, short]); + } else if let Some((remote, target)) = renamed_upstream(r)? { + args.extend([remote, format!("HEAD:{target}")]); } let argv: Vec<&str> = args.iter().map(String::as_str).collect(); cli::run_streaming(r.root(), &argv, cancel, on_output)?; @@ -814,6 +816,23 @@ pub(crate) fn update_branch( } /// The remote the current branch pushes to (`origin`, or the only remote, by default). +/// The remote and ref HEAD's branch tracks when the ref has another name: a plain +/// `git push` refuses that under the default `push.default=simple`. +fn renamed_upstream(r: &Repo) -> IdeResult> { + let repo = r.gix()?; + let Some(head) = repo.head_name().map_err(gx)? else { return Ok(None) }; + let direction = gix::remote::Direction::Fetch; + let Some(Ok(merge)) = repo.branch_remote_ref_name(head.as_ref(), direction) else { return Ok(None) }; + if merge.as_ref() == head.as_ref() { + return Ok(None); + } + let target = merge.as_bstr().to_str_lossy().into_owned(); + let short = head.as_ref().shorten().to_str_lossy().into_owned(); + Ok(repo + .branch_remote_name(short.as_str(), direction) + .map(|remote| (remote.as_bstr().to_str_lossy().into_owned(), target))) +} + fn push_remote(r: &Repo) -> IdeResult { let repo = r.gix()?; let from_branch = repo.head_name().map_err(gx)?.and_then(|head| { diff --git a/crates/ide-vcs/tests/history_ops.rs b/crates/ide-vcs/tests/history_ops.rs index 16cb66d22..0ef3d0b78 100644 --- a/crates/ide-vcs/tests/history_ops.rs +++ b/crates/ide-vcs/tests/history_ops.rs @@ -467,6 +467,32 @@ fn push_with_tags() { assert_eq!(git_in(&bare, &["rev-parse", "main"]), fx.git(&["rev-parse", "HEAD"])); } +#[test] +fn push_goes_to_an_upstream_with_another_name() { + let fx = Fixture::new(); + let bare = fx._tmp.path().join("origin.git"); + git_in(fx._tmp.path(), &["init", "-q", "--bare", bare.to_str().unwrap()]); + fx.write("a.txt", "1\n"); + fx.commit_all("one"); + fx.git(&["remote", "add", "origin", bare.to_str().unwrap()]); + fx.git(&["push", "-q", "origin", "main:release/one"]); + fx.git(&["fetch", "-q", "origin"]); + fx.git(&["checkout", "-q", "-b", "local-name", "--track", "origin/release/one"]); + fx.write("a.txt", "2\n"); + let c2 = fx.commit_all("two"); + let repo = fx.repo(); + let (cancel, out) = quiet(); + + repo.push(false, false, &cancel, &out).unwrap(); + assert_eq!(git_in(&bare, &["rev-parse", "release/one"]), c2); + assert_eq!(git_in(&bare, &["branch", "--list", "local-name"]), ""); + + fx.write("a.txt", "3\n"); + let c3 = fx.commit_all("three"); + repo.push_with_tags(false, false, Some(ide_vcs::PushTags::CurrentBranch), &cancel, &out).unwrap(); + assert_eq!(git_in(&bare, &["rev-parse", "release/one"]), c3); +} + #[test] fn push_branch_and_up_to_revision() { let fx = Fixture::new();