From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH 1/6] status: count push divergence outside the upstream
Date: Tue, 06 Oct 2026 07:08:19 +0000 [thread overview]
Message-ID: <e1651568b1923ab075542e2c5027917bae76c619.1791270504.git.gitgitgadget@gmail.com> (raw)
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
next prev parent reply other threads:[~2026-10-06 7:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 7:08 [PATCH 0/6] status, push: handle a push branch rebased onto a newer upstream Harald Nordgren via GitGitGadget
2026-10-06 7:08 ` Harald Nordgren via GitGitGadget [this message]
2026-10-06 7:08 ` [PATCH 2/6] status: say when the push branch was rebased cleanly Harald Nordgren via GitGitGadget
2026-10-06 7:08 ` [PATCH 3/6] status: suggest a force push after a clean rebase Harald Nordgren via GitGitGadget
2026-10-06 7:08 ` [PATCH 4/6] push: name the branch to pull from when it is not the upstream Harald Nordgren via GitGitGadget
2026-10-06 7:08 ` [PATCH 5/6] push: offer a force push after rewriting pushed commits Harald Nordgren via GitGitGadget
2026-10-06 7:08 ` [PATCH 6/6] push: suggest a force push after a clean rebase Harald Nordgren via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e1651568b1923ab075542e2c5027917bae76c619.1791270504.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox