git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 4/6] push: name the branch to pull from when it is not the upstream

From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
Date
Oct 6, 2026, 07:08 UTC
Message-ID
<b44f4cdeaff64a5099adfc9dd325033e8c38f7b4.1791270504.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.2437.git.git.1791270504.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>

When someone else pushed to the branch you push to, such as a topic branch on your fork, "git push" is rejected with a hint to run "git pull" first. If that branch is not your upstream, the pull merges the upstream and leaves the push branch diverged, so the push is rejected again.

Name the branch that diverged and suggest pulling from it:
  hint: Updates were rejected because 'origin/topic' has diverged
  hint: from your current branch. Use 'git pull origin topic'
  hint: to integrate the remote changes.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/push.c           | 37 +++++++++++++++++++++++---
 t/t6040-tracking-info.sh | 57 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/builtin/push.c b/builtin/push.c
index 2377b5af55..82435beab5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -12,6 +12,7 @@
 #include "environment.h"
 #include "gettext.h"
 #include "hex.h"
+#include "refs.h"
 #include "refspec.h"
 #include "run-command.h"
 #include "remote.h"
@@ -295,6 +296,11 @@ static const char message_advice_pull_before_push[] =
 	   "use 'git pull' before pushing again.\n"
 	   "See the 'Note about fast-forwards' in 'git push --help' for details.");
 
+static const char message_advice_pull_from_branch_before_push[] =
+	N_("Updates were rejected because '%s' has diverged\n"
+	   "from your current branch. Use 'git pull %s %s'\n"
+	   "to integrate the remote changes.");
+
 static const char message_advice_checkout_pull_push[] =
 	N_("Updates were rejected because a pushed branch tip is behind its remote\n"
 	   "counterpart. If you want to integrate the remote changes, use 'git pull'\n"
@@ -322,11 +328,35 @@ static const char message_advice_ref_needs_update[] =
 	   "remote changes, use 'git pull' before pushing again.\n"
 	   "See the 'Note about fast-forwards' in 'git push --help' for details.");
 
-static void advise_pull_before_push(void)
+static void advise_pull_before_push(struct remote *push_remote)
 {
+	struct branch *branch = branch_get(NULL);
+	struct remote *remote = NULL;
+	const char *upstream = NULL;
+	char *tracking = NULL;
+	char *tracking_name = NULL;
+
 	if (!advice_enabled(ADVICE_PUSH_NON_FF_CURRENT) || !advice_enabled(ADVICE_PUSH_UPDATE_REJECTED))
 		return;
-	advise(_(message_advice_pull_before_push));
+
+	if (branch) {
+		remote = repo_remote_for_push_tracking(the_repository,
+						       push_remote);
+		tracking = apply_refspecs(&remote->fetch, branch->refname);
+		upstream = branch_get_upstream(branch, NULL);
+	}
+	if (tracking)
+		tracking_name = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), tracking, 0);
+
+	if (tracking && (!upstream || strcmp(tracking, upstream)))
+		advise(_(message_advice_pull_from_branch_before_push),
+		       tracking_name, remote->name, branch->name);
+	else
+		advise(_(message_advice_pull_before_push));
+
+	free(tracking_name);
+	free(tracking);
 }
 
 static void advise_checkout_pull_push(void)
@@ -370,6 +400,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
 	int err;
 	unsigned int reject_reasons;
 	char *anon_url = transport_anonymize_url(transport->url);
+	struct remote *remote = transport->remote;
 
 	transport_set_verbosity(transport, verbosity, progress);
 	transport->family = family;
@@ -404,7 +435,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
 		return 0;
 
 	if (reject_reasons & REJECT_NON_FF_HEAD) {
-		advise_pull_before_push();
+		advise_pull_before_push(remote);
 	} else if (reject_reasons & REJECT_NON_FF_OTHER) {
 		advise_checkout_pull_push();
 	} else if (reject_reasons & REJECT_ALREADY_EXISTS) {
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 4074c6663a..9eb810e158 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -809,4 +809,61 @@ test_expect_success 'status.compareBranches after a clean rebase of the push bra
 	test_cmp expect actual
 '
 
+test_expect_success 'push to a push branch someone else updated suggests pulling from it' '
+	(
+		cd test &&
+		git checkout -b feature20 origin/main &&
+		advance work20 &&
+		git push origin feature20
+	) &&
+	git checkout feature20 &&
+	advance other20 &&
+	git checkout - &&
+	(
+		cd test &&
+		advance mine20 &&
+		git fetch &&
+		test_must_fail git push origin feature20 2>../actual
+	) &&
+	url=$(git -C test config remote.origin.url) &&
+	cat >expect <<-EOF &&
+	To $url
+	 ! [rejected]        feature20 -> feature20 (non-fast-forward)
+	error: failed to push some refs to ${SQ}$url${SQ}
+	hint: Updates were rejected because ${SQ}origin/feature20${SQ} has diverged
+	hint: from your current branch. Use ${SQ}git pull origin feature20${SQ}
+	hint: to integrate the remote changes.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'push to the upstream branch' '
+	(
+		cd test &&
+		git checkout -b feature21 origin/main &&
+		advance work21 &&
+		git push -u origin feature21
+	) &&
+	git checkout feature21 &&
+	advance other21 &&
+	git checkout - &&
+	(
+		cd test &&
+		advance mine21 &&
+		git fetch &&
+		test_must_fail git push 2>../actual
+	) &&
+	url=$(git -C test config remote.origin.url) &&
+	cat >expect <<-EOF &&
+	To $url
+	 ! [rejected]        feature21 -> feature21 (non-fast-forward)
+	error: failed to push some refs to ${SQ}$url${SQ}
+	hint: Updates were rejected because the tip of your current branch is behind
+	hint: its remote counterpart. If you want to integrate the remote changes,
+	hint: use ${SQ}git pull${SQ} before pushing again.
+	hint: See the ${SQ}Note about fast-forwards${SQ} in ${SQ}git push --help${SQ} for details.
+	EOF
+	test_cmp expect actual
+'
+
 test_done
-- 
gitgitgadget
Previous: Harald Nordgren via GitGitGadgetNext: Harald Nordgren via GitGitGadget
Message 5 of 7 in “status, push: handle a push branch rebased onto a newer upstream”
  1. 0/6 status, push: handle a push branch rebased onto a newer upstreamHarald Nordgren via GitGitGadget, Oct 6, 2026
  2. 1/6 status: count push divergence outside the upstreamHarald Nordgren via GitGitGadget, Oct 6, 2026
  3. 2/6 status: say when the push branch was rebased cleanlyHarald Nordgren via GitGitGadget, Oct 6, 2026
  4. 3/6 status: suggest a force push after a clean rebaseHarald Nordgren via GitGitGadget, Oct 6, 2026
  5. 4/6 push: name the branch to pull from when it is not the upstreamHarald Nordgren via GitGitGadget, Oct 6, 2026
  6. 5/6 push: offer a force push after rewriting pushed commitsHarald Nordgren via GitGitGadget, Oct 6, 2026
  7. 6/6 push: suggest a force push after a clean rebaseHarald Nordgren via GitGitGadget, Oct 6, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.