Subject: [PATCH v3] remote: qualify "git pull" advice for non-upstream compareBranches

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Wed, 20 May 2026 13:10:25 +0000

From: Harald Nordgren <[email-masked]>

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 <[email-masked]>
---
    remote: qualify "git pull" advice for non-upstream branches
    
     * Only suggest git pull <remote> <branch> when plain git pull wouldn't
       do the right thing.
     * Tests: when upstream and push are the same ref, the message stays
       plain git pull.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v3
Pull-Request: https://github.com/git/git/pull/2301

Range-diff vs v2:

 1:  1f06873f82 ! 1:  3703be9aac remote: qualify "git pull" advice for non-upstream branches
     @@ Metadata
      Author: Harald Nordgren <[email-masked]>
      
       ## Commit message ##
     -    remote: qualify "git pull" advice for non-upstream branches
     +    remote: qualify "git pull" advice for non-upstream compareBranches
      
     -    When "git status" reports the local branch is behind the push
     -    branch, the advice suggested a bare "git pull". That follows the
     -    upstream, which may live on a different remote, so emit
     -    "git pull <remote> <branch>" instead.
     +    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.
      
     -    Also enable the pull advice for push-branch comparisons; it was
     -    previously only set for the upstream.
     +    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 <[email-masked]>
      
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			flags |= ENABLE_ADVICE_DIVERGENCE;
      +		if (is_push) {
      +			flags |= ENABLE_ADVICE_PUSH;
     -+			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++;
     -+			else
     -+				push_remote_name = NULL;
     ++			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++;
     ++				else
     ++					push_remote_name = NULL;
     ++			}
      +		}
       		format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
      +					 push_remote_name, push_branch_name,
     @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap
       	test_cmp expect actual
       '
       
     -+test_expect_success 'status.compareBranches with behind push branch suggests qualified pull' '
     ++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 up to date with ${SQ}upstream/main${SQ}.
     ++	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)
     @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remap
      +	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_done

 remote.c                 | 46 +++++++++++++++++++-----
 t/t6040-tracking-info.sh | 78 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+), 9 deletions(-)

diff --git a/remote.c b/remote.c
index a664cd166a..2b82f6b312 100644
--- a/remote.c
+++ b/remote.c
@@ -2267,6 +2267,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)
 {
@@ -2302,9 +2304,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"
@@ -2315,9 +2323,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"));
+		}
 	}
 }
 
@@ -2355,6 +2369,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);
@@ -2396,13 +2412,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		if (reported)
 			strbuf_addstr(sb, "\n");
 
-		if (is_upstream)
+		if (is_upstream || is_push)
 			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++;
+				else
+					push_remote_name = NULL;
+			}
+		}
 		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..b613aba33a 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -646,4 +646,82 @@ 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_done

base-commit: 7bcaabddcf68bd0702697da5904c3b68c52f94cf
-- 
gitgitgadget