remote: qualify "git pull" advice for non-upstream compareBranches

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> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Harald Nordgren committed May 21, 2026 at 14:06 UTC 4b0a8b2506494323ae5c209ee7df6c08fec3831d
2 files changed +140 -8
remote.c
+40 -8
@@ -2267,6 +2267,8 @@ static void format_branch_comparison(struct strbuf *sb,
2267 bool up_to_date,
2268 int ours, int theirs,
2269 const char *branch_name,
2270 + const char *push_remote_name,
2271 + const char *push_branch_name,
2272 enum ahead_behind_flags abf,
2273 unsigned flags)
2274 {
@@ -2302,9 +2304,15 @@ static void format_branch_comparison(struct strbuf *sb,
2304 "and can be fast-forwarded.\n",
2305 theirs),
2306 branch_name, theirs);
2305 - if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
2306 - strbuf_addstr(sb,
2307 - _(" (use \"git pull\" to update your local branch)\n"));
2307 + if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
2308 + if (push_remote_name && push_branch_name)
2309 + strbuf_addf(sb,
2310 + _(" (use \"git pull %s %s\" to update your local branch)\n"),
2311 + push_remote_name, push_branch_name);
2312 + else
2313 + strbuf_addstr(sb,
2314 + _(" (use \"git pull\" to update your local branch)\n"));
2315 + }
2316 } else {
2317 strbuf_addf(sb,
2318 Q_("Your branch and '%s' have diverged,\n"
@@ -2315,9 +2323,15 @@ static void format_branch_comparison(struct strbuf *sb,
2323 "respectively.\n",
2324 ours + theirs),
2325 branch_name, ours, theirs);
2318 - if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
2319 - strbuf_addstr(sb,
2320 - _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
2326 + if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
2327 + if (push_remote_name && push_branch_name)
2328 + strbuf_addf(sb,
2329 + _(" (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"),
2330 + push_remote_name, push_branch_name);
2331 + else
2332 + strbuf_addstr(sb,
2333 + _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
2334 + }
2335 }
2336 }
2337
@@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
2369 int ours, theirs, cmp;
2370 int is_upstream, is_push;
2371 unsigned flags = 0;
2372 + const char *push_remote_name = NULL;
2373 + const char *push_branch_name = NULL;
2374
2375 full_ref = resolve_compare_branch(branch,
2376 branches.items[i].string);
@@ -2398,11 +2414,27 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
2414
2415 if (is_upstream)
2416 flags |= ENABLE_ADVICE_PULL;
2401 - if (is_push)
2402 - flags |= ENABLE_ADVICE_PUSH;
2417 if (show_divergence_advice && is_upstream)
2418 flags |= ENABLE_ADVICE_DIVERGENCE;
2419 + if (is_push) {
2420 + flags |= ENABLE_ADVICE_PUSH;
2421 + if (!upstream_ref || strcmp(upstream_ref, full_ref)) {
2422 + push_remote_name = pushremote_for_branch(branch, NULL);
2423 + if (push_remote_name &&
2424 + skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
2425 + skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
2426 + *push_branch_name == '/') {
2427 + push_branch_name++;
2428 + flags |= ENABLE_ADVICE_PULL;
2429 + } else {
2430 + push_remote_name = NULL;
2431 + }
2432 + } else {
2433 + flags |= ENABLE_ADVICE_PULL;
2434 + }
2435 + }
2436 format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
2437 + push_remote_name, push_branch_name,
2438 abf, flags);
2439 reported = 1;
2440
t/t6040-tracking-info.sh
+100
@@ -646,4 +646,104 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo
646 test_cmp expect actual
647 '
648
649 +test_expect_success 'status.compareBranches behind both upstream and push' '
650 + test_config -C test push.default current &&
651 + test_config -C test remote.pushDefault origin &&
652 + test_config -C test status.compareBranches "@{upstream} @{push}" &&
653 + git -C test checkout -b feature13 upstream/main &&
654 + (cd test && advance work13) &&
655 + git -C test push origin &&
656 + git -C test branch --set-upstream-to upstream/ahead &&
657 + git -C test reset --hard HEAD^ &&
658 + git -C test status >actual &&
659 + cat >expect <<-EOF &&
660 + On branch feature13
661 + Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded.
662 + (use "git pull" to update your local branch)
663 +
664 + Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.
665 + (use "git pull origin feature13" to update your local branch)
666 +
667 + nothing to commit, working tree clean
668 + EOF
669 + test_cmp expect actual
670 +'
671 +
672 +test_expect_success 'status.compareBranches with remapped push and behind push branch' '
673 + test_config -C test remote.pushDefault origin &&
674 + test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&
675 + test_config -C test status.compareBranches "@{push}" &&
676 + git -C test checkout -b feature14 upstream/main &&
677 + (cd test && advance work14) &&
678 + git -C test push &&
679 + git -C test reset --hard HEAD^ &&
680 + git -C test status >actual &&
681 + cat >expect <<-EOF &&
682 + On branch feature14
683 + Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.
684 + (use "git pull origin remapped14" to update your local branch)
685 +
686 + nothing to commit, working tree clean
687 + EOF
688 + test_cmp expect actual
689 +'
690 +
691 +test_expect_success 'status.compareBranches with behind push branch and no upstream' '
692 + test_config -C test push.default current &&
693 + test_config -C test remote.pushDefault origin &&
694 + test_config -C test status.compareBranches "@{push}" &&
695 + git -C test checkout --no-track -b feature15 upstream/main &&
696 + (cd test && advance work15) &&
697 + git -C test push origin &&
698 + git -C test reset --hard HEAD^ &&
699 + git -C test status >actual &&
700 + cat >expect <<-EOF &&
701 + On branch feature15
702 + Your branch is behind ${SQ}origin/feature15${SQ} by 1 commit, and can be fast-forwarded.
703 + (use "git pull origin feature15" to update your local branch)
704 +
705 + nothing to commit, working tree clean
706 + EOF
707 + test_cmp expect actual
708 +'
709 +
710 +test_expect_success 'status.compareBranches behind upstream-equals-push suggests plain pull' '
711 + test_config -C test status.compareBranches "@{upstream} @{push}" &&
712 + git -C test checkout -b feature16 origin/main &&
713 + (cd test && advance work16) &&
714 + git -C test push origin HEAD:main &&
715 + git -C test reset --hard HEAD^ &&
716 + git -C test status >actual &&
717 + cat >expect <<-EOF &&
718 + On branch feature16
719 + Your branch is behind ${SQ}origin/main${SQ} by 1 commit, and can be fast-forwarded.
720 + (use "git pull" to update your local branch)
721 +
722 + nothing to commit, working tree clean
723 + EOF
724 + test_cmp expect actual
725 +'
726 +
727 +test_expect_success 'status.compareBranches suppresses advice when push tracking ref is unconventional' '
728 + test_config -C test push.default current &&
729 + test_config -C test remote.imported.url ../. &&
730 + test_config -C test remote.imported.fetch "+refs/heads/*:refs/imported/imported/*" &&
731 + test_config -C test branch.feature17.pushRemote imported &&
732 + test_config -C test status.compareBranches "@{push}" &&
733 + git -C test fetch imported &&
734 + git -C test checkout --no-track -b feature17 refs/imported/imported/main &&
735 + (cd test && advance work17) &&
736 + git -C test push imported HEAD:feature17 &&
737 + git -C test fetch imported &&
738 + git -C test reset --hard HEAD^ &&
739 + git -C test status >actual &&
740 + cat >expect <<-EOF &&
741 + On branch feature17
742 + Your branch is behind ${SQ}imported/imported/feature17${SQ} by 1 commit, and can be fast-forwarded.
743 +
744 + nothing to commit, working tree clean
745 + EOF
746 + test_cmp expect actual
747 +'
748 +
749 test_done