[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