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 v4] remote: qualify "git pull" advice for non-upstream compareBranches
Date: Thu, 21 May 2026 14:06:07 +0000 [thread overview]
Message-ID: <pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
Enable ENABLE_ADVICE_PULL for push-branch comparisons too, not just
the upstream entry, so the "use git pull" hint prints when the local
branch is behind its push branch.
Spell out "git pull <remote> <branch>" so running the suggested
command actually pulls the ref the user was told about; plain
"git pull" would fetch the upstream instead.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote: qualify "git pull" advice for non-upstream branches
* Don't suggest git pull when we have no good command to suggest.
* New test for this. Asserts the behind line shows with no follow-up
advice.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v4
Pull-Request: https://github.com/git/git/pull/2301
Range-diff vs v3:
1: 3703be9aac ! 1: ef54dacb07 remote: qualify "git pull" advice for non-upstream compareBranches
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
full_ref = resolve_compare_branch(branch,
branches.items[i].string);
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- if (reported)
- strbuf_addstr(sb, "\n");
-- if (is_upstream)
-+ if (is_upstream || is_push)
+ if (is_upstream)
flags |= ENABLE_ADVICE_PULL;
- if (is_push)
- flags |= ENABLE_ADVICE_PUSH;
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ if (push_remote_name &&
+ skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
+ skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
-+ *push_branch_name == '/')
++ *push_branch_name == '/') {
+ push_branch_name++;
-+ else
++ flags |= ENABLE_ADVICE_PULL;
++ } else {
+ push_remote_name = NULL;
++ }
++ } else {
++ flags |= ENABLE_ADVICE_PULL;
+ }
+ }
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
@@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap
+ EOF
+ test_cmp expect actual
+'
++
++test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '
++ test_config -C test push.default current &&
++ test_config -C test remote.imported.url ../. &&
++ test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" &&
++ test_config -C test branch.feature17.pushRemote imported &&
++ test_config -C test status.compareBranches "@{push}" &&
++ git -C test fetch imported &&
++ git -C test checkout --no-track -b feature17 refs/imported/imported/main &&
++ (cd test && advance work17) &&
++ git -C test push imported HEAD:feature17 &&
++ git -C test fetch imported &&
++ git -C test reset --hard HEAD^ &&
++ git -C test status >actual &&
++ cat >expect <<-EOF &&
++ On branch feature17
++ Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.
++
++ nothing to commit, working tree clean
++ EOF
++ test_cmp expect actual
++'
+
test_done
remote.c | 48 +++++++++++++++----
t/t6040-tracking-info.sh | 100 +++++++++++++++++++++++++++++++++++++++
2 files changed, 140 insertions(+), 8 deletions(-)
diff --git a/remote.c b/remote.c
index 24a8118d25..193e1dd1f1 100644
--- a/remote.c
+++ b/remote.c
@@ -2268,6 +2268,8 @@ static void format_branch_comparison(struct strbuf *sb,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
+ const char *push_remote_name,
+ const char *push_branch_name,
enum ahead_behind_flags abf,
unsigned flags)
{
@@ -2303,9 +2305,15 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
+ if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (push_remote_name && push_branch_name)
+ strbuf_addf(sb,
+ _(" (use \"git pull %s %s\" to update your local branch)\n"),
+ push_remote_name, push_branch_name);
+ else
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ }
} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
@@ -2316,9 +2324,15 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (push_remote_name && push_branch_name)
+ strbuf_addf(sb,
+ _(" (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"),
+ push_remote_name, push_branch_name);
+ else
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
}
}
@@ -2356,6 +2370,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
int ours, theirs, cmp;
int is_upstream, is_push;
unsigned flags = 0;
+ const char *push_remote_name = NULL;
+ const char *push_branch_name = NULL;
full_ref = resolve_compare_branch(branch,
branches.items[i].string);
@@ -2399,11 +2415,27 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (is_upstream)
flags |= ENABLE_ADVICE_PULL;
- if (is_push)
- flags |= ENABLE_ADVICE_PUSH;
if (show_divergence_advice && is_upstream)
flags |= ENABLE_ADVICE_DIVERGENCE;
+ if (is_push) {
+ flags |= ENABLE_ADVICE_PUSH;
+ if (!upstream_ref || strcmp(upstream_ref, full_ref)) {
+ push_remote_name = pushremote_for_branch(branch, NULL);
+ if (push_remote_name &&
+ skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
+ skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
+ *push_branch_name == '/') {
+ push_branch_name++;
+ flags |= ENABLE_ADVICE_PULL;
+ } else {
+ push_remote_name = NULL;
+ }
+ } else {
+ flags |= ENABLE_ADVICE_PULL;
+ }
+ }
format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
+ push_remote_name, push_branch_name,
abf, flags);
reported = 1;
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0242b5bf7a..91cbb8775d 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo
test_cmp expect actual
'
+test_expect_success 'status.compareBranches behind both upstream and push' '
+ test_config -C test push.default current &&
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test status.compareBranches "@{upstream} @{push}" &&
+ git -C test checkout -b feature13 upstream/main &&
+ (cd test && advance work13) &&
+ git -C test push origin &&
+ git -C test branch --set-upstream-to upstream/ahead &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature13
+ Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+ Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin feature13" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with remapped push and behind push branch' '
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test checkout -b feature14 upstream/main &&
+ (cd test && advance work14) &&
+ git -C test push &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature14
+ Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin remapped14" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with behind push branch and no upstream' '
+ test_config -C test push.default current &&
+ test_config -C test remote.pushDefault origin &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test checkout --no-track -b feature15 upstream/main &&
+ (cd test && advance work15) &&
+ git -C test push origin &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature15
+ Your branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull origin feature15" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '
+ test_config -C test status.compareBranches "@{upstream} @{push}" &&
+ git -C test checkout -b feature16 origin/main &&
+ (cd test && advance work16) &&
+ git -C test push origin HEAD:main &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature16
+ Your branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '
+ test_config -C test push.default current &&
+ test_config -C test remote.imported.url ../. &&
+ test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" &&
+ test_config -C test branch.feature17.pushRemote imported &&
+ test_config -C test status.compareBranches "@{push}" &&
+ git -C test fetch imported &&
+ git -C test checkout --no-track -b feature17 refs/imported/imported/main &&
+ (cd test && advance work17) &&
+ git -C test push imported HEAD:feature17 &&
+ git -C test fetch imported &&
+ git -C test reset --hard HEAD^ &&
+ git -C test status >actual &&
+ cat >expect <<-EOF &&
+ On branch feature17
+ Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.
+
+ nothing to commit, working tree clean
+ EOF
+ test_cmp expect actual
+'
+
test_done
base-commit: aec3f587505a472db67e9462d0702e7d463a449d
--
gitgitgadget
prev parent reply other threads:[~2026-05-21 14:06 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-12 22:11 [PATCH] remote: qualify "git pull" advice for non-upstream branches Harald Nordgren via GitGitGadget
2026-05-13 9:50 ` [PATCH v2] " Harald Nordgren via GitGitGadget
2026-05-19 8:29 ` Junio C Hamano
2026-05-20 6:51 ` Harald Nordgren
2026-05-20 13:10 ` [PATCH v3] remote: qualify "git pull" advice for non-upstream compareBranches Harald Nordgren via GitGitGadget
2026-05-21 8:19 ` Junio C Hamano
2026-05-21 14:06 ` 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=pull.2301.v4.git.git.1779372367317.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