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();