[PATCH 1/6] status: count push divergence outside the upstream
- From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
- Date
- Oct 6, 2026, 07:08 UTC
- Message-ID
- <e1651568b1923ab075542e2c5027917bae76c619.1791270504.git.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.2437.git.git.1791270504.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
After rebasing onto a newer upstream, "git status" counts every commit the upstream gained since the last push as a difference from the push branch when status.compareBranches includes "@{push}". That can be many commits, even when only a few of your own differ.
Keep the full counts, and when they include commits from the upstream, add how many of them are not in it:
Your branch and 'origin/topic' have diverged, and have 51 and 1 different commits each (1 and 1 not in 'upstream/main').
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> --- remote.c | 81 +++++++++++++++++++++++++++++++++------- t/t6040-tracking-info.sh | 32 ++++++++++++++++ 2 files changed, 100 insertions(+), 13 deletions(-)
diff --git a/remote.c b/remote.c index fe62068463..cbe79275f7 100644 --- a/remote.c +++ b/remote.c @@ -2247,11 +2247,12 @@ int resolve_remote_symref(struct ref *ref, struct ref *list) */ static int stat_branch_pair(const char *branch_name, const char *base, + const char *exclude, int *num_ours, int *num_theirs, enum ahead_behind_flags abf) { struct object_id oid; - struct commit *ours, *theirs; + struct commit *ours, *theirs, *excluded = NULL; struct rev_info revs; struct strvec argv = STRVEC_INIT; @@ -2268,6 +2269,14 @@ static int stat_branch_pair(const char *branch_name, const char *base, if (!ours) return -1; + if (exclude) { + if (refs_read_ref(get_main_ref_store(the_repository), exclude, &oid)) + return -1; + excluded = lookup_commit_reference(the_repository, &oid); + if (!excluded) + return -1; + } + *num_theirs = *num_ours = 0; /* are we the same? */ @@ -2284,6 +2293,8 @@ static int stat_branch_pair(const char *branch_name, const char *base, strvec_pushf(&argv, "%s...%s", oid_to_hex(&ours->object.oid), oid_to_hex(&theirs->object.oid)); + if (excluded) + strvec_pushf(&argv, "^%s", oid_to_hex(&excluded->object.oid)); strvec_push(&argv, "--"); repo_init_revisions(the_repository, &revs, NULL); @@ -2305,6 +2316,8 @@ static int stat_branch_pair(const char *branch_name, const char *base, /* clear object flags smudged by the above traversal */ clear_commit_marks(ours, ALL_REV_FLAGS); clear_commit_marks(theirs, ALL_REV_FLAGS); + if (excluded) + clear_commit_marks(excluded, ALL_REV_FLAGS); strvec_clear(&argv); release_revisions(&revs); @@ -2344,7 +2357,23 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, if (!base) return -1; - return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf); + return stat_branch_pair(branch->refname, base, NULL, + num_ours, num_theirs, abf); +} + +/* + * Count the commits that differ between branch_name and base but are not + * in upstream. Return false when they cannot be counted or upstream + * accounts for none of the ours and theirs commits. + */ +static bool stat_outside_upstream(const char *branch_name, const char *base, + const char *upstream, int ours, int theirs, + int *ours_unmerged, int *theirs_unmerged) +{ + if (stat_branch_pair(branch_name, base, upstream, ours_unmerged, + theirs_unmerged, AHEAD_BEHIND_FULL) < 0) + return false; + return *ours_unmerged != ours || *theirs_unmerged != theirs; } static char *resolve_compare_branch(struct branch *branch, const char *name) @@ -2376,6 +2405,8 @@ static void format_branch_comparison(struct strbuf *sb, const char *branch_name, const char *push_remote_name, const char *push_branch_name, + const char *upstream_name, + int ours_unmerged, int theirs_unmerged, enum ahead_behind_flags abf, unsigned flags) { @@ -2421,15 +2452,27 @@ static void format_branch_comparison(struct strbuf *sb, _(" (use \"git pull\" to update your local branch)\n")); } } else { - strbuf_addf(sb, - Q_("Your branch and '%s' have diverged,\n" - "and have %d and %d different commit each, " - "respectively.\n", - "Your branch and '%s' have diverged,\n" - "and have %d and %d different commits each, " - "respectively.\n", - ours + theirs), - branch_name, ours, theirs); + if (upstream_name) + strbuf_addf(sb, + Q_("Your branch and '%s' have diverged,\n" + "and have %d and %d different commit each " + "(%d and %d not in '%s').\n", + "Your branch and '%s' have diverged,\n" + "and have %d and %d different commits each " + "(%d and %d not in '%s').\n", + ours + theirs), + branch_name, ours, theirs, + ours_unmerged, theirs_unmerged, upstream_name); + else + strbuf_addf(sb, + Q_("Your branch and '%s' have diverged,\n" + "and have %d and %d different commit each, " + "respectively.\n", + "Your branch and '%s' have diverged,\n" + "and have %d and %d different commits each, " + "respectively.\n", + ours + theirs), + branch_name, ours, theirs); if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) { if (push_remote_name && push_branch_name) strbuf_addf(sb, @@ -2473,7 +2516,9 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, for (i = 0; i < branches.nr; i++) { char *full_ref; char *short_ref; + char *upstream_name = NULL; int ours, theirs, cmp; + int ours_unmerged = 0, theirs_unmerged = 0; int is_upstream, is_push; unsigned flags = 0; const char *push_remote_name = NULL; @@ -2498,9 +2543,17 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref))) is_push = 1; - cmp = stat_branch_pair(branch->refname, full_ref, + cmp = stat_branch_pair(branch->refname, full_ref, NULL, &ours, &theirs, abf); + if (cmp > 0 && ours && theirs && upstream_ref && !is_upstream && + stat_outside_upstream(branch->refname, full_ref, + upstream_ref, ours, theirs, + &ours_unmerged, &theirs_unmerged)) + upstream_name = refs_shorten_unambiguous_ref( + get_main_ref_store(the_repository), + upstream_ref, 0); + if (cmp < 0) { if (is_upstream) { strbuf_addf(sb, @@ -2542,11 +2595,13 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, } format_branch_comparison(sb, !cmp, ours, theirs, short_ref, push_remote_name, push_branch_name, - abf, flags); + upstream_name, ours_unmerged, + theirs_unmerged, abf, flags); reported = 1; free(full_ref); free(short_ref); + free(upstream_name); } string_list_clear(&branches, 0); diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index e95d420972..f8df16a5de 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -746,4 +746,36 @@ test_expect_success 'status.compareBranches suppresses advice when push tracking test_cmp expect actual ' +test_expect_success 'status.compareBranches counts push divergence outside upstream' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{upstream} @{push}" && + ( + cd test && + git checkout -b feature18 origin/main && + advance work18 && + git push + ) && + git checkout main && + advance main18a && + advance main18b && + git checkout - && + ( + cd test && + echo amended >work18 && + git commit -a --amend --no-edit && + git pull --rebase && + git status >../actual + ) && + cat >expect <<-EOF && + On branch feature18 + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. + + Your branch and ${SQ}origin/feature18${SQ} have diverged, + and have 3 and 1 different commits each (1 and 1 not in ${SQ}origin/main${SQ}). + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + test_done
-- gitgitgadget