{"thread":{"id":"65627","subject":"[PATCH] remote: qualify \"git pull\" advice for non-upstream branches","startedAt":"2026-05-12T22:11:30Z","lastAt":"2026-06-10T17:35:57Z","messageCount":9,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"543222","messageId":"pull.2301.git.git.1778623888178.gitgitgadget@gmail.com","threadId":"65627","inReplyTo":null,"subject":"[PATCH] remote: qualify \"git pull\" advice for non-upstream branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-12T22:11:28Z","receivedAt":"2026-05-12T22:11:30Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen \"git status\" reports the local branch is behind or has diverged\nfrom the push branch, the advice suggested a bare \"git pull\". That\nfollows the upstream, which may live on a different remote, so emit\n\"git pull <remote> <branch>\" instead.\n\nAlso enable the pull and divergence advice for push-branch\ncomparisons; they were previously only set for the upstream.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    remote: qualify \"git pull\" advice for non-upstream branches\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v1\nPull-Request: https://github.com/git/git/pull/2301\n\n remote.c                 | 47 +++++++++++++++++++++++++++++++---------\n t/t6040-tracking-info.sh | 41 +++++++++++++++++++++++++++++++++++\n 2 files changed, 78 insertions(+), 10 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex a664cd166a..d1e09079cb 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2267,6 +2267,8 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t\t     bool up_to_date,\n \t\t\t\t     int ours, int theirs,\n \t\t\t\t     const char *branch_name,\n+\t\t\t\t     const char *push_remote_name,\n+\t\t\t\t     const char *push_branch_name,\n \t\t\t\t     enum ahead_behind_flags abf,\n \t\t\t\t     unsigned flags)\n {\n@@ -2302,9 +2304,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"and can be fast-forwarded.\\n\",\n \t\t\t   theirs),\n \t\t\tbranch_name, theirs);\n-\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" to update your local branch)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\t}\n \t} else {\n \t\tstrbuf_addf(sb,\n \t\t\tQ_(\"Your branch and '%s' have diverged,\\n\"\n@@ -2315,9 +2323,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"respectively.\\n\",\n \t\t\t   ours + theirs),\n \t\t\tbranch_name, ours, theirs);\n-\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" if you want to integrate the remote branch with yours)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\t}\n \t}\n }\n \n@@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tint ours, theirs, cmp;\n \t\tint is_upstream, is_push;\n \t\tunsigned flags = 0;\n+\t\tconst char *push_remote_name = NULL;\n+\t\tconst char *push_branch_name = NULL;\n \n \t\tfull_ref = resolve_compare_branch(branch,\n \t\t\t\t\t\t  branches.items[i].string);\n@@ -2396,13 +2412,24 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tif (reported)\n \t\t\tstrbuf_addstr(sb, \"\\n\");\n \n-\t\tif (is_upstream)\n+\t\tif (is_upstream || is_push) {\n \t\t\tflags |= ENABLE_ADVICE_PULL;\n-\t\tif (is_push)\n+\t\t\tif (show_divergence_advice)\n+\t\t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n+\t\t}\n+\t\tif (is_push) {\n \t\t\tflags |= ENABLE_ADVICE_PUSH;\n-\t\tif (show_divergence_advice && is_upstream)\n-\t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n+\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n+\t\t\tif (push_remote_name &&\n+\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n+\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n+\t\t\t    *push_branch_name == '/')\n+\t\t\t\tpush_branch_name++;\n+\t\t\telse\n+\t\t\t\tpush_remote_name = NULL;\n+\t\t}\n \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n+\t\t\t\t\t push_remote_name, push_branch_name,\n \t\t\t\t\t abf, flags);\n \t\treported = 1;\n \ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0242b5bf7a..3199063762 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -529,6 +529,7 @@ test_expect_success 'status.compareBranches with diverged push branch' '\n \n \tYour branch and ${SQ}origin/feature8${SQ} have diverged,\n \tand have 1 and 1 different commits each, respectively.\n+\t  (use \"git pull origin feature8\" if you want to integrate the remote branch with yours)\n \n \tnothing to commit, working tree clean\n \tEOF\n@@ -646,4 +647,44 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'status.compareBranches with behind push branch suggests qualified pull' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature13 upstream/main &&\n+\t(cd test && advance work13) &&\n+\tgit -C test push origin &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature13\n+\tYour branch is up to date with ${SQ}upstream/main${SQ}.\n+\n+\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature13\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout -b feature14 upstream/main &&\n+\t(cd test && advance work14) &&\n+\tgit -C test push &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature14\n+\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin remapped14\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\nbase-commit: 29bd7ed5127255713c1ac2f43b7c6f257d7b4594\n-- \ngitgitgadget\n"},{"id":"543238","messageId":"pull.2301.v2.git.git.1778665812261.gitgitgadget@gmail.com","threadId":"65627","inReplyTo":"pull.2301.git.git.1778623888178.gitgitgadget@gmail.com","subject":"[PATCH v2] remote: qualify \"git pull\" advice for non-upstream branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-13T09:50:12Z","receivedAt":"2026-05-13T09:50:16Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen \"git status\" reports the local branch is behind the push\nbranch, the advice suggested a bare \"git pull\". That follows the\nupstream, which may live on a different remote, so emit\n\"git pull <remote> <branch>\" instead.\n\nAlso enable the pull advice for push-branch comparisons; it was\npreviously only set for the upstream.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    remote: qualify \"git pull\" advice for non-upstream branches\n    \n    Remove the message only when diverged from push-branch, because it was\n    in the way when I used it now on my real branch.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v2\nPull-Request: https://github.com/git/git/pull/2301\n\nRange-diff vs v1:\n\n 1:  0a06883cc8 ! 1:  1f06873f82 remote: qualify \"git pull\" advice for non-upstream branches\n     @@ Metadata\n       ## Commit message ##\n          remote: qualify \"git pull\" advice for non-upstream branches\n      \n     -    When \"git status\" reports the local branch is behind or has diverged\n     -    from the push branch, the advice suggested a bare \"git pull\". That\n     -    follows the upstream, which may live on a different remote, so emit\n     +    When \"git status\" reports the local branch is behind the push\n     +    branch, the advice suggested a bare \"git pull\". That follows the\n     +    upstream, which may live on a different remote, so emit\n          \"git pull <remote> <branch>\" instead.\n      \n     -    Also enable the pull and divergence advice for push-branch\n     -    comparisons; they were previously only set for the upstream.\n     +    Also enable the pull advice for push-branch comparisons; it was\n     +    previously only set for the upstream.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n       \t\t\tstrbuf_addstr(sb, \"\\n\");\n       \n      -\t\tif (is_upstream)\n     -+\t\tif (is_upstream || is_push) {\n     ++\t\tif (is_upstream || is_push)\n       \t\t\tflags |= ENABLE_ADVICE_PULL;\n      -\t\tif (is_push)\n     -+\t\t\tif (show_divergence_advice)\n     -+\t\t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n     -+\t\t}\n     +-\t\t\tflags |= ENABLE_ADVICE_PUSH;\n     + \t\tif (show_divergence_advice && is_upstream)\n     + \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n      +\t\tif (is_push) {\n     - \t\t\tflags |= ENABLE_ADVICE_PUSH;\n     --\t\tif (show_divergence_advice && is_upstream)\n     --\t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n     ++\t\t\tflags |= ENABLE_ADVICE_PUSH;\n      +\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n      +\t\t\tif (push_remote_name &&\n      +\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n       \n      \n       ## t/t6040-tracking-info.sh ##\n     -@@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with diverged push branch' '\n     - \n     - \tYour branch and ${SQ}origin/feature8${SQ} have diverged,\n     - \tand have 1 and 1 different commits each, respectively.\n     -+\t  (use \"git pull origin feature8\" if you want to integrate the remote branch with yours)\n     - \n     - \tnothing to commit, working tree clean\n     - \tEOF\n      @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remapped push and upstream remo\n       \ttest_cmp expect actual\n       '\n\n\n remote.c                 | 44 ++++++++++++++++++++++++++++++++--------\n t/t6040-tracking-info.sh | 40 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 75 insertions(+), 9 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex a664cd166a..e096fdb674 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2267,6 +2267,8 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t\t     bool up_to_date,\n \t\t\t\t     int ours, int theirs,\n \t\t\t\t     const char *branch_name,\n+\t\t\t\t     const char *push_remote_name,\n+\t\t\t\t     const char *push_branch_name,\n \t\t\t\t     enum ahead_behind_flags abf,\n \t\t\t\t     unsigned flags)\n {\n@@ -2302,9 +2304,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"and can be fast-forwarded.\\n\",\n \t\t\t   theirs),\n \t\t\tbranch_name, theirs);\n-\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" to update your local branch)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\t}\n \t} else {\n \t\tstrbuf_addf(sb,\n \t\t\tQ_(\"Your branch and '%s' have diverged,\\n\"\n@@ -2315,9 +2323,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"respectively.\\n\",\n \t\t\t   ours + theirs),\n \t\t\tbranch_name, ours, theirs);\n-\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" if you want to integrate the remote branch with yours)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\t}\n \t}\n }\n \n@@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tint ours, theirs, cmp;\n \t\tint is_upstream, is_push;\n \t\tunsigned flags = 0;\n+\t\tconst char *push_remote_name = NULL;\n+\t\tconst char *push_branch_name = NULL;\n \n \t\tfull_ref = resolve_compare_branch(branch,\n \t\t\t\t\t\t  branches.items[i].string);\n@@ -2396,13 +2412,23 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tif (reported)\n \t\t\tstrbuf_addstr(sb, \"\\n\");\n \n-\t\tif (is_upstream)\n+\t\tif (is_upstream || is_push)\n \t\t\tflags |= ENABLE_ADVICE_PULL;\n-\t\tif (is_push)\n-\t\t\tflags |= ENABLE_ADVICE_PUSH;\n \t\tif (show_divergence_advice && is_upstream)\n \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n+\t\tif (is_push) {\n+\t\t\tflags |= ENABLE_ADVICE_PUSH;\n+\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n+\t\t\tif (push_remote_name &&\n+\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n+\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n+\t\t\t    *push_branch_name == '/')\n+\t\t\t\tpush_branch_name++;\n+\t\t\telse\n+\t\t\t\tpush_remote_name = NULL;\n+\t\t}\n \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n+\t\t\t\t\t push_remote_name, push_branch_name,\n \t\t\t\t\t abf, flags);\n \t\treported = 1;\n \ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0242b5bf7a..b686bf356a 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -646,4 +646,44 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'status.compareBranches with behind push branch suggests qualified pull' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature13 upstream/main &&\n+\t(cd test && advance work13) &&\n+\tgit -C test push origin &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature13\n+\tYour branch is up to date with ${SQ}upstream/main${SQ}.\n+\n+\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature13\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout -b feature14 upstream/main &&\n+\t(cd test && advance work14) &&\n+\tgit -C test push &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature14\n+\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin remapped14\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\nbase-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08\n-- \ngitgitgadget\n"},{"id":"543605","messageId":"xmqqbjeb7qfv.fsf@gitster.g","threadId":"65627","inReplyTo":"pull.2301.v2.git.git.1778665812261.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] remote: qualify \"git pull\" advice for non-upstream branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-19T08:29:24Z","receivedAt":"2026-05-19T08:29:26Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When \"git status\" reports the local branch is behind the push\n> branch, the advice suggested a bare \"git pull\". That follows the\n> upstream, which may live on a different remote, so emit\n> \"git pull <remote> <branch>\" instead.\n\nHmph, shouldn't this be done conditionally, though?  Most new users\nfollow the recommended pattern to set branch.<name>.merge so that\n\"git pull\" would do the right thing for them, I presume, even when\nthey are using triangular workflow to push to a different remote\nthan the remote they pull from, so the new and more verbose message\nwould not help the users any more than the existing message, right?\n\nCan the code tell the situation where the extra part of the message\nwould help and give it only then?\n"},{"id":"543735","messageId":"CAHwyqnVd+eG14ZO0OE9vRFc03Wj2juMWaj0tfrTBseVgi_QdVw@mail.gmail.com","threadId":"65627","inReplyTo":"xmqqbjeb7qfv.fsf@gitster.g","subject":"Re: [PATCH v2] remote: qualify \"git pull\" advice for non-upstream branches","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-05-20T06:51:53Z","receivedAt":"2026-05-20T06:52:31Z","isPatch":true,"body":"> Hmph, shouldn't this be done conditionally, though?  Most new users\n> follow the recommended pattern to set branch.<name>.merge so that\n> \"git pull\" would do the right thing for them, I presume, even when\n> they are using triangular workflow to push to a different remote\n> than the remote they pull from, so the new and more verbose message\n> would not help the users any more than the existing message, right?\n>\n> Can the code tell the situation where the extra part of the message\n> would help and give it only then?\n\nYes, that's a good idea.\n\n\nHarald\n"},{"id":"543741","messageId":"pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com","threadId":"65627","inReplyTo":"pull.2301.v2.git.git.1778665812261.gitgitgadget@gmail.com","subject":"[PATCH v3] remote: qualify \"git pull\" advice for non-upstream compareBranches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-20T13:10:25Z","receivedAt":"2026-05-20T13:10:29Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nEnable ENABLE_ADVICE_PULL for push-branch comparisons too, not just\nthe upstream entry, so the \"use git pull\" hint prints when the local\nbranch is behind its push branch.\n\nSpell out \"git pull <remote> <branch>\" so running the suggested\ncommand actually pulls the ref the user was told about; plain\n\"git pull\" would fetch the upstream instead.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    remote: qualify \"git pull\" advice for non-upstream branches\n    \n     * Only suggest git pull <remote> <branch> when plain git pull wouldn't\n       do the right thing.\n     * Tests: when upstream and push are the same ref, the message stays\n       plain git pull.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v3\nPull-Request: https://github.com/git/git/pull/2301\n\nRange-diff vs v2:\n\n 1:  1f06873f82 ! 1:  3703be9aac remote: qualify \"git pull\" advice for non-upstream branches\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    remote: qualify \"git pull\" advice for non-upstream branches\n     +    remote: qualify \"git pull\" advice for non-upstream compareBranches\n      \n     -    When \"git status\" reports the local branch is behind the push\n     -    branch, the advice suggested a bare \"git pull\". That follows the\n     -    upstream, which may live on a different remote, so emit\n     -    \"git pull <remote> <branch>\" instead.\n     +    Enable ENABLE_ADVICE_PULL for push-branch comparisons too, not just\n     +    the upstream entry, so the \"use git pull\" hint prints when the local\n     +    branch is behind its push branch.\n      \n     -    Also enable the pull advice for push-branch comparisons; it was\n     -    previously only set for the upstream.\n     +    Spell out \"git pull <remote> <branch>\" so running the suggested\n     +    command actually pulls the ref the user was told about; plain\n     +    \"git pull\" would fetch the upstream instead.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n       \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n      +\t\tif (is_push) {\n      +\t\t\tflags |= ENABLE_ADVICE_PUSH;\n     -+\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n     -+\t\t\tif (push_remote_name &&\n     -+\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n     -+\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n     -+\t\t\t    *push_branch_name == '/')\n     -+\t\t\t\tpush_branch_name++;\n     -+\t\t\telse\n     -+\t\t\t\tpush_remote_name = NULL;\n     ++\t\t\tif (!upstream_ref || strcmp(upstream_ref, full_ref)) {\n     ++\t\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n     ++\t\t\t\tif (push_remote_name &&\n     ++\t\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n     ++\t\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n     ++\t\t\t\t    *push_branch_name == '/')\n     ++\t\t\t\t\tpush_branch_name++;\n     ++\t\t\t\telse\n     ++\t\t\t\t\tpush_remote_name = NULL;\n     ++\t\t\t}\n      +\t\t}\n       \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n      +\t\t\t\t\t push_remote_name, push_branch_name,\n     @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap\n       \ttest_cmp expect actual\n       '\n       \n     -+test_expect_success 'status.compareBranches with behind push branch suggests qualified pull' '\n     ++test_expect_success 'status.compareBranches behind both upstream and push' '\n      +\ttest_config -C test push.default current &&\n      +\ttest_config -C test remote.pushDefault origin &&\n      +\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n      +\tgit -C test checkout -b feature13 upstream/main &&\n      +\t(cd test && advance work13) &&\n      +\tgit -C test push origin &&\n     ++\tgit -C test branch --set-upstream-to upstream/ahead &&\n      +\tgit -C test reset --hard HEAD^ &&\n      +\tgit -C test status >actual &&\n      +\tcat >expect <<-EOF &&\n      +\tOn branch feature13\n     -+\tYour branch is up to date with ${SQ}upstream/main${SQ}.\n     ++\tYour branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.\n     ++\t  (use \"git pull\" to update your local branch)\n      +\n      +\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n      +\t  (use \"git pull origin feature13\" to update your local branch)\n     @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap\n      +\tEOF\n      +\ttest_cmp expect actual\n      +'\n     ++\n     ++test_expect_success 'status.compareBranches with behind push branch and no upstream' '\n     ++\ttest_config -C test push.default current &&\n     ++\ttest_config -C test remote.pushDefault origin &&\n     ++\ttest_config -C test status.compareBranches \"@{push}\" &&\n     ++\tgit -C test checkout --no-track -b feature15 upstream/main &&\n     ++\t(cd test && advance work15) &&\n     ++\tgit -C test push origin &&\n     ++\tgit -C test reset --hard HEAD^ &&\n     ++\tgit -C test status >actual &&\n     ++\tcat >expect <<-EOF &&\n     ++\tOn branch feature15\n     ++\tYour branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.\n     ++\t  (use \"git pull origin feature15\" to update your local branch)\n     ++\n     ++\tnothing to commit, working tree clean\n     ++\tEOF\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     ++test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '\n     ++\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n     ++\tgit -C test checkout -b feature16 origin/main &&\n     ++\t(cd test && advance work16) &&\n     ++\tgit -C test push origin HEAD:main &&\n     ++\tgit -C test reset --hard HEAD^ &&\n     ++\tgit -C test status >actual &&\n     ++\tcat >expect <<-EOF &&\n     ++\tOn branch feature16\n     ++\tYour branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.\n     ++\t  (use \"git pull\" to update your local branch)\n     ++\n     ++\tnothing to commit, working tree clean\n     ++\tEOF\n     ++\ttest_cmp expect actual\n     ++'\n      +\n       test_done\n\n\n remote.c                 | 46 +++++++++++++++++++-----\n t/t6040-tracking-info.sh | 78 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 115 insertions(+), 9 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex a664cd166a..2b82f6b312 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2267,6 +2267,8 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t\t     bool up_to_date,\n \t\t\t\t     int ours, int theirs,\n \t\t\t\t     const char *branch_name,\n+\t\t\t\t     const char *push_remote_name,\n+\t\t\t\t     const char *push_branch_name,\n \t\t\t\t     enum ahead_behind_flags abf,\n \t\t\t\t     unsigned flags)\n {\n@@ -2302,9 +2304,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"and can be fast-forwarded.\\n\",\n \t\t\t   theirs),\n \t\t\tbranch_name, theirs);\n-\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" to update your local branch)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\t}\n \t} else {\n \t\tstrbuf_addf(sb,\n \t\t\tQ_(\"Your branch and '%s' have diverged,\\n\"\n@@ -2315,9 +2323,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"respectively.\\n\",\n \t\t\t   ours + theirs),\n \t\t\tbranch_name, ours, theirs);\n-\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" if you want to integrate the remote branch with yours)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\t}\n \t}\n }\n \n@@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tint ours, theirs, cmp;\n \t\tint is_upstream, is_push;\n \t\tunsigned flags = 0;\n+\t\tconst char *push_remote_name = NULL;\n+\t\tconst char *push_branch_name = NULL;\n \n \t\tfull_ref = resolve_compare_branch(branch,\n \t\t\t\t\t\t  branches.items[i].string);\n@@ -2396,13 +2412,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tif (reported)\n \t\t\tstrbuf_addstr(sb, \"\\n\");\n \n-\t\tif (is_upstream)\n+\t\tif (is_upstream || is_push)\n \t\t\tflags |= ENABLE_ADVICE_PULL;\n-\t\tif (is_push)\n-\t\t\tflags |= ENABLE_ADVICE_PUSH;\n \t\tif (show_divergence_advice && is_upstream)\n \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n+\t\tif (is_push) {\n+\t\t\tflags |= ENABLE_ADVICE_PUSH;\n+\t\t\tif (!upstream_ref || strcmp(upstream_ref, full_ref)) {\n+\t\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n+\t\t\t\tif (push_remote_name &&\n+\t\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n+\t\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n+\t\t\t\t    *push_branch_name == '/')\n+\t\t\t\t\tpush_branch_name++;\n+\t\t\t\telse\n+\t\t\t\t\tpush_remote_name = NULL;\n+\t\t\t}\n+\t\t}\n \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n+\t\t\t\t\t push_remote_name, push_branch_name,\n \t\t\t\t\t abf, flags);\n \t\treported = 1;\n \ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0242b5bf7a..b613aba33a 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -646,4 +646,82 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'status.compareBranches behind both upstream and push' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature13 upstream/main &&\n+\t(cd test && advance work13) &&\n+\tgit -C test push origin &&\n+\tgit -C test branch --set-upstream-to upstream/ahead &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature13\n+\tYour branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull\" to update your local branch)\n+\n+\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature13\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout -b feature14 upstream/main &&\n+\t(cd test && advance work14) &&\n+\tgit -C test push &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature14\n+\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin remapped14\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with behind push branch and no upstream' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout --no-track -b feature15 upstream/main &&\n+\t(cd test && advance work15) &&\n+\tgit -C test push origin &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature15\n+\tYour branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature15\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature16 origin/main &&\n+\t(cd test && advance work16) &&\n+\tgit -C test push origin HEAD:main &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature16\n+\tYour branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\nbase-commit: 7bcaabddcf68bd0702697da5904c3b68c52f94cf\n-- \ngitgitgadget\n"},{"id":"543785","messageId":"xmqqy0hd18fn.fsf@gitster.g","threadId":"65627","inReplyTo":"pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] remote: qualify \"git pull\" advice for non-upstream compareBranches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-21T08:19:24Z","receivedAt":"2026-05-21T08:19:26Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n> +\t\t\tif (push_remote_name && push_branch_name)\n> +\t\t\t\tstrbuf_addf(sb,\n> +\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" if you want to integrate the remote branch with yours)\\n\"),\n> +\t\t\t\t\tpush_remote_name, push_branch_name);\n> +\t\t\telse\n> +\t\t\t\tstrbuf_addstr(sb,\n> +\t\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n\nHere is where \"git pull\" is suggested as a fallback when the history\nis diverged (e.g., you pushed and then you rebased).\n\n> +\t\t}\n>  \t}\n>  }\n>  \n> @@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n>  \t\tint ours, theirs, cmp;\n>  \t\tint is_upstream, is_push;\n>  \t\tunsigned flags = 0;\n> +\t\tconst char *push_remote_name = NULL;\n> +\t\tconst char *push_branch_name = NULL;\n>  \n>  \t\tfull_ref = resolve_compare_branch(branch,\n>  \t\t\t\t\t\t  branches.items[i].string);\n> @@ -2396,13 +2412,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n>  \t\tif (reported)\n>  \t\t\tstrbuf_addstr(sb, \"\\n\");\n>  \n> -\t\tif (is_upstream)\n> +\t\tif (is_upstream || is_push)\n>  \t\t\tflags |= ENABLE_ADVICE_PULL;\n> -\t\tif (is_push)\n> -\t\t\tflags |= ENABLE_ADVICE_PUSH;\n>  \t\tif (show_divergence_advice && is_upstream)\n>  \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n> +\t\tif (is_push) {\n> +\t\t\tflags |= ENABLE_ADVICE_PUSH;\n> +\t\t\tif (!upstream_ref || strcmp(upstream_ref, full_ref)) {\n> +\t\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n\nHere we _know_ that our repository has separate upstream and\npush/publish repositories.  But we may not be able to \"qualify\" it\nin the following \"if\" statement, in which case ...\n\n> +\t\t\t\tif (push_remote_name &&\n> +\t\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n> +\t\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n> +\t\t\t\t    *push_branch_name == '/')\n> +\t\t\t\t\tpush_branch_name++;\n> +\t\t\t\telse\n> +\t\t\t\t\tpush_remote_name = NULL;\n\n... we assign NULL to push_remote_name to \"punt\".\n\n> +\t\t\t}\n> +\t\t}\n\nWhich means that this call to the helper function cannot distinguish\nbetween the case where we were in \"push\" and pushing to the upstream\n(i.e., \"git pull\" without extra arguments is perfectly a sensible\nsuggestion) and the case where we were in \"push\", diverged, and\ntriangular (i.e., \"git pull\" with or without extra arguments is not\nan appropriate thing to suggest) but we cannot exactly tell what is\ngoing on.\n\nShoudln't the \"punt\" case refrain from suggesting \"git pull\"?\n\n>  \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n> +\t\t\t\t\t push_remote_name, push_branch_name,\n>  \t\t\t\t\t abf, flags);\n>  \t\treported = 1;\n>  \n> diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\n> index 0242b5bf7a..b613aba33a 100755\n> --- a/t/t6040-tracking-info.sh\n> +++ b/t/t6040-tracking-info.sh\n> @@ -646,4 +646,82 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'status.compareBranches behind both upstream and push' '\n> +\ttest_config -C test push.default current &&\n> +\ttest_config -C test remote.pushDefault origin &&\n> +\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n> +\tgit -C test checkout -b feature13 upstream/main &&\n> +\t(cd test && advance work13) &&\n> +\tgit -C test push origin &&\n> +\tgit -C test branch --set-upstream-to upstream/ahead &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature13\n> +\tYour branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull\" to update your local branch)\n> +\n> +\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull origin feature13\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n> +\ttest_config -C test remote.pushDefault origin &&\n> +\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n> +\ttest_config -C test status.compareBranches \"@{push}\" &&\n> +\tgit -C test checkout -b feature14 upstream/main &&\n> +\t(cd test && advance work14) &&\n> +\tgit -C test push &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature14\n> +\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull origin remapped14\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'status.compareBranches with behind push branch and no upstream' '\n> +\ttest_config -C test push.default current &&\n> +\ttest_config -C test remote.pushDefault origin &&\n> +\ttest_config -C test status.compareBranches \"@{push}\" &&\n> +\tgit -C test checkout --no-track -b feature15 upstream/main &&\n> +\t(cd test && advance work15) &&\n> +\tgit -C test push origin &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature15\n> +\tYour branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull origin feature15\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '\n> +\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n> +\tgit -C test checkout -b feature16 origin/main &&\n> +\t(cd test && advance work16) &&\n> +\tgit -C test push origin HEAD:main &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature16\n> +\tYour branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n>\n> base-commit: 7bcaabddcf68bd0702697da5904c3b68c52f94cf\n"},{"id":"543830","messageId":"pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com","threadId":"65627","inReplyTo":"pull.2301.v3.git.git.1779282625696.gitgitgadget@gmail.com","subject":"[PATCH v4] remote: qualify \"git pull\" advice for non-upstream compareBranches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-21T14:06:07Z","receivedAt":"2026-05-21T14:06:11Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nEnable ENABLE_ADVICE_PULL for push-branch comparisons too, not just\nthe upstream entry, so the \"use git pull\" hint prints when the local\nbranch is behind its push branch.\n\nSpell out \"git pull <remote> <branch>\" so running the suggested\ncommand actually pulls the ref the user was told about; plain\n\"git pull\" would fetch the upstream instead.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    remote: qualify \"git pull\" advice for non-upstream branches\n    \n     * Don't suggest git pull when we have no good command to suggest.\n     * New test for this. Asserts the behind line shows with no follow-up\n       advice.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v4\nPull-Request: https://github.com/git/git/pull/2301\n\nRange-diff vs v3:\n\n 1:  3703be9aac ! 1:  ef54dacb07 remote: qualify \"git pull\" advice for non-upstream compareBranches\n     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n       \t\tfull_ref = resolve_compare_branch(branch,\n       \t\t\t\t\t\t  branches.items[i].string);\n      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n     - \t\tif (reported)\n     - \t\t\tstrbuf_addstr(sb, \"\\n\");\n       \n     --\t\tif (is_upstream)\n     -+\t\tif (is_upstream || is_push)\n     + \t\tif (is_upstream)\n       \t\t\tflags |= ENABLE_ADVICE_PULL;\n      -\t\tif (is_push)\n      -\t\t\tflags |= ENABLE_ADVICE_PUSH;\n     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,\n      +\t\t\t\tif (push_remote_name &&\n      +\t\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n      +\t\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n     -+\t\t\t\t    *push_branch_name == '/')\n     ++\t\t\t\t    *push_branch_name == '/') {\n      +\t\t\t\t\tpush_branch_name++;\n     -+\t\t\t\telse\n     ++\t\t\t\t\tflags |= ENABLE_ADVICE_PULL;\n     ++\t\t\t\t} else {\n      +\t\t\t\t\tpush_remote_name = NULL;\n     ++\t\t\t\t}\n     ++\t\t\t} else {\n     ++\t\t\t\tflags |= ENABLE_ADVICE_PULL;\n      +\t\t\t}\n      +\t\t}\n       \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n     @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap\n      +\tEOF\n      +\ttest_cmp expect actual\n      +'\n     ++\n     ++test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '\n     ++\ttest_config -C test push.default current &&\n     ++\ttest_config -C test remote.imported.url ../. &&\n     ++\ttest_config -C test remote.imported.fetch \"+refs/heads/*:refs/imported/imported/*\" &&\n     ++\ttest_config -C test branch.feature17.pushRemote imported &&\n     ++\ttest_config -C test status.compareBranches \"@{push}\" &&\n     ++\tgit -C test fetch imported &&\n     ++\tgit -C test checkout --no-track -b feature17 refs/imported/imported/main &&\n     ++\t(cd test && advance work17) &&\n     ++\tgit -C test push imported HEAD:feature17 &&\n     ++\tgit -C test fetch imported &&\n     ++\tgit -C test reset --hard HEAD^ &&\n     ++\tgit -C test status >actual &&\n     ++\tcat >expect <<-EOF &&\n     ++\tOn branch feature17\n     ++\tYour branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.\n     ++\n     ++\tnothing to commit, working tree clean\n     ++\tEOF\n     ++\ttest_cmp expect actual\n     ++'\n      +\n       test_done\n\n\n remote.c                 |  48 +++++++++++++++----\n t/t6040-tracking-info.sh | 100 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 140 insertions(+), 8 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex 24a8118d25..193e1dd1f1 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2268,6 +2268,8 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t\t     bool up_to_date,\n \t\t\t\t     int ours, int theirs,\n \t\t\t\t     const char *branch_name,\n+\t\t\t\t     const char *push_remote_name,\n+\t\t\t\t     const char *push_branch_name,\n \t\t\t\t     enum ahead_behind_flags abf,\n \t\t\t\t     unsigned flags)\n {\n@@ -2303,9 +2305,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"and can be fast-forwarded.\\n\",\n \t\t\t   theirs),\n \t\t\tbranch_name, theirs);\n-\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\tif (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" to update your local branch)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" to update your local branch)\\n\"));\n+\t\t}\n \t} else {\n \t\tstrbuf_addf(sb,\n \t\t\tQ_(\"Your branch and '%s' have diverged,\\n\"\n@@ -2316,9 +2324,15 @@ static void format_branch_comparison(struct strbuf *sb,\n \t\t\t       \"respectively.\\n\",\n \t\t\t   ours + theirs),\n \t\t\tbranch_name, ours, theirs);\n-\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\tif (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {\n+\t\t\tif (push_remote_name && push_branch_name)\n+\t\t\t\tstrbuf_addf(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull %s %s\\\" if you want to integrate the remote branch with yours)\\n\"),\n+\t\t\t\t\tpush_remote_name, push_branch_name);\n+\t\t\telse\n+\t\t\t\tstrbuf_addstr(sb,\n+\t\t\t\t\t_(\"  (use \\\"git pull\\\" if you want to integrate the remote branch with yours)\\n\"));\n+\t\t}\n \t}\n }\n \n@@ -2356,6 +2370,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \t\tint ours, theirs, cmp;\n \t\tint is_upstream, is_push;\n \t\tunsigned flags = 0;\n+\t\tconst char *push_remote_name = NULL;\n+\t\tconst char *push_branch_name = NULL;\n \n \t\tfull_ref = resolve_compare_branch(branch,\n \t\t\t\t\t\t  branches.items[i].string);\n@@ -2399,11 +2415,27 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \n \t\tif (is_upstream)\n \t\t\tflags |= ENABLE_ADVICE_PULL;\n-\t\tif (is_push)\n-\t\t\tflags |= ENABLE_ADVICE_PUSH;\n \t\tif (show_divergence_advice && is_upstream)\n \t\t\tflags |= ENABLE_ADVICE_DIVERGENCE;\n+\t\tif (is_push) {\n+\t\t\tflags |= ENABLE_ADVICE_PUSH;\n+\t\t\tif (!upstream_ref || strcmp(upstream_ref, full_ref)) {\n+\t\t\t\tpush_remote_name = pushremote_for_branch(branch, NULL);\n+\t\t\t\tif (push_remote_name &&\n+\t\t\t\t    skip_prefix(full_ref, \"refs/remotes/\", &push_branch_name) &&\n+\t\t\t\t    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&\n+\t\t\t\t    *push_branch_name == '/') {\n+\t\t\t\t\tpush_branch_name++;\n+\t\t\t\t\tflags |= ENABLE_ADVICE_PULL;\n+\t\t\t\t} else {\n+\t\t\t\t\tpush_remote_name = NULL;\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tflags |= ENABLE_ADVICE_PULL;\n+\t\t\t}\n+\t\t}\n \t\tformat_branch_comparison(sb, !cmp, ours, theirs, short_ref,\n+\t\t\t\t\t push_remote_name, push_branch_name,\n \t\t\t\t\t abf, flags);\n \t\treported = 1;\n \ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0242b5bf7a..91cbb8775d 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'status.compareBranches behind both upstream and push' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature13 upstream/main &&\n+\t(cd test && advance work13) &&\n+\tgit -C test push origin &&\n+\tgit -C test branch --set-upstream-to upstream/ahead &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature13\n+\tYour branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull\" to update your local branch)\n+\n+\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature13\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout -b feature14 upstream/main &&\n+\t(cd test && advance work14) &&\n+\tgit -C test push &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature14\n+\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin remapped14\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches with behind push branch and no upstream' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.pushDefault origin &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test checkout --no-track -b feature15 upstream/main &&\n+\t(cd test && advance work15) &&\n+\tgit -C test push origin &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature15\n+\tYour branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull origin feature15\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '\n+\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n+\tgit -C test checkout -b feature16 origin/main &&\n+\t(cd test && advance work16) &&\n+\tgit -C test push origin HEAD:main &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature16\n+\tYour branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.\n+\t  (use \"git pull\" to update your local branch)\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '\n+\ttest_config -C test push.default current &&\n+\ttest_config -C test remote.imported.url ../. &&\n+\ttest_config -C test remote.imported.fetch \"+refs/heads/*:refs/imported/imported/*\" &&\n+\ttest_config -C test branch.feature17.pushRemote imported &&\n+\ttest_config -C test status.compareBranches \"@{push}\" &&\n+\tgit -C test fetch imported &&\n+\tgit -C test checkout --no-track -b feature17 refs/imported/imported/main &&\n+\t(cd test && advance work17) &&\n+\tgit -C test push imported HEAD:feature17 &&\n+\tgit -C test fetch imported &&\n+\tgit -C test reset --hard HEAD^ &&\n+\tgit -C test status >actual &&\n+\tcat >expect <<-EOF &&\n+\tOn branch feature17\n+\tYour branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\nbase-commit: aec3f587505a472db67e9462d0702e7d463a449d\n-- \ngitgitgadget\n"},{"id":"545168","messageId":"xmqq7bo6xuok.fsf@gitster.g","threadId":"65627","inReplyTo":"pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] remote: qualify \"git pull\" advice for non-upstream compareBranches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-10T15:48:59Z","receivedAt":"2026-06-10T15:49:01Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Enable ENABLE_ADVICE_PULL for push-branch comparisons too, not just\n> the upstream entry, so the \"use git pull\" hint prints when the local\n> branch is behind its push branch.\n>\n> Spell out \"git pull <remote> <branch>\" so running the suggested\n> command actually pulls the ref the user was told about; plain\n> \"git pull\" would fetch the upstream instead.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>     remote: qualify \"git pull\" advice for non-upstream branches\n>     \n>      * Don't suggest git pull when we have no good command to suggest.\n>      * New test for this. Asserts the behind line shows with no follow-up\n>        advice.\n\nVery well written.  \n\nWhat does not happen in the \"punt\" case (the first bullet point\nabove) may deserve to be given in the commit log message, but\notherwise it is very clear what the change wanted to do to the\nfuture readers of \"git log\".  \n\n>  remote.c                 |  48 +++++++++++++++----\n>  t/t6040-tracking-info.sh | 100 +++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 140 insertions(+), 8 deletions(-)\n\nThe code changes look correct, and the new tests checks the new\nsuggestion as well as the \"punt\" case, which is good.\n\nShall we mark it for 'next' now?\n\nThanks for working on this.\n\n>> diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\n> index 0242b5bf7a..91cbb8775d 100755\n> --- a/t/t6040-tracking-info.sh\n> +++ b/t/t6040-tracking-info.sh\n> @@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'status.compareBranches behind both upstream and push' '\n> +\ttest_config -C test push.default current &&\n> +\ttest_config -C test remote.pushDefault origin &&\n> +\ttest_config -C test status.compareBranches \"@{upstream} @{push}\" &&\n> +\tgit -C test checkout -b feature13 upstream/main &&\n> +\t(cd test && advance work13) &&\n> +\tgit -C test push origin &&\n> +\tgit -C test branch --set-upstream-to upstream/ahead &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature13\n> +\tYour branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull\" to update your local branch)\n> +\n> +\tYour branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull origin feature13\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nA good test that clearly shows how @{push} is described ;-)\n\n> +test_expect_success 'status.compareBranches with remapped push and behind push branch' '\n> +\ttest_config -C test remote.pushDefault origin &&\n> +\ttest_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&\n> +\ttest_config -C test status.compareBranches \"@{push}\" &&\n> +\tgit -C test checkout -b feature14 upstream/main &&\n> +\t(cd test && advance work14) &&\n> +\tgit -C test push &&\n> +\tgit -C test reset --hard HEAD^ &&\n> +\tgit -C test status >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tOn branch feature14\n> +\tYour branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.\n> +\t  (use \"git pull origin remapped14\" to update your local branch)\n> +\n> +\tnothing to commit, working tree clean\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nOK.\n"},{"id":"545175","messageId":"CAHwyqnXei114KYmbEOvMEYSkbrw0VHgrZJv52Y3qAhJ+Yq18_w@mail.gmail.com","threadId":"65627","inReplyTo":"xmqq7bo6xuok.fsf@gitster.g","subject":"Re: [PATCH v4] remote: qualify \"git pull\" advice for non-upstream compareBranches","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-10T17:35:19Z","receivedAt":"2026-06-10T17:35:57Z","isPatch":true,"body":"> >     remote: qualify \"git pull\" advice for non-upstream branches\n> >\n> >      * Don't suggest git pull when we have no good command to suggest.\n> >      * New test for this. Asserts the behind line shows with no follow-up\n> >        advice.\n>\n> Very well written.\n>\n> What does not happen in the \"punt\" case (the first bullet point\n> above) may deserve to be given in the commit log message, but\n> otherwise it is very clear what the change wanted to do to the\n> future readers of \"git log\".\n\nThanks. It's a good point, but maybe it's implicit that nothing\nhappens when nothing can happen?\n\n> Shall we mark it for 'next' now?\n>\n> Thanks for working on this.\n\nYes please, thanks for all the help!\n\n\nHarald\n"}]}