Git development
 help / color / mirror / Atom feed
From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH 6/6] push: suggest a force push after a clean rebase
Date: Tue, 06 Oct 2026 07:08:24 +0000	[thread overview]
Message-ID: <78571ebf5fb578852f8bae35fbf7cb3eb3a0bd46.1791270504.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2437.git.git.1791270504.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

After rebasing pushed work onto a newer upstream without changing it,
"git status" says the branch was rebased cleanly, but a rejected
"git push" still suggests pulling from the push branch, which would
only bring back old copies of the same commits.

When the commits outside the upstream carry the same changes on both
sides, say what "git status" says and suggest only the force push:

  hint: Updates were rejected because 'origin/topic' has diverged
  hint: from your current branch, which was rebased cleanly on 'upstream/main'.
  hint: Use 'git push --force-with-lease origin topic' to replace it.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/push.c           | 20 +++++++++++++++++---
 remote.c                 | 17 +++++++++++++++++
 remote.h                 |  6 ++++++
 t/t6040-tracking-info.sh | 25 +++++++++++++++++++++++++
 4 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/builtin/push.c b/builtin/push.c
index d918723d43..bf6e5386a0 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -301,6 +301,11 @@ static const char message_advice_pull_from_branch_before_push[] =
 	   "from your current branch. Use 'git pull %s %s'\n"
 	   "to integrate the remote changes.");
 
+static const char message_advice_force_after_clean_rebase[] =
+	N_("Updates were rejected because '%s' has diverged\n"
+	   "from your current branch, which was rebased cleanly on '%s'.\n"
+	   "Use 'git push --force-with-lease %s %s' to replace it.");
+
 static const char message_advice_pull_or_force_before_push[] =
 	N_("Updates were rejected because '%s' has diverged\n"
 	   "from your current branch. Use 'git pull %s %s'\n"
@@ -356,15 +361,24 @@ static void advise_pull_before_push(struct remote *push_remote,
 		tracking_name = refs_shorten_unambiguous_ref(
 			get_main_ref_store(the_repository), tracking, 0);
 
-	if (tracking && (reject_reasons & REJECT_NON_FF_HEAD_REWRITE))
+	if (tracking && branch_rebased_cleanly(branch, tracking)) {
+		char *upstream_name = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), upstream, 0);
+
+		advise(_(message_advice_force_after_clean_rebase),
+		       tracking_name, upstream_name,
+		       remote->name, branch->name);
+		free(upstream_name);
+	} else if (tracking && (reject_reasons & REJECT_NON_FF_HEAD_REWRITE)) {
 		advise(_(message_advice_pull_or_force_before_push),
 		       tracking_name, remote->name, branch->name,
 		       remote->name, branch->name);
-	else if (tracking && (!upstream || strcmp(tracking, upstream)))
+	} else if (tracking && (!upstream || strcmp(tracking, upstream))) {
 		advise(_(message_advice_pull_from_branch_before_push),
 		       tracking_name, remote->name, branch->name);
-	else
+	} else {
 		advise(_(message_advice_pull_before_push));
+	}
 
 	free(tracking_name);
 	free(tracking);
diff --git a/remote.c b/remote.c
index af8d026073..623ff98a01 100644
--- a/remote.c
+++ b/remote.c
@@ -2391,6 +2391,23 @@ static bool stat_outside_upstream(const char *branch_name, const char *base,
 	return true;
 }
 
+bool branch_rebased_cleanly(struct branch *branch, const char *base)
+{
+	const char *upstream = branch_get_upstream(branch, NULL);
+	int ours, theirs, ours_unmerged, theirs_unmerged;
+	bool same_changes = false;
+
+	if (!upstream || !strcmp(upstream, base))
+		return false;
+	if (stat_branch_pair(branch->refname, base, NULL, &ours, &theirs,
+			     NULL, AHEAD_BEHIND_FULL) <= 0 || !ours || !theirs)
+		return false;
+	return stat_outside_upstream(branch->refname, base, upstream,
+				     ours, theirs, &ours_unmerged,
+				     &theirs_unmerged, &same_changes) &&
+	       same_changes;
+}
+
 static char *resolve_compare_branch(struct branch *branch, const char *name)
 {
 	const char *resolved = NULL;
diff --git a/remote.h b/remote.h
index cca02033b9..dd8ef6c443 100644
--- a/remote.h
+++ b/remote.h
@@ -408,6 +408,12 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			 enum ahead_behind_flags abf,
 			 int show_divergence_advice);
 
+/*
+ * Return true when the branch has diverged from base only because the
+ * work on base was rebased cleanly on the upstream of the branch.
+ */
+bool branch_rebased_cleanly(struct branch *branch, const char *base);
+
 struct ref *get_local_heads(void);
 
 /*
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index b53034ba36..b5339e1452 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -804,6 +804,31 @@ test_expect_success 'status.compareBranches after a clean rebase of the push bra
 	and have 3 and 1 different commits each (rebased cleanly on ${SQ}origin/main${SQ}).
 	  (use "git push --force-with-lease" to publish your local commits)
 
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual &&
+	(
+		cd test &&
+		test_must_fail git push 2>../push.err &&
+		git push --force-with-lease origin feature19 &&
+		git status >../actual
+	) &&
+	url=$(git -C test config remote.origin.url) &&
+	cat >expect <<-EOF &&
+	To $url
+	 ! [rejected]        feature19 -> feature19 (non-fast-forward)
+	error: failed to push some refs to ${SQ}$url${SQ}
+	hint: Updates were rejected because ${SQ}origin/feature19${SQ} has diverged
+	hint: from your current branch, which was rebased cleanly on ${SQ}origin/main${SQ}.
+	hint: Use ${SQ}git push --force-with-lease origin feature19${SQ} to replace it.
+	EOF
+	test_cmp expect push.err &&
+	cat >expect <<-EOF &&
+	On branch feature19
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is up to date with ${SQ}origin/feature19${SQ}.
+
 	nothing to commit, working tree clean
 	EOF
 	test_cmp expect actual
-- 
gitgitgadget

      parent reply	other threads:[~2026-10-06  7:08 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06  7:08 [PATCH 0/6] status, push: handle a push branch rebased onto a newer upstream Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` [PATCH 1/6] status: count push divergence outside the upstream Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` [PATCH 2/6] status: say when the push branch was rebased cleanly Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` [PATCH 3/6] status: suggest a force push after a clean rebase Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` [PATCH 4/6] push: name the branch to pull from when it is not the upstream Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` [PATCH 5/6] push: offer a force push after rewriting pushed commits Harald Nordgren via GitGitGadget
2026-10-06  7:08 ` Harald Nordgren via GitGitGadget [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=78571ebf5fb578852f8bae35fbf7cb3eb3a0bd46.1791270504.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=haraldnordgren@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox