From: Phillip Wood Date: Fri, 09 Jan 2026 14:56:38 GMT Subject: Re: [PATCH v17 2/2] status: show comparison with push remote tracking branch Message-ID: In-Reply-To: <1348542edced5d17c1220ee6bd30afd943acdbcf.1767608269.git.gitgitgadget@gmail.com> Hi Harald On 05/01/2026 10:17, Harald Nordgren via GitGitGadget wrote: > From: Harald Nordgren > > "git status" on a branch that follows a remote branch compares > commits on the current branch and the remote-tracking branch it > builds upon, to show "ahead", "behind", or "diverged" status. > > When working on a feature branch that tracks a remote feature branch, > but you also want to track progress relative to the push destination > tracking branch (which may differ from the upstream branch), git status > now shows an additional comparison. > > When the upstream tracking branch differs from the push destination > tracking branch, git status shows both the comparison with the upstream > tracking branch (as before) and an additional comparison with the push > destination tracking branch. The push branch comparison appears on a > separate line after the upstream branch status, using the same format. > > Example output when tracking origin/main but push destination is > origin/feature: > On branch feature > Your branch and 'origin/main' have diverged, > and have 3 and 1 different commits each, respectively. > (use "git pull" if you want to integrate the remote branch with yours) > > Your branch is ahead of 'origin/feature' by 1 commit. > (use "git push" to publish your local commits) The advice looks good > The comparison is only shown when the push destination tracking branch > differs from the upstream tracking branch, even if they are on the same > remote. Sounds sensible > diff --git a/remote.c b/remote.c > index 7163a8ec28..1f87b85b22 100644 > --- a/remote.c > +++ b/remote.c > @@ -29,6 +29,11 @@ > > enum map_direction { FROM_SRC, FROM_DST }; > > +enum branch_mode_flags { > + BRANCH_MODE_PULL = (1 << 0), > + BRANCH_MODE_PUSH = (1 << 1), > +}; Using an enum for a set of flags is a bit confusing. > +static char *get_remote_push_branch(struct branch *branch, char **full_ref_out) > +{ > + struct remote *remote; > + const char *push_remote; > + char *push_dst = NULL; > + char *tracking_ref; > + const char *resolved; > + char *ret; > + > + if (!branch) > + return NULL; > + > + push_remote = pushremote_for_branch(branch, NULL); > + if (!push_remote) > + return NULL; > + > + remote = remotes_remote_get(the_repository, push_remote); > + if (!remote) > + return NULL; > + > + push_dst = remote_ref_for_branch(branch, 1); > + if (!push_dst) { > + if (remote->push.nr) > + return NULL; > + push_dst = xstrdup(branch->refname); > + } > + > + tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL); > + free(push_dst); On reflection I wonder if we should be calling branch_get_push() instead of remote_ref_for_branch() and tracking_for_push_dest() as it respects 'push.default' and so the branch it returns is the one that "git push" without any arguments would push to. > + if (!tracking_ref) > + return NULL; > + > + resolved = refs_resolve_ref_unsafe( > + get_main_ref_store(the_repository), > + tracking_ref, > + RESOLVE_REF_READING, > + NULL, NULL); > + > + if (!resolved) { > + free(tracking_ref); > + return NULL; > + } > + > + if (full_ref_out) I think it would be simpler to just return the full refname and let the caller shorten it. > + *full_ref_out = xstrdup(resolved); > + > + ret = refs_shorten_unambiguous_ref( > + get_main_ref_store(the_repository), resolved, 0); > + free(tracking_ref); > + return ret; > +} > + > static void format_branch_comparison(struct strbuf *sb, > int sti, > int ours, int theirs, > const char *branch_name, > enum ahead_behind_flags abf, > + enum branch_mode_flags advice_flags, > int show_divergence_advice) > { > if (!sti) { > @@ -2252,7 +2311,8 @@ static void format_branch_comparison(struct strbuf *sb, > strbuf_addf(sb, > _("Your branch and '%s' refer to different commits.\n"), > branch_name); > - if (advice_enabled(ADVICE_STATUS_HINTS)) > + if ((advice_flags & BRANCH_MODE_PUSH) && Why are we checking for BRANCH_MODE_PUSH here? Don't we want to show this advice regardless of the mode? > + advice_enabled(ADVICE_STATUS_HINTS)) > strbuf_addf(sb, _(" (use \"%s\" for details)\n"), > "git status --ahead-behind"); > } else if (!theirs) { > @@ -2261,7 +2321,8 @@ static void format_branch_comparison(struct strbuf *sb, > "Your branch is ahead of '%s' by %d commits.\n", > ours), > branch_name, ours); > - if (advice_enabled(ADVICE_STATUS_HINTS)) > + if ((advice_flags & BRANCH_MODE_PUSH) && > + advice_enabled(ADVICE_STATUS_HINTS)) Having to test the flags each time is a bit cumbersome. We could define a couple of local variables to simplify this bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) && advice_enabled(ADVICE_STATUS_HINTS); bool want_pull_advice = advice_flags & BRANCH_MODE_PULL && advice_enabled(ADVICE_STATUS_HINTS); Then we can simplify the above to if (want_push_advice) > strbuf_addstr(sb, > _(" (use \"git push\" to publish your local commits)\n")); > } else if (!ours) { > [...] > @@ -2285,7 +2347,8 @@ static void format_branch_comparison(struct strbuf *sb, > "respectively.\n", > ours + theirs), > branch_name, ours, theirs); > - if (show_divergence_advice && > + if ((advice_flags & BRANCH_MODE_PULL) && > + show_divergence_advice && If we don't want to show this can't we set show_divergance_adivce to false when we call this function - why is it guarded by BRANCH_MODE_PULL as well? > advice_enabled(ADVICE_STATUS_HINTS)) > strbuf_addstr(sb, > _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n")); > @@ -2303,6 +2366,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, > const char *full_base; > char *base; > int upstream_is_gone = 0; > + enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH; Here we set an enum to a value that is not a member of the enum. > + int push_ours, push_theirs, push_sti; > + char *full_push = NULL; > + char *push = NULL; > + enum branch_mode_flags push_branch_modes = 0; > > sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf); > if (sti < 0) { > @@ -2314,6 +2382,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, > base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), > full_base, 0); > > + push = get_remote_push_branch(branch, &full_push); > + if (push && strcmp(base, push)) { This is good - we only show the push branch separately if it differs from the upstream branch. > + push_sti = stat_branch_pair(branch->refname, full_push, > + &push_ours, &push_theirs, abf); > + if (push_sti >= 0) { > + base_branch_modes = BRANCH_MODE_PULL; > + push_branch_modes = BRANCH_MODE_PUSH; > + } This combined with checking "push_branch_modes & BRANCH_MODE_PUSH" below ensures we skip the push branch if push_sti < 0. That's good but it is a bit hard to follow. Thanks Phillip > + } > + > if (upstream_is_gone) { > strbuf_addf(sb, > _("Your branch is based on '%s', but the upstream is gone.\n"), > @@ -2322,10 +2400,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, > strbuf_addstr(sb, > _(" (use \"git branch --unset-upstream\" to fixup)\n")); > } else { > - format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice); > + format_branch_comparison(sb, sti, ours, theirs, base, abf, > + base_branch_modes, show_divergence_advice); > + } > + > + if (push_branch_modes & BRANCH_MODE_PUSH) { > + strbuf_addstr(sb, "\n"); > + format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf, > + push_branch_modes, 0); > } > > free(base); > + free(full_push); > + free(push); > return 1; > } > > diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh > index 0b719bbae6..cf5a926dcd 100755 > --- a/t/t6040-tracking-info.sh > +++ b/t/t6040-tracking-info.sh > @@ -292,4 +292,266 @@ test_expect_success '--set-upstream-to @{-1}' ' > test_cmp expect actual > ' > > +test_expect_success 'status tracking origin/main shows only main' ' > + ( > + cd test && > + git checkout b4 && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch b4 > + Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' ' > + ( > + cd test && > + git checkout b4 && > + git status --no-ahead-behind >../actual > + ) && > + cat >expect <<-EOF && > + On branch b4 > + Your branch and ${SQ}origin/main${SQ} refer to different commits. > + (use "git status --ahead-behind" for details) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status shows ahead of both origin/main and feature branch' ' > + ( > + cd test && > + git checkout -b feature2 origin/main && > + git push origin HEAD && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature2 > + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. > + > + Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'checkout shows ahead of both origin/main and feature branch' ' > + ( > + cd test && > + git checkout feature2 >../actual > + ) && > + cat >expect <<-EOF && > + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. > + > + Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'setup for ahead of tracked but diverged from main' ' > + ( > + cd test && > + git checkout -b feature4 origin/main && > + advance work1 && > + git checkout origin/main && > + advance work2 && > + git push origin HEAD:main && > + git checkout feature4 && > + advance work3 > + ) > +' > + > +test_expect_success 'status shows diverged from origin/main and ahead of feature branch' ' > + ( > + cd test && > + git checkout feature4 && > + git branch --set-upstream-to origin/main && > + git push origin HEAD && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature4 > + Your branch and ${SQ}origin/main${SQ} have diverged, > + and have 3 and 1 different commits each, respectively. > + (use "git pull" if you want to integrate the remote branch with yours) > + > + Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status --no-ahead-behind shows diverged from origin/main and ahead of feature branch' ' > + ( > + cd test && > + git checkout feature4 && > + git status --no-ahead-behind >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature4 > + Your branch and ${SQ}origin/main${SQ} refer to different commits. > + > + Your branch and ${SQ}origin/feature4${SQ} refer to different commits. > + (use "git status --ahead-behind" for details) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'setup upstream remote' ' > + ( > + cd test && > + git remote add upstream ../. && > + git fetch upstream && > + git config remote.pushDefault origin > + ) > +' > + > +test_expect_success 'status with upstream remote and push.default set to origin' ' > + ( > + cd test && > + git checkout -b feature5 upstream/main && > + git push origin && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature5 > + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. > + > + Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status with upstream remote and push.default set to origin and diverged' ' > + ( > + cd test && > + git checkout -b feature6 upstream/main && > + advance work && > + git push origin && > + git reset --hard upstream/main && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature6 > + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. > + > + Your branch and ${SQ}origin/feature6${SQ} have diverged, > + and have 1 and 1 different commits each, respectively. > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status with upstream remote and push branch up to date' ' > + ( > + cd test && > + git checkout -b feature7 upstream/main && > + git push origin && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature7 > + Your branch is up to date with ${SQ}upstream/main${SQ}. > + > + Your branch is up to date with ${SQ}origin/feature7${SQ}. > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status --no-ahead-behind with upstream remote and push branch up to date' ' > + ( > + cd test && > + git checkout feature7 && > + git push origin && > + git status --no-ahead-behind >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature7 > + Your branch is up to date with ${SQ}upstream/main${SQ}. > + > + Your branch is up to date with ${SQ}origin/feature7${SQ}. > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'checkout shows push branch up to date' ' > + ( > + cd test && > + git checkout feature7 >../actual > + ) && > + cat >expect <<-EOF && > + Your branch is up to date with ${SQ}upstream/main${SQ}. > + > + Your branch is up to date with ${SQ}origin/feature7${SQ}. > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status shows remapped push refspec' ' > + ( > + cd test && > + git checkout -b feature8 origin/main && > + git config remote.origin.push refs/heads/feature8:refs/heads/remapped && > + git push && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature8 > + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. > + > + Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > +test_expect_success 'status shows remapped push refspec with upstream remote' ' > + ( > + cd test && > + git checkout -b feature9 upstream/main && > + git config remote.origin.push refs/heads/feature9:refs/heads/remapped && > + git push origin && > + advance work && > + git status >../actual > + ) && > + cat >expect <<-EOF && > + On branch feature9 > + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. > + > + Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit. > + (use "git push" to publish your local commits) > + > + nothing to commit, working tree clean > + EOF > + test_cmp expect actual > +' > + > test_done