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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.