* [PATCH v6 1/6] status: show comparison with upstream default branch
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 2/6] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
` (6 subsequent siblings)
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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" (i.e. you have built new history,
while others are not touching it), "behind" (i.e. you haven't
added any work since you were in-sync, while others have added
their work on the branch), "diverged" (i.e. you have commits
that you haven't pushed out, while others have added commits).
When you fork a branch 'feature' from the 'main' branch of the
remote, but then create 'feature' branch at the remote and push
there, while you still occasionally pull from or rebase onto
their 'main', you'd also want to know how much you have diverged
from 'main', in addition to how your 'feature' and their
'feature' compares. Currently the comparison with 'main' is not
given, making it hard to know when to start thinking about
rebasing onto the upstream default branch.
Show two sets of comparison: one with the tracking branch (as
before), and another with the upstream's default branch (what
their HEAD points to, typically 'main' or 'master'). The latter
comparison appears on a separate line after the tracking branch
status, using the same format:
- "Ahead of 'origin/main' by N commits" when purely ahead
- "Behind 'origin/main' by N commits" when purely behind
- "Diverged from 'origin/main' by N commits" when diverged
Example output when tracking a feature branch:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
Ahead of 'origin/main' by 5 commits.
The upstream default branch is determined by checking symbolic refs:
1. refs/remotes/upstream/HEAD (if upstream remote exists)
2. refs/remotes/origin/HEAD (fallback)
This works with any default branch name (main, master, develop,
etc.) as long as the symbolic ref is configured. The comparison
is also shown when the branch is up-to-date with its tracking
branch but differs from the upstream default branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 104 +++++++++++++++++
t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
2 files changed, 350 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..b2a1e980b1 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,98 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_default_remote_ref(char **full_ref_out)
+{
+ int flag;
+ const char *resolved;
+ static const char *remotes[] = { "upstream", "origin", NULL };
+ int i;
+
+ for (i = 0; remotes[i]; i++) {
+ struct strbuf head_ref = STRBUF_INIT;
+ strbuf_addf(&head_ref, "refs/remotes/%s/HEAD", remotes[i]);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ head_ref.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ strbuf_release(&head_ref);
+
+ if (resolved && (flag & REF_ISSYMREF)) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ return refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+ }
+
+ return NULL;
+}
+
+static int is_default_remote_branch(const char *name)
+{
+ char *default_full = NULL;
+ char *default_short;
+ int result = 0;
+
+ default_short = get_default_remote_ref(&default_full);
+ if (!default_short)
+ return 0;
+
+ result = !strcmp(name, default_short);
+
+ free(default_short);
+ free(default_full);
+ return result;
+}
+
+static void format_default_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ enum ahead_behind_flags abf)
+{
+ int default_ours = 0, default_theirs = 0;
+ char *default_full = NULL;
+ char *default_short;
+
+ default_short = get_default_remote_ref(&default_full);
+ if (!default_short)
+ return;
+
+ if (stat_branch_pair(branch_refname, default_full,
+ &default_ours, &default_theirs, abf) <= 0) {
+ free(default_short);
+ free(default_full);
+ return;
+ }
+
+ strbuf_addstr(sb, "\n");
+
+ if (default_ours > 0 && default_theirs == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ default_ours),
+ default_short, default_ours);
+ } else if (default_theirs > 0 && default_ours == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ default_theirs),
+ default_short, default_theirs);
+ } else if (default_ours > 0 && default_theirs > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ default_ours + default_theirs),
+ default_short, default_ours + default_theirs);
+ }
+
+ free(default_short);
+ free(default_full);
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2248,6 +2340,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ int show_default_branch_comparison;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2258,6 +2351,9 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
+ show_default_branch_comparison = !is_default_remote_branch(base);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2269,6 +2365,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
base);
+ if (show_default_branch_comparison)
+ format_default_branch_comparison(sb, branch->refname, abf);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
@@ -2285,6 +2383,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
+ if (show_default_branch_comparison)
+ format_default_branch_comparison(sb, branch->refname, abf);
} else if (!ours) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
@@ -2296,6 +2396,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
+ if (show_default_branch_comparison)
+ format_default_branch_comparison(sb, branch->refname, abf);
} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
@@ -2310,6 +2412,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ if (show_default_branch_comparison)
+ format_default_branch_comparison(sb, branch->refname, abf);
}
free(base);
return 1;
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..e2bd48f858 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -292,4 +293,249 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
+test_expect_success 'setup for ahead of non-main tracking branch' '
+ (
+ cd test &&
+ git checkout -b feature origin/main &&
+ advance feature1 &&
+ git push origin feature &&
+ git checkout -b work --track origin/feature &&
+ advance work1 &&
+ advance work2
+ )
+'
+
+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git status --long -b | head -5
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
+ git checkout work 2>&1 | grep -E "(Switched|Your branch|Ahead of)" | head -3
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 >/dev/null &&
+ git status --long -b
+ ) >actual &&
+ test_grep "ahead of .origin/main. by 2 commits" actual &&
+ test_grep ! "Ahead of" actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout origin/main &&
+ git checkout -b oldfeature &&
+ advance oldfeature1 &&
+ git push origin oldfeature &&
+ git checkout origin/main &&
+ advance main_newer &&
+ git push origin HEAD:main &&
+ git checkout -b work2 --track origin/oldfeature &&
+ advance work2_commit
+ )
+'
+
+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
+ git status --long -b | head -5
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for diverged from tracked but behind main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b work2b &&
+ git branch --set-upstream-to=origin/oldfeature &&
+ git checkout origin/main &&
+ advance main_extra &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows diverged from tracked and behind origin/main' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
+ git status --long -b | head -6
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
+Your branch and '\''origin/oldfeature'\'' have diverged,
+and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for behind tracked but ahead of main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b feature3 &&
+ advance feature3_1 &&
+ advance feature3_2 &&
+ advance feature3_3 &&
+ git push origin feature3 &&
+ git checkout -b work3 --track origin/feature3 &&
+ git reset --hard HEAD~2
+ )
+'
+
+test_expect_success 'status shows behind tracked and ahead of origin/main' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
+ git status --long -b | head -5
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote preference' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ )
+'
+
+test_expect_success 'status prefers upstream remote over origin for comparison' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git status --long -b | head -5
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of default' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from default' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
+ git status --long -b | head -4
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
+ (
+ cd test &&
+ git remote remove upstream &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature2 --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead2 &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
+ git status --long -b | head -4
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature3 --track origin/feature
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git status --long -b | head -4
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+EOF
+ test_cmp expect actual
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v6 2/6] Simplify default branch comparison logic
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 1/6] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 3/6] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
` (5 subsequent siblings)
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
This maintains the same functionality while reducing ref resolution calls
from multiple to one, and eliminating unnecessary memory allocations.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 59 ++++++++++++++++++--------------------------------------
1 file changed, 19 insertions(+), 40 deletions(-)
diff --git a/remote.c b/remote.c
index b2a1e980b1..f3831ef3be 100644
--- a/remote.c
+++ b/remote.c
@@ -2267,41 +2267,17 @@ static char *get_default_remote_ref(char **full_ref_out)
return NULL;
}
-static int is_default_remote_branch(const char *name)
-{
- char *default_full = NULL;
- char *default_short;
- int result = 0;
-
- default_short = get_default_remote_ref(&default_full);
- if (!default_short)
- return 0;
-
- result = !strcmp(name, default_short);
-
- free(default_short);
- free(default_full);
- return result;
-}
-
static void format_default_branch_comparison(struct strbuf *sb,
const char *branch_refname,
+ const char *default_full,
+ const char *default_short,
enum ahead_behind_flags abf)
{
int default_ours = 0, default_theirs = 0;
- char *default_full = NULL;
- char *default_short;
-
- default_short = get_default_remote_ref(&default_full);
- if (!default_short)
- return;
if (stat_branch_pair(branch_refname, default_full,
- &default_ours, &default_theirs, abf) <= 0) {
- free(default_short);
- free(default_full);
+ &default_ours, &default_theirs, abf) <= 0)
return;
- }
strbuf_addstr(sb, "\n");
@@ -2324,9 +2300,6 @@ static void format_default_branch_comparison(struct strbuf *sb,
default_ours + default_theirs),
default_short, default_ours + default_theirs);
}
-
- free(default_short);
- free(default_full);
}
/*
@@ -2340,7 +2313,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
- int show_default_branch_comparison;
+ char *default_full = NULL;
+ char *default_short = NULL;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2352,7 +2326,13 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
- show_default_branch_comparison = !is_default_remote_branch(base);
+ default_short = get_default_remote_ref(&default_full);
+ if (default_short && !strcmp(base, default_short)) {
+ free(default_short);
+ free(default_full);
+ default_short = NULL;
+ default_full = NULL;
+ }
if (upstream_is_gone) {
strbuf_addf(sb,
@@ -2365,8 +2345,6 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
base);
- if (show_default_branch_comparison)
- format_default_branch_comparison(sb, branch->refname, abf);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
@@ -2383,8 +2361,6 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
- if (show_default_branch_comparison)
- format_default_branch_comparison(sb, branch->refname, abf);
} else if (!ours) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
@@ -2396,8 +2372,6 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
- if (show_default_branch_comparison)
- format_default_branch_comparison(sb, branch->refname, abf);
} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
@@ -2412,10 +2386,15 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
- if (show_default_branch_comparison)
- format_default_branch_comparison(sb, branch->refname, abf);
}
+
+ if (default_short && !upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK)
+ format_default_branch_comparison(sb, branch->refname, default_full,
+ default_short, abf);
+
free(base);
+ free(default_short);
+ free(default_full);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v6 3/6] Use repo.settings.statusGoalBranch config for status comparison
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 1/6] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 2/6] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 4/6] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
` (4 subsequent siblings)
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Replace hardcoded upstream/origin preference with configurable
repo.settings.statusGoalBranch setting. When unset, skip the
default branch comparison entirely.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 48 ++++++++++++++++-----------
t/t6040-tracking-info.sh | 71 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+), 19 deletions(-)
diff --git a/remote.c b/remote.c
index f3831ef3be..edb6d374e7 100644
--- a/remote.c
+++ b/remote.c
@@ -2239,32 +2239,42 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
static char *get_default_remote_ref(char **full_ref_out)
{
- int flag;
+ const char *config_value;
const char *resolved;
- static const char *remotes[] = { "upstream", "origin", NULL };
- int i;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *slash_pos;
+ char *ret = NULL;
- for (i = 0; remotes[i]; i++) {
- struct strbuf head_ref = STRBUF_INIT;
- strbuf_addf(&head_ref, "refs/remotes/%s/HEAD", remotes[i]);
+ if (repo_config_get_value(the_repository, "repo.settings.statusGoalBranch", &config_value))
+ return NULL;
- resolved = refs_resolve_ref_unsafe(
- get_main_ref_store(the_repository),
- head_ref.buf,
- RESOLVE_REF_READING,
- NULL, &flag);
+ if (!config_value || !*config_value)
+ return NULL;
- strbuf_release(&head_ref);
+ slash_pos = strchr(config_value, '/');
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1])
+ return NULL;
- if (resolved && (flag & REF_ISSYMREF)) {
- if (full_ref_out)
- *full_ref_out = xstrdup(resolved);
- return refs_shorten_unambiguous_ref(
- get_main_ref_store(the_repository), resolved, 0);
- }
+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
+ (int)(slash_pos - config_value), config_value,
+ slash_pos + 1);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
}
- return NULL;
+ strbuf_release(&ref_buf);
+ return ret;
}
static void format_default_branch_comparison(struct strbuf *sb,
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index e2bd48f858..00dadc03e7 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -309,6 +309,7 @@ test_expect_success 'status shows ahead of both tracked branch and origin/main'
(
cd test &&
git checkout work >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -5
) >actual &&
cat >expect <<-\EOF &&
@@ -325,6 +326,7 @@ test_expect_success 'checkout shows ahead of both tracked branch and origin/main
(
cd test &&
git checkout main >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git checkout work 2>&1 | grep -E "(Switched|Your branch|Ahead of)" | head -3
) >actual &&
cat >expect <<-\EOF &&
@@ -364,6 +366,7 @@ test_expect_success 'status shows ahead of tracked and diverged from origin/main
(
cd test &&
git checkout work2 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -5
) >actual &&
cat >expect <<-\EOF &&
@@ -393,6 +396,7 @@ test_expect_success 'status shows diverged from tracked and behind origin/main'
(
cd test &&
git checkout work2b >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -6
) >actual &&
cat >expect <<-\EOF &&
@@ -425,6 +429,7 @@ test_expect_success 'status shows behind tracked and ahead of origin/main' '
(
cd test &&
git checkout work3 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -5
) >actual &&
cat >expect <<-\EOF &&
@@ -450,6 +455,7 @@ test_expect_success 'status prefers upstream remote over origin for comparison'
(
cd test &&
git checkout work >/dev/null &&
+ git config repo.settings.statusGoalBranch upstream/main &&
git status --long -b | head -5
) >actual &&
cat >expect <<-\EOF &&
@@ -477,6 +483,7 @@ test_expect_success 'status shows up to date with tracked but diverged from defa
(
cd test &&
git checkout synced_feature >/dev/null &&
+ git config repo.settings.statusGoalBranch upstream/main &&
git status --long -b | head -4
) >actual &&
cat >expect <<-\EOF &&
@@ -504,6 +511,7 @@ test_expect_success 'status shows up to date with tracked but diverged from orig
(
cd test &&
git checkout synced_feature2 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -4
) >actual &&
cat >expect <<-\EOF &&
@@ -527,6 +535,7 @@ test_expect_success 'status shows up to date with tracked but shows default bran
(
cd test &&
git checkout synced_feature3 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
git status --long -b | head -4
) >actual &&
cat >expect <<-\EOF &&
@@ -538,4 +547,66 @@ EOF
test_cmp expect actual
'
+test_expect_success 'status with repo.settings.statusGoalBranch unset shows no default comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config --unset repo.settings.statusGoalBranch 2>/dev/null || true &&
+ git status --long -b | head -3
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with repo.settings.statusGoalBranch set uses configured branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/main &&
+ git status --long -b | head -4
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with repo.settings.statusGoalBranch set to different remote/branch' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/feature &&
+ git status --long -b | head -4
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with repo.settings.statusGoalBranch set to non-existent branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config repo.settings.statusGoalBranch origin/nonexistent &&
+ git status --long -b | head -3
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+EOF
+ test_cmp expect actual
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v6 4/6] Rename default_remote to goal_branch
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
` (2 preceding siblings ...)
2025-12-24 23:41 ` [PATCH v6 3/6] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 5/6] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
` (3 subsequent siblings)
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Rename variables and functions from 'default' to 'goal' to better
reflect that they represent the goal branch from config rather than
a default remote. Also move the goal branch lookup to where it's used.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 63 ++++++++++++++++++++++++++++----------------------------
1 file changed, 31 insertions(+), 32 deletions(-)
diff --git a/remote.c b/remote.c
index edb6d374e7..99c0e18df4 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,7 +2237,7 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-static char *get_default_remote_ref(char **full_ref_out)
+static char *get_goal_branch_ref(char **full_ref_out)
{
const char *config_value;
const char *resolved;
@@ -2253,8 +2253,11 @@ static char *get_default_remote_ref(char **full_ref_out)
return NULL;
slash_pos = strchr(config_value, '/');
- if (!slash_pos || slash_pos == config_value || !slash_pos[1])
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
+ warning(_("invalid value for repo.settings.statusGoalBranch: '%s' (expected format: remote/branch)"),
+ config_value);
return NULL;
+ }
strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
(int)(slash_pos - config_value), config_value,
@@ -2277,38 +2280,38 @@ static char *get_default_remote_ref(char **full_ref_out)
return ret;
}
-static void format_default_branch_comparison(struct strbuf *sb,
+static void format_goal_branch_comparison(struct strbuf *sb,
const char *branch_refname,
- const char *default_full,
- const char *default_short,
+ const char *goal_full,
+ const char *goal_short,
enum ahead_behind_flags abf)
{
- int default_ours = 0, default_theirs = 0;
+ int goal_ahead = 0, goal_behind = 0;
- if (stat_branch_pair(branch_refname, default_full,
- &default_ours, &default_theirs, abf) <= 0)
+ if (stat_branch_pair(branch_refname, goal_full,
+ &goal_ahead, &goal_behind, abf) <= 0)
return;
strbuf_addstr(sb, "\n");
- if (default_ours > 0 && default_theirs == 0) {
+ if (goal_ahead > 0 && goal_behind == 0) {
strbuf_addf(sb,
Q_("Ahead of '%s' by %d commit.\n",
"Ahead of '%s' by %d commits.\n",
- default_ours),
- default_short, default_ours);
- } else if (default_theirs > 0 && default_ours == 0) {
+ goal_ahead),
+ goal_short, goal_ahead);
+ } else if (goal_behind > 0 && goal_ahead == 0) {
strbuf_addf(sb,
Q_("Behind '%s' by %d commit.\n",
"Behind '%s' by %d commits.\n",
- default_theirs),
- default_short, default_theirs);
- } else if (default_ours > 0 && default_theirs > 0) {
+ goal_behind),
+ goal_short, goal_behind);
+ } else if (goal_ahead > 0 && goal_behind > 0) {
strbuf_addf(sb,
Q_("Diverged from '%s' by %d commit.\n",
"Diverged from '%s' by %d commits.\n",
- default_ours + default_theirs),
- default_short, default_ours + default_theirs);
+ goal_ahead + goal_behind),
+ goal_short, goal_ahead + goal_behind);
}
}
@@ -2323,8 +2326,6 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
- char *default_full = NULL;
- char *default_short = NULL;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2336,14 +2337,6 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
- default_short = get_default_remote_ref(&default_full);
- if (default_short && !strcmp(base, default_short)) {
- free(default_short);
- free(default_full);
- default_short = NULL;
- default_full = NULL;
- }
-
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2398,13 +2391,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
- if (default_short && !upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK)
- format_default_branch_comparison(sb, branch->refname, default_full,
- default_short, abf);
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *goal_full = NULL;
+ char *goal_short = get_goal_branch_ref(&goal_full);
+
+ if (goal_short && strcmp(base, goal_short))
+ format_goal_branch_comparison(sb, branch->refname, goal_full,
+ goal_short, abf);
+
+ free(goal_short);
+ free(goal_full);
+ }
free(base);
- free(default_short);
- free(default_full);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v6 5/6] Add warning for malformed statusGoalBranch config
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
` (3 preceding siblings ...)
2025-12-24 23:41 ` [PATCH v6 4/6] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-24 23:41 ` [PATCH v6 6/6] Change config key to status.compareBranch Harald Nordgren via GitGitGadget
` (2 subsequent siblings)
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Warn when repo.settings.statusGoalBranch has invalid format
and skip the goal branch comparison. Also update test to
compare full checkout output instead of grepping.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
t/t6040-tracking-info.sh | 49 +++++++++++++++++++++++++++++-----------
1 file changed, 36 insertions(+), 13 deletions(-)
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 00dadc03e7..598dd89483 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -310,7 +310,7 @@ test_expect_success 'status shows ahead of both tracked branch and origin/main'
cd test &&
git checkout work >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -5
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work
@@ -318,6 +318,8 @@ Your branch is ahead of '\''origin/feature'\'' by 2 commits.
(use "git push" to publish your local commits)
Ahead of '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -327,11 +329,13 @@ test_expect_success 'checkout shows ahead of both tracked branch and origin/main
cd test &&
git checkout main >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git checkout work 2>&1 | grep -E "(Switched|Your branch|Ahead of)" | head -3
+ git checkout work 2>&1
) >actual &&
cat >expect <<-\EOF &&
Switched to branch '\''work'\''
Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
Ahead of '\''origin/main'\'' by 3 commits.
EOF
test_cmp expect actual
@@ -367,7 +371,7 @@ test_expect_success 'status shows ahead of tracked and diverged from origin/main
cd test &&
git checkout work2 >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -5
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work2
@@ -375,6 +379,8 @@ Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
(use "git push" to publish your local commits)
Diverged from '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -397,7 +403,7 @@ test_expect_success 'status shows diverged from tracked and behind origin/main'
cd test &&
git checkout work2b >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -6
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work2b
@@ -406,6 +412,8 @@ and have 1 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)
Behind '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -430,7 +438,7 @@ test_expect_success 'status shows behind tracked and ahead of origin/main' '
cd test &&
git checkout work3 >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -5
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work3
@@ -438,6 +446,8 @@ Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forw
(use "git pull" to update your local branch)
Ahead of '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -456,7 +466,7 @@ test_expect_success 'status prefers upstream remote over origin for comparison'
cd test &&
git checkout work >/dev/null &&
git config repo.settings.statusGoalBranch upstream/main &&
- git status --long -b | head -5
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work
@@ -464,6 +474,8 @@ Your branch is ahead of '\''origin/feature'\'' by 2 commits.
(use "git push" to publish your local commits)
Diverged from '\''upstream/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -484,13 +496,15 @@ test_expect_success 'status shows up to date with tracked but diverged from defa
cd test &&
git checkout synced_feature >/dev/null &&
git config repo.settings.statusGoalBranch upstream/main &&
- git status --long -b | head -4
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature
Your branch is up to date with '\''origin/feature'\''.
Diverged from '\''upstream/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -512,13 +526,15 @@ test_expect_success 'status shows up to date with tracked but diverged from orig
cd test &&
git checkout synced_feature2 >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -4
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature2
Your branch is up to date with '\''origin/feature'\''.
Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -536,13 +552,15 @@ test_expect_success 'status shows up to date with tracked but shows default bran
cd test &&
git checkout synced_feature3 >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -4
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature3
Your branch is up to date with '\''origin/feature'\''.
Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -552,12 +570,13 @@ test_expect_success 'status with repo.settings.statusGoalBranch unset shows no d
cd test &&
git checkout synced_feature3 >/dev/null &&
git config --unset repo.settings.statusGoalBranch 2>/dev/null || true &&
- git status --long -b | head -3
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature3
Your branch is up to date with '\''origin/feature'\''.
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -567,13 +586,15 @@ test_expect_success 'status with repo.settings.statusGoalBranch set uses configu
cd test &&
git checkout synced_feature3 >/dev/null &&
git config repo.settings.statusGoalBranch origin/main &&
- git status --long -b | head -4
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature3
Your branch is up to date with '\''origin/feature'\''.
Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -583,13 +604,14 @@ test_expect_success 'status with repo.settings.statusGoalBranch set to different
cd test &&
git checkout work >/dev/null &&
git config repo.settings.statusGoalBranch origin/feature &&
- git status --long -b | head -4
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch work
Your branch is ahead of '\''origin/feature'\'' by 2 commits.
(use "git push" to publish your local commits)
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
@@ -599,12 +621,13 @@ test_expect_success 'status with repo.settings.statusGoalBranch set to non-exist
cd test &&
git checkout synced_feature3 >/dev/null &&
git config repo.settings.statusGoalBranch origin/nonexistent &&
- git status --long -b | head -3
+ git status --long -b
) >actual &&
cat >expect <<-\EOF &&
On branch synced_feature3
Your branch is up to date with '\''origin/feature'\''.
+nothing to commit, working tree clean
EOF
test_cmp expect actual
'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v6 6/6] Change config key to status.compareBranch
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
` (4 preceding siblings ...)
2025-12-24 23:41 ` [PATCH v6 5/6] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41 ` Harald Nordgren via GitGitGadget
2025-12-25 8:00 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Junio C Hamano
2025-12-25 9:45 ` [PATCH v7] status: additional comparison with goal branch Harald Nordgren via GitGitGadget
7 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Update from repo.settings.statusGoalBranch to follow standard
git config pattern status.compareBranch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 4 ++--
t/t6040-tracking-info.sh | 34 +++++++++++++++++-----------------
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/remote.c b/remote.c
index 99c0e18df4..7e13c027b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2246,7 +2246,7 @@ static char *get_goal_branch_ref(char **full_ref_out)
char *slash_pos;
char *ret = NULL;
- if (repo_config_get_value(the_repository, "repo.settings.statusGoalBranch", &config_value))
+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
return NULL;
if (!config_value || !*config_value)
@@ -2254,7 +2254,7 @@ static char *get_goal_branch_ref(char **full_ref_out)
slash_pos = strchr(config_value, '/');
if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
- warning(_("invalid value for repo.settings.statusGoalBranch: '%s' (expected format: remote/branch)"),
+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
config_value);
return NULL;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 598dd89483..fe34ddf0ab 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -309,7 +309,7 @@ test_expect_success 'status shows ahead of both tracked branch and origin/main'
(
cd test &&
git checkout work >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -328,7 +328,7 @@ test_expect_success 'checkout shows ahead of both tracked branch and origin/main
(
cd test &&
git checkout main >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git checkout work 2>&1
) >actual &&
cat >expect <<-\EOF &&
@@ -370,7 +370,7 @@ test_expect_success 'status shows ahead of tracked and diverged from origin/main
(
cd test &&
git checkout work2 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -402,7 +402,7 @@ test_expect_success 'status shows diverged from tracked and behind origin/main'
(
cd test &&
git checkout work2b >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -437,7 +437,7 @@ test_expect_success 'status shows behind tracked and ahead of origin/main' '
(
cd test &&
git checkout work3 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -465,7 +465,7 @@ test_expect_success 'status prefers upstream remote over origin for comparison'
(
cd test &&
git checkout work >/dev/null &&
- git config repo.settings.statusGoalBranch upstream/main &&
+ git config status.goalBranch upstream/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -495,7 +495,7 @@ test_expect_success 'status shows up to date with tracked but diverged from defa
(
cd test &&
git checkout synced_feature >/dev/null &&
- git config repo.settings.statusGoalBranch upstream/main &&
+ git config status.goalBranch upstream/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -525,7 +525,7 @@ test_expect_success 'status shows up to date with tracked but diverged from orig
(
cd test &&
git checkout synced_feature2 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -551,7 +551,7 @@ test_expect_success 'status shows up to date with tracked but shows default bran
(
cd test &&
git checkout synced_feature3 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -565,11 +565,11 @@ EOF
test_cmp expect actual
'
-test_expect_success 'status with repo.settings.statusGoalBranch unset shows no default comparison' '
+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
(
cd test &&
git checkout synced_feature3 >/dev/null &&
- git config --unset repo.settings.statusGoalBranch 2>/dev/null || true &&
+ git config --unset status.goalBranch 2>/dev/null || true &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -581,11 +581,11 @@ EOF
test_cmp expect actual
'
-test_expect_success 'status with repo.settings.statusGoalBranch set uses configured branch' '
+test_expect_success 'status with status.goalBranch set uses configured branch' '
(
cd test &&
git checkout synced_feature3 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/main &&
+ git config status.goalBranch origin/main &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -599,11 +599,11 @@ EOF
test_cmp expect actual
'
-test_expect_success 'status with repo.settings.statusGoalBranch set to different remote/branch' '
+test_expect_success 'status with status.goalBranch set to different remote/branch' '
(
cd test &&
git checkout work >/dev/null &&
- git config repo.settings.statusGoalBranch origin/feature &&
+ git config status.goalBranch origin/feature &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
@@ -616,11 +616,11 @@ EOF
test_cmp expect actual
'
-test_expect_success 'status with repo.settings.statusGoalBranch set to non-existent branch' '
+test_expect_success 'status with status.goalBranch set to non-existent branch' '
(
cd test &&
git checkout synced_feature3 >/dev/null &&
- git config repo.settings.statusGoalBranch origin/nonexistent &&
+ git config status.goalBranch origin/nonexistent &&
git status --long -b
) >actual &&
cat >expect <<-\EOF &&
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
` (5 preceding siblings ...)
2025-12-24 23:41 ` [PATCH v6 6/6] Change config key to status.compareBranch Harald Nordgren via GitGitGadget
@ 2025-12-25 8:00 ` Junio C Hamano
2025-12-25 9:45 ` [PATCH] " Harald Nordgren
2025-12-25 9:45 ` [PATCH v7] status: additional comparison with goal branch Harald Nordgren via GitGitGadget
7 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2025-12-25 8:00 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
> ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
>
> Harald Nordgren (6):
> status: show comparison with upstream default branch
> Simplify default branch comparison logic
> Use repo.settings.statusGoalBranch config for status comparison
> Rename default_remote to goal_branch
> Add warning for malformed statusGoalBranch config
> Change config key to status.compareBranch
It seems that [v6 6/6] smells like an "oops, what I did in [v6 3/6]
was wrong, and this is an incremental fix on top of it".
Please don't.
When presenting your topic to the list, rather, after you finish a
series and the end result reaches a satisfactory state, please look
back and polish patches to hide such mistakes in the middle, pretend
as if you are a perfect developer who wrote a logical progression of
patches that goes straight to the goal without stumbling around,
taking detours, and making mistakes you need to correct in a later
step. Detours may have been taken when you initially wrote the
series, and it may show the "true history" from your point of view,
but to others (and the most importantly, to those who read "git log
-p" later in order to extend your work to suit their needs better),
they are merely distracting.
The titles in [2-6/6] by the way do not seem to follow the
established convention, like [1/6] does, i.e. "status: show
comparison...", use an area prefix "<area>:", followed by a
short-summary that is not capitalized.
Thanks.
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v7] status: additional comparison with goal branch
2025-12-24 23:41 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
` (6 preceding siblings ...)
2025-12-25 8:00 ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Junio C Hamano
@ 2025-12-25 9:45 ` Harald Nordgren via GitGitGadget
2025-12-25 12:33 ` [PATCH v8] status: show comparison with configured " Harald Nordgren via GitGitGadget
7 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-25 9:45 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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" (i.e. you have built new history,
while others are not touching it), "behind" (i.e. you haven't
added any work since you were in-sync, while others have added
their work on the branch), "diverged" (i.e. you have commits
that you haven't pushed out, while others have added commits).
When you fork a branch 'feature' from the 'main' branch of the
remote, but then create 'feature' branch at the remote and push
there, while you still occasionally pull from or rebase onto
their 'main', you'd also want to know how much you have diverged
from 'main', in addition to how your 'feature' and their
'feature' compares. Currently the comparison with 'main' is not
given, making it hard to know when to start thinking about
rebasing onto the upstream default branch.
Show two sets of comparison: one with the tracking branch (as
before), and another with the upstream's default branch (what
their HEAD points to, typically 'main' or 'master'). The latter
comparison appears on a separate line after the tracking branch
status, using the same format:
- "Ahead of 'origin/main' by N commits" when purely ahead
- "Behind 'origin/main' by N commits" when purely behind
- "Diverged from 'origin/main' by N commits" when diverged
Example output when tracking a feature branch:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
Ahead of 'origin/main' by 5 commits.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
status: show default branch comparison when tracking non-default branch
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson"
sandals@crustytoothpaste.net
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v7
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v6:
1: a3800aed18 ! 1: 04e2fb76ee status: show comparison with upstream default branch
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- status: show comparison with upstream default branch
+ status: additional comparison with goal branch
"git status" on a branch that follows a remote branch compares
commits on the current branch and the remote-tracking branch it
@@ Commit message
Ahead of 'origin/main' by 5 commits.
- The upstream default branch is determined by checking symbolic refs:
- 1. refs/remotes/upstream/HEAD (if upstream remote exists)
- 2. refs/remotes/origin/HEAD (fallback)
-
- This works with any default branch name (main, master, develop,
- etc.) as long as the symbolic ref is configured. The comparison
- is also shown when the branch is up-to-date with its tracking
- branch but differs from the upstream default branch.
-
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## remote.c ##
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-+static char *get_default_remote_ref(char **full_ref_out)
++static char *get_goal_branch_ref(char **full_ref_out)
+{
-+ int flag;
++ const char *config_value;
+ const char *resolved;
-+ static const char *remotes[] = { "upstream", "origin", NULL };
-+ int i;
-+
-+ for (i = 0; remotes[i]; i++) {
-+ struct strbuf head_ref = STRBUF_INIT;
-+ strbuf_addf(&head_ref, "refs/remotes/%s/HEAD", remotes[i]);
-+
-+ resolved = refs_resolve_ref_unsafe(
-+ get_main_ref_store(the_repository),
-+ head_ref.buf,
-+ RESOLVE_REF_READING,
-+ NULL, &flag);
-+
-+ strbuf_release(&head_ref);
-+
-+ if (resolved && (flag & REF_ISSYMREF)) {
-+ if (full_ref_out)
-+ *full_ref_out = xstrdup(resolved);
-+ return refs_shorten_unambiguous_ref(
-+ get_main_ref_store(the_repository), resolved, 0);
-+ }
-+ }
++ int flag;
++ struct strbuf ref_buf = STRBUF_INIT;
++ char *slash_pos;
++ char *ret = NULL;
+
-+ return NULL;
-+}
++ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
++ return NULL;
+
-+static int is_default_remote_branch(const char *name)
-+{
-+ char *default_full = NULL;
-+ char *default_short;
-+ int result = 0;
++ if (!config_value || !*config_value)
++ return NULL;
+
-+ default_short = get_default_remote_ref(&default_full);
-+ if (!default_short)
-+ return 0;
++ slash_pos = strchr(config_value, '/');
++ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
++ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
++ config_value);
++ return NULL;
++ }
+
-+ result = !strcmp(name, default_short);
++ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
++ (int)(slash_pos - config_value), config_value,
++ slash_pos + 1);
++
++ resolved = refs_resolve_ref_unsafe(
++ get_main_ref_store(the_repository),
++ ref_buf.buf,
++ RESOLVE_REF_READING,
++ NULL, &flag);
++
++ if (resolved) {
++ if (full_ref_out)
++ *full_ref_out = xstrdup(resolved);
++ ret = refs_shorten_unambiguous_ref(
++ get_main_ref_store(the_repository), resolved, 0);
++ }
+
-+ free(default_short);
-+ free(default_full);
-+ return result;
++ strbuf_release(&ref_buf);
++ return ret;
+}
+
-+static void format_default_branch_comparison(struct strbuf *sb,
++static void format_goal_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
++ const char *goal_full,
++ const char *goal_short,
+ enum ahead_behind_flags abf)
+{
-+ int default_ours = 0, default_theirs = 0;
-+ char *default_full = NULL;
-+ char *default_short;
-+
-+ default_short = get_default_remote_ref(&default_full);
-+ if (!default_short)
-+ return;
++ int goal_ahead = 0, goal_behind = 0;
+
-+ if (stat_branch_pair(branch_refname, default_full,
-+ &default_ours, &default_theirs, abf) <= 0) {
-+ free(default_short);
-+ free(default_full);
++ if (stat_branch_pair(branch_refname, goal_full,
++ &goal_ahead, &goal_behind, abf) <= 0)
+ return;
-+ }
+
+ strbuf_addstr(sb, "\n");
+
-+ if (default_ours > 0 && default_theirs == 0) {
++ if (goal_ahead > 0 && goal_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
-+ default_ours),
-+ default_short, default_ours);
-+ } else if (default_theirs > 0 && default_ours == 0) {
++ goal_ahead),
++ goal_short, goal_ahead);
++ } else if (goal_behind > 0 && goal_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
-+ default_theirs),
-+ default_short, default_theirs);
-+ } else if (default_ours > 0 && default_theirs > 0) {
++ goal_behind),
++ goal_short, goal_behind);
++ } else if (goal_ahead > 0 && goal_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
-+ default_ours + default_theirs),
-+ default_short, default_ours + default_theirs);
++ goal_ahead + goal_behind),
++ goal_short, goal_ahead + goal_behind);
+ }
-+
-+ free(default_short);
-+ free(default_full);
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-+ int show_default_branch_comparison;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
-+
-+ show_default_branch_comparison = !is_default_remote_branch(base);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- strbuf_addf(sb,
- _("Your branch is up to date with '%s'.\n"),
- base);
-+ if (show_default_branch_comparison)
-+ format_default_branch_comparison(sb, branch->refname, abf);
- } else if (abf == AHEAD_BEHIND_QUICK) {
- strbuf_addf(sb,
- _("Your branch and '%s' refer to different commits.\n"),
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git push\" to publish your local commits)\n"));
-+ if (show_default_branch_comparison)
-+ format_default_branch_comparison(sb, branch->refname, abf);
- } else if (!ours) {
- strbuf_addf(sb,
- Q_("Your branch is behind '%s' by %d commit, "
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
-+ if (show_default_branch_comparison)
-+ format_default_branch_comparison(sb, branch->refname, abf);
- } else {
- strbuf_addf(sb,
- Q_("Your branch and '%s' have diverged,\n"
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
-+ if (show_default_branch_comparison)
-+ format_default_branch_comparison(sb, branch->refname, abf);
}
++
++ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
++ char *goal_full = NULL;
++ char *goal_short = get_goal_branch_ref(&goal_full);
++
++ if (goal_short && strcmp(base, goal_short))
++ format_goal_branch_comparison(sb, branch->refname, goal_full,
++ goal_short, abf);
++
++ free(goal_short);
++ free(goal_full);
++ }
++
free(base);
return 1;
+ }
## t/t6040-tracking-info.sh ##
@@ t/t6040-tracking-info.sh: test_expect_success setup '
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
-+ git status --long -b | head -5
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
-+ git checkout work 2>&1 | grep -E "(Switched|Your branch|Ahead of)" | head -3
++ git config status.goalBranch origin/main &&
++ git checkout work 2>&1
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
++ (use "git push" to publish your local commits)
++
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
-+ git status --long -b | head -5
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
-+ git status --long -b | head -6
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
-+ git status --long -b | head -5
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
-+ git status --long -b | head -5
++ git config status.goalBranch upstream/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
-+ git status --long -b | head -4
++ git config status.goalBranch upstream/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
-+ git status --long -b | head -4
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
-+ git status --long -b | head -4
++ git config status.goalBranch origin/main &&
++ git status --long -b
++ ) >actual &&
++ cat >expect <<-\EOF &&
++On branch synced_feature3
++Your branch is up to date with '\''origin/feature'\''.
++
++Diverged from '\''origin/main'\'' by 5 commits.
++
++nothing to commit, working tree clean
++EOF
++ test_cmp expect actual
++'
++
++test_expect_success 'status with status.goalBranch unset shows no default comparison' '
++ (
++ cd test &&
++ git checkout synced_feature3 >/dev/null &&
++ git config --unset status.goalBranch 2>/dev/null || true &&
++ git status --long -b
++ ) >actual &&
++ cat >expect <<-\EOF &&
++On branch synced_feature3
++Your branch is up to date with '\''origin/feature'\''.
++
++nothing to commit, working tree clean
++EOF
++ test_cmp expect actual
++'
++
++test_expect_success 'status with status.goalBranch set uses configured branch' '
++ (
++ cd test &&
++ git checkout synced_feature3 >/dev/null &&
++ git config status.goalBranch origin/main &&
++ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
++
++nothing to commit, working tree clean
++EOF
++ test_cmp expect actual
++'
++
++test_expect_success 'status with status.goalBranch set to different remote/branch' '
++ (
++ cd test &&
++ git checkout work >/dev/null &&
++ git config status.goalBranch origin/feature &&
++ git status --long -b
++ ) >actual &&
++ cat >expect <<-\EOF &&
++On branch work
++Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
++ (
++ cd test &&
++ git checkout synced_feature3 >/dev/null &&
++ git config status.goalBranch origin/nonexistent &&
++ git status --long -b
++ ) >actual &&
++ cat >expect <<-\EOF &&
++On branch synced_feature3
++Your branch is up to date with '\''origin/feature'\''.
++
++nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
2: 417f2075fb < -: ---------- Simplify default branch comparison logic
3: c9ec5d9610 < -: ---------- Use repo.settings.statusGoalBranch config for status comparison
4: 0e308141da < -: ---------- Rename default_remote to goal_branch
5: 441678939f < -: ---------- Add warning for malformed statusGoalBranch config
6: 242dbbae44 < -: ---------- Change config key to status.compareBranch
remote.c | 92 +++++++++++
t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
2 files changed, 432 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..7e13c027b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,84 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_goal_branch_ref(char **full_ref_out)
+{
+ const char *config_value;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *slash_pos;
+ char *ret = NULL;
+
+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
+ return NULL;
+
+ if (!config_value || !*config_value)
+ return NULL;
+
+ slash_pos = strchr(config_value, '/');
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
+ config_value);
+ return NULL;
+ }
+
+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
+ (int)(slash_pos - config_value), config_value,
+ slash_pos + 1);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
+static void format_goal_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ const char *goal_full,
+ const char *goal_short,
+ enum ahead_behind_flags abf)
+{
+ int goal_ahead = 0, goal_behind = 0;
+
+ if (stat_branch_pair(branch_refname, goal_full,
+ &goal_ahead, &goal_behind, abf) <= 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
+ if (goal_ahead > 0 && goal_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ goal_ahead),
+ goal_short, goal_ahead);
+ } else if (goal_behind > 0 && goal_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ goal_behind),
+ goal_short, goal_behind);
+ } else if (goal_ahead > 0 && goal_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ goal_ahead + goal_behind),
+ goal_short, goal_ahead + goal_behind);
+ }
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2258,6 +2336,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2390,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *goal_full = NULL;
+ char *goal_short = get_goal_branch_ref(&goal_full);
+
+ if (goal_short && strcmp(base, goal_short))
+ format_goal_branch_comparison(sb, branch->refname, goal_full,
+ goal_short, abf);
+
+ free(goal_short);
+ free(goal_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..fe34ddf0ab 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -292,4 +293,343 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
+test_expect_success 'setup for ahead of non-main tracking branch' '
+ (
+ cd test &&
+ git checkout -b feature origin/main &&
+ advance feature1 &&
+ git push origin feature &&
+ git checkout -b work --track origin/feature &&
+ advance work1 &&
+ advance work2
+ )
+'
+
+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git checkout work 2>&1
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 >/dev/null &&
+ git status --long -b
+ ) >actual &&
+ test_grep "ahead of .origin/main. by 2 commits" actual &&
+ test_grep ! "Ahead of" actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout origin/main &&
+ git checkout -b oldfeature &&
+ advance oldfeature1 &&
+ git push origin oldfeature &&
+ git checkout origin/main &&
+ advance main_newer &&
+ git push origin HEAD:main &&
+ git checkout -b work2 --track origin/oldfeature &&
+ advance work2_commit
+ )
+'
+
+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for diverged from tracked but behind main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b work2b &&
+ git branch --set-upstream-to=origin/oldfeature &&
+ git checkout origin/main &&
+ advance main_extra &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows diverged from tracked and behind origin/main' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
+Your branch and '\''origin/oldfeature'\'' have diverged,
+and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for behind tracked but ahead of main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b feature3 &&
+ advance feature3_1 &&
+ advance feature3_2 &&
+ advance feature3_3 &&
+ git push origin feature3 &&
+ git checkout -b work3 --track origin/feature3 &&
+ git reset --hard HEAD~2
+ )
+'
+
+test_expect_success 'status shows behind tracked and ahead of origin/main' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote preference' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ )
+'
+
+test_expect_success 'status prefers upstream remote over origin for comparison' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of default' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from default' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
+ (
+ cd test &&
+ git remote remove upstream &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature2 --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead2 &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature3 --track origin/feature
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config --unset status.goalBranch 2>/dev/null || true &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set uses configured branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set to different remote/branch' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/feature &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/nonexistent &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
test_done
base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v8] status: show comparison with configured goal branch
2025-12-25 9:45 ` [PATCH v7] status: additional comparison with goal branch Harald Nordgren via GitGitGadget
@ 2025-12-25 12:33 ` Harald Nordgren via GitGitGadget
2025-12-28 9:16 ` Code review? Harald Nordgren
` (2 more replies)
0 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-25 12:33 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 a goal branch (e.g.,
upstream/main), you can configure status.goalBranch to show an
additional comparison.
Add support for status.goalBranch configuration option that specifies
a remote/branch to compare against. When set, git status shows both
the comparison with the tracking branch (as before) and an additional
comparison with the configured goal branch, if it differs from the
tracking branch. The goal branch comparison appears on a separate
line after the tracking branch status, using the same format:
- "Ahead of 'upstream/main' by N commits" when purely ahead
- "Behind 'upstream/main' by N commits" when purely behind
- "Diverged from 'upstream/main' by N commits" when diverged
Example output when tracking a feature branch with status.goalBranch
set to upstream/main:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
Ahead of 'upstream/main' by 5 commits.
The comparison is only shown when status.goalBranch is configured
and the goal branch differs from the tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
status: show comparison with configured goal branch
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson"
sandals@crustytoothpaste.net
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v8
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v7:
1: 04e2fb76ee ! 1: 7e2574d5ae status: additional comparison with goal branch
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- status: additional comparison with goal branch
+ status: show comparison with configured goal branch
"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" (i.e. you have built new history,
- while others are not touching it), "behind" (i.e. you haven't
- added any work since you were in-sync, while others have added
- their work on the branch), "diverged" (i.e. you have commits
- that you haven't pushed out, while others have added commits).
+ builds upon, to show "ahead", "behind", or "diverged" status.
- When you fork a branch 'feature' from the 'main' branch of the
- remote, but then create 'feature' branch at the remote and push
- there, while you still occasionally pull from or rebase onto
- their 'main', you'd also want to know how much you have diverged
- from 'main', in addition to how your 'feature' and their
- 'feature' compares. Currently the comparison with 'main' is not
- given, making it hard to know when to start thinking about
- rebasing onto the upstream default branch.
+ When working on a feature branch that tracks a remote feature branch,
+ but you also want to track progress relative to a goal branch (e.g.,
+ upstream/main), you can configure status.goalBranch to show an
+ additional comparison.
- Show two sets of comparison: one with the tracking branch (as
- before), and another with the upstream's default branch (what
- their HEAD points to, typically 'main' or 'master'). The latter
- comparison appears on a separate line after the tracking branch
- status, using the same format:
- - "Ahead of 'origin/main' by N commits" when purely ahead
- - "Behind 'origin/main' by N commits" when purely behind
- - "Diverged from 'origin/main' by N commits" when diverged
+ Add support for status.goalBranch configuration option that specifies
+ a remote/branch to compare against. When set, git status shows both
+ the comparison with the tracking branch (as before) and an additional
+ comparison with the configured goal branch, if it differs from the
+ tracking branch. The goal branch comparison appears on a separate
+ line after the tracking branch status, using the same format:
+ - "Ahead of 'upstream/main' by N commits" when purely ahead
+ - "Behind 'upstream/main' by N commits" when purely behind
+ - "Diverged from 'upstream/main' by N commits" when diverged
- Example output when tracking a feature branch:
+ Example output when tracking a feature branch with status.goalBranch
+ set to upstream/main:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
- Ahead of 'origin/main' by 5 commits.
+ Ahead of 'upstream/main' by 5 commits.
+
+ The comparison is only shown when status.goalBranch is configured
+ and the goal branch differs from the tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
remote.c | 92 +++++++++++
t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
2 files changed, 432 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..7e13c027b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,84 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_goal_branch_ref(char **full_ref_out)
+{
+ const char *config_value;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *slash_pos;
+ char *ret = NULL;
+
+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
+ return NULL;
+
+ if (!config_value || !*config_value)
+ return NULL;
+
+ slash_pos = strchr(config_value, '/');
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
+ config_value);
+ return NULL;
+ }
+
+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
+ (int)(slash_pos - config_value), config_value,
+ slash_pos + 1);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
+static void format_goal_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ const char *goal_full,
+ const char *goal_short,
+ enum ahead_behind_flags abf)
+{
+ int goal_ahead = 0, goal_behind = 0;
+
+ if (stat_branch_pair(branch_refname, goal_full,
+ &goal_ahead, &goal_behind, abf) <= 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
+ if (goal_ahead > 0 && goal_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ goal_ahead),
+ goal_short, goal_ahead);
+ } else if (goal_behind > 0 && goal_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ goal_behind),
+ goal_short, goal_behind);
+ } else if (goal_ahead > 0 && goal_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ goal_ahead + goal_behind),
+ goal_short, goal_ahead + goal_behind);
+ }
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2258,6 +2336,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2390,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *goal_full = NULL;
+ char *goal_short = get_goal_branch_ref(&goal_full);
+
+ if (goal_short && strcmp(base, goal_short))
+ format_goal_branch_comparison(sb, branch->refname, goal_full,
+ goal_short, abf);
+
+ free(goal_short);
+ free(goal_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..fe34ddf0ab 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -292,4 +293,343 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
+test_expect_success 'setup for ahead of non-main tracking branch' '
+ (
+ cd test &&
+ git checkout -b feature origin/main &&
+ advance feature1 &&
+ git push origin feature &&
+ git checkout -b work --track origin/feature &&
+ advance work1 &&
+ advance work2
+ )
+'
+
+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git checkout work 2>&1
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 >/dev/null &&
+ git status --long -b
+ ) >actual &&
+ test_grep "ahead of .origin/main. by 2 commits" actual &&
+ test_grep ! "Ahead of" actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout origin/main &&
+ git checkout -b oldfeature &&
+ advance oldfeature1 &&
+ git push origin oldfeature &&
+ git checkout origin/main &&
+ advance main_newer &&
+ git push origin HEAD:main &&
+ git checkout -b work2 --track origin/oldfeature &&
+ advance work2_commit
+ )
+'
+
+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for diverged from tracked but behind main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b work2b &&
+ git branch --set-upstream-to=origin/oldfeature &&
+ git checkout origin/main &&
+ advance main_extra &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows diverged from tracked and behind origin/main' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
+Your branch and '\''origin/oldfeature'\'' have diverged,
+and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for behind tracked but ahead of main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b feature3 &&
+ advance feature3_1 &&
+ advance feature3_2 &&
+ advance feature3_3 &&
+ git push origin feature3 &&
+ git checkout -b work3 --track origin/feature3 &&
+ git reset --hard HEAD~2
+ )
+'
+
+test_expect_success 'status shows behind tracked and ahead of origin/main' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote preference' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ )
+'
+
+test_expect_success 'status prefers upstream remote over origin for comparison' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of default' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from default' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
+ (
+ cd test &&
+ git remote remove upstream &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature2 --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead2 &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature3 --track origin/feature
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config --unset status.goalBranch 2>/dev/null || true &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set uses configured branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set to different remote/branch' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/feature &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/nonexistent &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
test_done
base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Code review?
2025-12-25 12:33 ` [PATCH v8] status: show comparison with configured " Harald Nordgren via GitGitGadget
@ 2025-12-28 9:16 ` Harald Nordgren
2025-12-28 19:37 ` Junio C Hamano
2025-12-28 11:46 ` [PATCH v8] status: show comparison with configured goal branch Junio C Hamano
2025-12-28 15:41 ` [PATCH v9 0/2] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2025-12-28 9:16 UTC (permalink / raw)
To: gitgitgadget
Cc: git, gitster, ychin.macvim, chris.torek, sandals, haraldnordgren
Hi!
Could I get some code review on this? Maybe it's ready to be merged?
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Code review?
2025-12-28 9:16 ` Code review? Harald Nordgren
@ 2025-12-28 19:37 ` Junio C Hamano
2025-12-28 20:16 ` Harald Nordgren
2025-12-30 16:08 ` Harald Nordgren
0 siblings, 2 replies; 147+ messages in thread
From: Junio C Hamano @ 2025-12-28 19:37 UTC (permalink / raw)
To: Harald Nordgren; +Cc: gitgitgadget, git, ychin.macvim, chris.torek, sandals
Harald Nordgren <haraldnordgren@gmail.com> writes:
> Could I get some code review on this? Maybe it's ready to be merged?
At least not from me at the code level, not yet for now.
I still do not quite get why you need a new configuration variable,
so the problem I had were still at the design level.
A happy new year.
^ permalink raw reply [flat|nested] 147+ messages in thread
* Code review?
2025-12-28 19:37 ` Junio C Hamano
@ 2025-12-28 20:16 ` Harald Nordgren
2025-12-28 23:09 ` Ben Knoble
2025-12-30 16:08 ` Harald Nordgren
1 sibling, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2025-12-28 20:16 UTC (permalink / raw)
To: gitster
Cc: chris.torek, git, gitgitgadget, haraldnordgren, sandals,
ychin.macvim
Hi!
The config variable solves to problem of finding which ”goal branch” to compare to, which I otherwise find unsolvable. I took it from the previous discussion that trying to extract the default branch from the remote is not a good idea.
Is the solution through using the remote/pushRemote?
If that’s the solution they are set per branch (as I understand it), so each time checking out a new branch this ”goal branch” comparison would be lost and has to be configured again. That ruins the feature.
Looking at the git repositories I have on my machine, none of them have pushRemote set up. And the remote setting is pointing to my fork, never to upstream, I think most people have it like that.
I appreciate all the help so far! Happy new year!
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Code review?
2025-12-28 20:16 ` Harald Nordgren
@ 2025-12-28 23:09 ` Ben Knoble
2025-12-29 12:17 ` Triangular workflows Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Ben Knoble @ 2025-12-28 23:09 UTC (permalink / raw)
To: Harald Nordgren
Cc: gitster, chris.torek, git, gitgitgadget, sandals, ychin.macvim
>
> Le 28 déc. 2025 à 15:16, Harald Nordgren <haraldnordgren@gmail.com> a écrit :
>
> Hi!
>
> The config variable solves to problem of finding which ”goal branch” to compare to, which I otherwise find unsolvable. I took it from the previous discussion that trying to extract the default branch from the remote is not a good idea.
>
> Is the solution through using the remote/pushRemote?
>
> If that’s the solution they are set per branch (as I understand it), so each time checking out a new branch this ”goal branch” comparison would be lost and has to be configured again. That ruins the feature.
>
> Looking at the git repositories I have on my machine, none of them have pushRemote set up. And the remote setting is pointing to my fork, never to upstream, I think most people have it like that.
>
> I appreciate all the help so far! Happy new year!
>
>
> Harald
>
When I get to it, I intend to send a short email describing how I configure Git for triangular workflows.
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Code review?
2025-12-28 19:37 ` Junio C Hamano
2025-12-28 20:16 ` Harald Nordgren
@ 2025-12-30 16:08 ` Harald Nordgren
1 sibling, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2025-12-30 16:08 UTC (permalink / raw)
To: gitster
Cc: chris.torek, git, gitgitgadget, haraldnordgren, sandals,
ychin.macvim
Hi!
I found a way now to eliminate the config variable, and instead uses the
push branch when it's different from the tracking branch.
Let me know what you think.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v8] status: show comparison with configured goal branch
2025-12-25 12:33 ` [PATCH v8] status: show comparison with configured " Harald Nordgren via GitGitGadget
2025-12-28 9:16 ` Code review? Harald Nordgren
@ 2025-12-28 11:46 ` Junio C Hamano
2025-12-28 15:46 ` Code review? Harald Nordgren
2025-12-28 15:41 ` [PATCH v9 0/2] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2025-12-28 11:46 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +test_expect_success 'status shows ahead of both tracked branch and origin/main' '
> + (
> + cd test &&
> + git checkout work >/dev/null &&
What is this redirecction for?
> + git config status.goalBranch origin/main &&
> + git status --long -b
> + ) >actual &&
Instead of redirecting the whole thing, if you are grabbing the
output from "git status", do it more like this, probably:
(
cd test &&
... &&
git status --long --branch >../actual
)
> + cat >expect <<-\EOF &&
Looking up what "<<-\EOF" means, it makes little sense to have these
lines ...
> +On branch work
> +Your branch is ahead of '\''origin/feature'\'' by 2 commits.
> + (use "git push" to publish your local commits)
> +
> +Ahead of '\''origin/main'\'' by 3 commits.
> +
> +nothing to commit, working tree clean
> +EOF
... abut the left edge of the page. Unlike <<\EOF, the dash sign
tells the shell that it should remove the leading tab from the line
before feeding "cat", and the point of using that construct "<<-\EOF"
to begin with is so that you can indent the here doc to the same
level as the command text. IOW, you use "<<-\EOF" only because you
want to avoid these ugly lines that are sticking to the left, like
the above. Instead you can do this:
cat >expect <<-\EOF &&
On branch work
Your branch is ...
(use "git push" ...
...
EOF
and the shell strips the leading tabs from these lines.
> + test_cmp expect actual
> +'
> +test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
> + (
> + cd test &&
> + git checkout main >/dev/null &&
> + git config status.goalBranch origin/main &&
> + git checkout work 2>&1
Likewise.
> + ) >actual &&
> + cat >expect <<-\EOF &&
> +Switched to branch '\''work'\''
> +Your branch is ahead of '\''origin/feature'\'' by 2 commits.
> + (use "git push" to publish your local commits)
> +
> +Ahead of '\''origin/main'\'' by 3 commits.
> +EOF
Likewise.
Also, doesn't $SQ work here, i.e.
cat >expect <<-EOF &&
Switched to branch ${SQ}work${SQ}
Your branch is ahead of ${SQ}...${SQ} by 2 commits.
...
EOF
As you want interpolation if you go this route, we lose quote from
the end of here-doc token and write "<<-EOF" here, instead of
"<<-\EOF".
> + test_cmp expect actual
> +'
> +
> +test_expect_success 'status tracking origin/main shows only main' '
> + (
> + cd test &&
> + git checkout b4 >/dev/null &&
> + git status --long -b
Likewise.
> + ) >actual &&
> + test_grep "ahead of .origin/main. by 2 commits" actual &&
> + test_grep ! "Ahead of" actual
> +'
I'll stop here.
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v9 0/2] status: show comparison with configured goal branch
2025-12-25 12:33 ` [PATCH v8] status: show comparison with configured " Harald Nordgren via GitGitGadget
2025-12-28 9:16 ` Code review? Harald Nordgren
2025-12-28 11:46 ` [PATCH v8] status: show comparison with configured goal branch Junio C Hamano
@ 2025-12-28 15:41 ` Harald Nordgren via GitGitGadget
2025-12-28 15:41 ` [PATCH v9 1/2] " Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-28 15:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
Harald Nordgren (2):
status: show comparison with configured goal branch
improve tests
remote.c | 92 +++++++++++
t/t6040-tracking-info.sh | 339 +++++++++++++++++++++++++++++++++++++++
2 files changed, 431 insertions(+)
base-commit: 7c7698a654a7a0031f65b0ab0c1c4e438e95df60
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v9
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v9
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v8:
1: 7e2574d5ae = 1: ecfe122585 status: show comparison with configured goal branch
-: ---------- > 2: 53bab23737 improve tests
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v9 1/2] status: show comparison with configured goal branch
2025-12-28 15:41 ` [PATCH v9 0/2] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
@ 2025-12-28 15:41 ` Harald Nordgren via GitGitGadget
2025-12-28 15:41 ` [PATCH v9 2/2] improve tests Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-28 15:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 a goal branch (e.g.,
upstream/main), you can configure status.goalBranch to show an
additional comparison.
Add support for status.goalBranch configuration option that specifies
a remote/branch to compare against. When set, git status shows both
the comparison with the tracking branch (as before) and an additional
comparison with the configured goal branch, if it differs from the
tracking branch. The goal branch comparison appears on a separate
line after the tracking branch status, using the same format:
- "Ahead of 'upstream/main' by N commits" when purely ahead
- "Behind 'upstream/main' by N commits" when purely behind
- "Diverged from 'upstream/main' by N commits" when diverged
Example output when tracking a feature branch with status.goalBranch
set to upstream/main:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
Ahead of 'upstream/main' by 5 commits.
The comparison is only shown when status.goalBranch is configured
and the goal branch differs from the tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 92 +++++++++++
t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
2 files changed, 432 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..7e13c027b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,84 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_goal_branch_ref(char **full_ref_out)
+{
+ const char *config_value;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *slash_pos;
+ char *ret = NULL;
+
+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
+ return NULL;
+
+ if (!config_value || !*config_value)
+ return NULL;
+
+ slash_pos = strchr(config_value, '/');
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
+ config_value);
+ return NULL;
+ }
+
+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
+ (int)(slash_pos - config_value), config_value,
+ slash_pos + 1);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
+static void format_goal_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ const char *goal_full,
+ const char *goal_short,
+ enum ahead_behind_flags abf)
+{
+ int goal_ahead = 0, goal_behind = 0;
+
+ if (stat_branch_pair(branch_refname, goal_full,
+ &goal_ahead, &goal_behind, abf) <= 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
+ if (goal_ahead > 0 && goal_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ goal_ahead),
+ goal_short, goal_ahead);
+ } else if (goal_behind > 0 && goal_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ goal_behind),
+ goal_short, goal_behind);
+ } else if (goal_ahead > 0 && goal_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ goal_ahead + goal_behind),
+ goal_short, goal_ahead + goal_behind);
+ }
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2258,6 +2336,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2390,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *goal_full = NULL;
+ char *goal_short = get_goal_branch_ref(&goal_full);
+
+ if (goal_short && strcmp(base, goal_short))
+ format_goal_branch_comparison(sb, branch->refname, goal_full,
+ goal_short, abf);
+
+ free(goal_short);
+ free(goal_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..fe34ddf0ab 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -292,4 +293,343 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
+test_expect_success 'setup for ahead of non-main tracking branch' '
+ (
+ cd test &&
+ git checkout -b feature origin/main &&
+ advance feature1 &&
+ git push origin feature &&
+ git checkout -b work --track origin/feature &&
+ advance work1 &&
+ advance work2
+ )
+'
+
+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git checkout work 2>&1
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 >/dev/null &&
+ git status --long -b
+ ) >actual &&
+ test_grep "ahead of .origin/main. by 2 commits" actual &&
+ test_grep ! "Ahead of" actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout origin/main &&
+ git checkout -b oldfeature &&
+ advance oldfeature1 &&
+ git push origin oldfeature &&
+ git checkout origin/main &&
+ advance main_newer &&
+ git push origin HEAD:main &&
+ git checkout -b work2 --track origin/oldfeature &&
+ advance work2_commit
+ )
+'
+
+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for diverged from tracked but behind main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b work2b &&
+ git branch --set-upstream-to=origin/oldfeature &&
+ git checkout origin/main &&
+ advance main_extra &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows diverged from tracked and behind origin/main' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
+Your branch and '\''origin/oldfeature'\'' have diverged,
+and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for behind tracked but ahead of main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b feature3 &&
+ advance feature3_1 &&
+ advance feature3_2 &&
+ advance feature3_3 &&
+ git push origin feature3 &&
+ git checkout -b work3 --track origin/feature3 &&
+ git reset --hard HEAD~2
+ )
+'
+
+test_expect_success 'status shows behind tracked and ahead of origin/main' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote preference' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ )
+'
+
+test_expect_success 'status prefers upstream remote over origin for comparison' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of default' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from default' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
+ (
+ cd test &&
+ git remote remove upstream &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature2 --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead2 &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature3 --track origin/feature
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config --unset status.goalBranch 2>/dev/null || true &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set uses configured branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set to different remote/branch' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/feature &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/nonexistent &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v9 2/2] improve tests
2025-12-28 15:41 ` [PATCH v9 0/2] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
2025-12-28 15:41 ` [PATCH v9 1/2] " Harald Nordgren via GitGitGadget
@ 2025-12-28 15:41 ` Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-28 15:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Simplify tests based on feedback.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
t/t6040-tracking-info.sh | 249 +++++++++++++++++++--------------------
1 file changed, 124 insertions(+), 125 deletions(-)
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index fe34ddf0ab..a875b4c73b 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -308,45 +308,44 @@ test_expect_success 'setup for ahead of non-main tracking branch' '
test_expect_success 'status shows ahead of both tracked branch and origin/main' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Ahead of '\''origin/main'\'' by 3 commits.
+ Ahead of ${SQ}origin/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
(
cd test &&
- git checkout main >/dev/null &&
+ git checkout main &&
git config status.goalBranch origin/main &&
- git checkout work 2>&1
- ) >actual &&
- cat >expect <<-\EOF &&
-Switched to branch '\''work'\''
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git checkout work >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Ahead of '\''origin/main'\'' by 3 commits.
-EOF
+ Ahead of ${SQ}origin/main${SQ} by 3 commits.
+ EOF
test_cmp expect actual
'
test_expect_success 'status tracking origin/main shows only main' '
(
cd test &&
- git checkout b4 >/dev/null &&
- git status --long -b
- ) >actual &&
+ git checkout b4 &&
+ git status >../actual
+ ) &&
test_grep "ahead of .origin/main. by 2 commits" actual &&
test_grep ! "Ahead of" actual
'
@@ -369,19 +368,19 @@ test_expect_success 'setup for ahead of tracked but diverged from main' '
test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
(
cd test &&
- git checkout work2 >/dev/null &&
+ git checkout work2 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work2
-Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work2
+ Your branch is ahead of ${SQ}origin/oldfeature${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
-Diverged from '\''origin/main'\'' by 3 commits.
+ Diverged from ${SQ}origin/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -401,20 +400,20 @@ test_expect_success 'setup for diverged from tracked but behind main' '
test_expect_success 'status shows diverged from tracked and behind origin/main' '
(
cd test &&
- git checkout work2b >/dev/null &&
+ git checkout work2b &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work2b
-Your branch and '\''origin/oldfeature'\'' have diverged,
-and have 1 and 1 different commits each, respectively.
- (use "git pull" if you want to integrate the remote branch with yours)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work2b
+ Your branch and ${SQ}origin/oldfeature${SQ} have diverged,
+ and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
-Behind '\''origin/main'\'' by 1 commit.
+ Behind ${SQ}origin/main${SQ} by 1 commit.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -436,19 +435,19 @@ test_expect_success 'setup for behind tracked but ahead of main' '
test_expect_success 'status shows behind tracked and ahead of origin/main' '
(
cd test &&
- git checkout work3 >/dev/null &&
+ git checkout work3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work3
-Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
- (use "git pull" to update your local branch)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work3
+ Your branch is behind ${SQ}origin/feature3${SQ} by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
-Ahead of '\''origin/main'\'' by 1 commit.
+ Ahead of ${SQ}origin/main${SQ} by 1 commit.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -464,19 +463,19 @@ test_expect_success 'setup upstream remote preference' '
test_expect_success 'status prefers upstream remote over origin for comparison' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch upstream/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Diverged from '\''upstream/main'\'' by 5 commits.
+ Diverged from ${SQ}upstream/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -494,18 +493,18 @@ test_expect_success 'setup for up to date with tracked but ahead of default' '
test_expect_success 'status shows up to date with tracked but diverged from default' '
(
cd test &&
- git checkout synced_feature >/dev/null &&
+ git checkout synced_feature &&
git config status.goalBranch upstream/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''upstream/main'\'' by 3 commits.
+ Diverged from ${SQ}upstream/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -524,18 +523,18 @@ test_expect_success 'setup for up to date with tracked but ahead of origin/main'
test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
(
cd test &&
- git checkout synced_feature2 >/dev/null &&
+ git checkout synced_feature2 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature2
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature2
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -550,85 +549,85 @@ test_expect_success 'setup for up to date with tracked but purely ahead of origi
test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch unset shows no default comparison' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
- git config --unset status.goalBranch 2>/dev/null || true &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git checkout synced_feature3 &&
+ git config --unset status.goalBranch || true &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set uses configured branch' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set to different remote/branch' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch origin/feature &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set to non-existent branch' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/nonexistent &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch
2025-12-28 15:41 ` [PATCH v9 0/2] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
2025-12-28 15:41 ` [PATCH v9 1/2] " Harald Nordgren via GitGitGadget
2025-12-28 15:41 ` [PATCH v9 2/2] improve tests Harald Nordgren via GitGitGadget
@ 2025-12-30 16:08 ` Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 1/3] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
` (4 more replies)
2 siblings, 5 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-30 16:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com
Harald Nordgren (3):
status: show comparison with configured goal branch
improve tests
use pushRemote and tracking branch
remote.c | 89 +++++++++++++++++++++
t/t6040-tracking-info.sh | 167 +++++++++++++++++++++++++++++++++++++++
2 files changed, 256 insertions(+)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v10
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v9:
1: ecfe122585 = 1: 43a75944fb status: show comparison with configured goal branch
2: 53bab23737 = 2: e6d24b8b6a improve tests
-: ---------- > 3: 13c2a03b0a use pushRemote and tracking branch
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v10 1/3] status: show comparison with configured goal branch
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
@ 2025-12-30 16:08 ` Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 2/3] improve tests Harald Nordgren via GitGitGadget
` (3 subsequent siblings)
4 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-30 16:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 a goal branch (e.g.,
upstream/main), you can configure status.goalBranch to show an
additional comparison.
Add support for status.goalBranch configuration option that specifies
a remote/branch to compare against. When set, git status shows both
the comparison with the tracking branch (as before) and an additional
comparison with the configured goal branch, if it differs from the
tracking branch. The goal branch comparison appears on a separate
line after the tracking branch status, using the same format:
- "Ahead of 'upstream/main' by N commits" when purely ahead
- "Behind 'upstream/main' by N commits" when purely behind
- "Diverged from 'upstream/main' by N commits" when diverged
Example output when tracking a feature branch with status.goalBranch
set to upstream/main:
On branch feature
Your branch is ahead of 'origin/feature' by 2 commits.
(use "git push" to publish your local commits)
Ahead of 'upstream/main' by 5 commits.
The comparison is only shown when status.goalBranch is configured
and the goal branch differs from the tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 92 +++++++++++
t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
2 files changed, 432 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..7e13c027b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,84 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_goal_branch_ref(char **full_ref_out)
+{
+ const char *config_value;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *slash_pos;
+ char *ret = NULL;
+
+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
+ return NULL;
+
+ if (!config_value || !*config_value)
+ return NULL;
+
+ slash_pos = strchr(config_value, '/');
+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
+ config_value);
+ return NULL;
+ }
+
+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
+ (int)(slash_pos - config_value), config_value,
+ slash_pos + 1);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
+static void format_goal_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ const char *goal_full,
+ const char *goal_short,
+ enum ahead_behind_flags abf)
+{
+ int goal_ahead = 0, goal_behind = 0;
+
+ if (stat_branch_pair(branch_refname, goal_full,
+ &goal_ahead, &goal_behind, abf) <= 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
+ if (goal_ahead > 0 && goal_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ goal_ahead),
+ goal_short, goal_ahead);
+ } else if (goal_behind > 0 && goal_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ goal_behind),
+ goal_short, goal_behind);
+ } else if (goal_ahead > 0 && goal_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ goal_ahead + goal_behind),
+ goal_short, goal_ahead + goal_behind);
+ }
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2258,6 +2336,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2390,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *goal_full = NULL;
+ char *goal_short = get_goal_branch_ref(&goal_full);
+
+ if (goal_short && strcmp(base, goal_short))
+ format_goal_branch_comparison(sb, branch->refname, goal_full,
+ goal_short, abf);
+
+ free(goal_short);
+ free(goal_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..fe34ddf0ab 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,6 +21,7 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -292,4 +293,343 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
+test_expect_success 'setup for ahead of non-main tracking branch' '
+ (
+ cd test &&
+ git checkout -b feature origin/main &&
+ advance feature1 &&
+ git push origin feature &&
+ git checkout -b work --track origin/feature &&
+ advance work1 &&
+ advance work2
+ )
+'
+
+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
+ (
+ cd test &&
+ git checkout main >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git checkout work 2>&1
+ ) >actual &&
+ cat >expect <<-\EOF &&
+Switched to branch '\''work'\''
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Ahead of '\''origin/main'\'' by 3 commits.
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
+ git checkout b4 >/dev/null &&
+ git status --long -b
+ ) >actual &&
+ test_grep "ahead of .origin/main. by 2 commits" actual &&
+ test_grep ! "Ahead of" actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+ (
+ cd test &&
+ git checkout origin/main &&
+ git checkout -b oldfeature &&
+ advance oldfeature1 &&
+ git push origin oldfeature &&
+ git checkout origin/main &&
+ advance main_newer &&
+ git push origin HEAD:main &&
+ git checkout -b work2 --track origin/oldfeature &&
+ advance work2_commit
+ )
+'
+
+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+ (
+ cd test &&
+ git checkout work2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2
+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''origin/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for diverged from tracked but behind main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b work2b &&
+ git branch --set-upstream-to=origin/oldfeature &&
+ git checkout origin/main &&
+ advance main_extra &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows diverged from tracked and behind origin/main' '
+ (
+ cd test &&
+ git checkout work2b >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work2b
+Your branch and '\''origin/oldfeature'\'' have diverged,
+and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
+Behind '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for behind tracked but ahead of main' '
+ (
+ cd test &&
+ git fetch origin &&
+ git checkout origin/main &&
+ git checkout -b feature3 &&
+ advance feature3_1 &&
+ advance feature3_2 &&
+ advance feature3_3 &&
+ git push origin feature3 &&
+ git checkout -b work3 --track origin/feature3 &&
+ git reset --hard HEAD~2
+ )
+'
+
+test_expect_success 'status shows behind tracked and ahead of origin/main' '
+ (
+ cd test &&
+ git checkout work3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work3
+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
+
+Ahead of '\''origin/main'\'' by 1 commit.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote preference' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ )
+'
+
+test_expect_success 'status prefers upstream remote over origin for comparison' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
+ (use "git push" to publish your local commits)
+
+Diverged from '\''upstream/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of default' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from default' '
+ (
+ cd test &&
+ git checkout synced_feature >/dev/null &&
+ git config status.goalBranch upstream/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''upstream/main'\'' by 3 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
+ (
+ cd test &&
+ git remote remove upstream &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature2 --track origin/feature &&
+ git checkout origin/main &&
+ advance main_ahead2 &&
+ git push origin HEAD:main
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
+ (
+ cd test &&
+ git checkout synced_feature2 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature2
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
+ (
+ cd test &&
+ git checkout origin/feature &&
+ git checkout -b synced_feature3 --track origin/feature
+ )
+'
+
+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config --unset status.goalBranch 2>/dev/null || true &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set uses configured branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/main &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+Diverged from '\''origin/main'\'' by 5 commits.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'status with status.goalBranch set to different remote/branch' '
+ (
+ cd test &&
+ git checkout work >/dev/null &&
+ git config status.goalBranch origin/feature &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch work
+Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
+ (
+ cd test &&
+ git checkout synced_feature3 >/dev/null &&
+ git config status.goalBranch origin/nonexistent &&
+ git status --long -b
+ ) >actual &&
+ cat >expect <<-\EOF &&
+On branch synced_feature3
+Your branch is up to date with '\''origin/feature'\''.
+
+nothing to commit, working tree clean
+EOF
+ test_cmp expect actual
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v10 2/3] improve tests
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 1/3] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
@ 2025-12-30 16:08 ` Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 3/3] use pushRemote and tracking branch Harald Nordgren via GitGitGadget
` (2 subsequent siblings)
4 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-30 16:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Simplify tests based on feedback.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
t/t6040-tracking-info.sh | 249 +++++++++++++++++++--------------------
1 file changed, 124 insertions(+), 125 deletions(-)
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index fe34ddf0ab..a875b4c73b 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -308,45 +308,44 @@ test_expect_success 'setup for ahead of non-main tracking branch' '
test_expect_success 'status shows ahead of both tracked branch and origin/main' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Ahead of '\''origin/main'\'' by 3 commits.
+ Ahead of ${SQ}origin/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
(
cd test &&
- git checkout main >/dev/null &&
+ git checkout main &&
git config status.goalBranch origin/main &&
- git checkout work 2>&1
- ) >actual &&
- cat >expect <<-\EOF &&
-Switched to branch '\''work'\''
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git checkout work >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Ahead of '\''origin/main'\'' by 3 commits.
-EOF
+ Ahead of ${SQ}origin/main${SQ} by 3 commits.
+ EOF
test_cmp expect actual
'
test_expect_success 'status tracking origin/main shows only main' '
(
cd test &&
- git checkout b4 >/dev/null &&
- git status --long -b
- ) >actual &&
+ git checkout b4 &&
+ git status >../actual
+ ) &&
test_grep "ahead of .origin/main. by 2 commits" actual &&
test_grep ! "Ahead of" actual
'
@@ -369,19 +368,19 @@ test_expect_success 'setup for ahead of tracked but diverged from main' '
test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
(
cd test &&
- git checkout work2 >/dev/null &&
+ git checkout work2 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work2
-Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work2
+ Your branch is ahead of ${SQ}origin/oldfeature${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
-Diverged from '\''origin/main'\'' by 3 commits.
+ Diverged from ${SQ}origin/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -401,20 +400,20 @@ test_expect_success 'setup for diverged from tracked but behind main' '
test_expect_success 'status shows diverged from tracked and behind origin/main' '
(
cd test &&
- git checkout work2b >/dev/null &&
+ git checkout work2b &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work2b
-Your branch and '\''origin/oldfeature'\'' have diverged,
-and have 1 and 1 different commits each, respectively.
- (use "git pull" if you want to integrate the remote branch with yours)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work2b
+ Your branch and ${SQ}origin/oldfeature${SQ} have diverged,
+ and have 1 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
-Behind '\''origin/main'\'' by 1 commit.
+ Behind ${SQ}origin/main${SQ} by 1 commit.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -436,19 +435,19 @@ test_expect_success 'setup for behind tracked but ahead of main' '
test_expect_success 'status shows behind tracked and ahead of origin/main' '
(
cd test &&
- git checkout work3 >/dev/null &&
+ git checkout work3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work3
-Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
- (use "git pull" to update your local branch)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work3
+ Your branch is behind ${SQ}origin/feature3${SQ} by 2 commits, and can be fast-forwarded.
+ (use "git pull" to update your local branch)
-Ahead of '\''origin/main'\'' by 1 commit.
+ Ahead of ${SQ}origin/main${SQ} by 1 commit.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -464,19 +463,19 @@ test_expect_success 'setup upstream remote preference' '
test_expect_success 'status prefers upstream remote over origin for comparison' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch upstream/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-Diverged from '\''upstream/main'\'' by 5 commits.
+ Diverged from ${SQ}upstream/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -494,18 +493,18 @@ test_expect_success 'setup for up to date with tracked but ahead of default' '
test_expect_success 'status shows up to date with tracked but diverged from default' '
(
cd test &&
- git checkout synced_feature >/dev/null &&
+ git checkout synced_feature &&
git config status.goalBranch upstream/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''upstream/main'\'' by 3 commits.
+ Diverged from ${SQ}upstream/main${SQ} by 3 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -524,18 +523,18 @@ test_expect_success 'setup for up to date with tracked but ahead of origin/main'
test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
(
cd test &&
- git checkout synced_feature2 >/dev/null &&
+ git checkout synced_feature2 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature2
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature2
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
@@ -550,85 +549,85 @@ test_expect_success 'setup for up to date with tracked but purely ahead of origi
test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch unset shows no default comparison' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
- git config --unset status.goalBranch 2>/dev/null || true &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git checkout synced_feature3 &&
+ git config --unset status.goalBranch || true &&
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set uses configured branch' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/main &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-Diverged from '\''origin/main'\'' by 5 commits.
+ Diverged from ${SQ}origin/main${SQ} by 5 commits.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set to different remote/branch' '
(
cd test &&
- git checkout work >/dev/null &&
+ git checkout work &&
git config status.goalBranch origin/feature &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch work
-Your branch is ahead of '\''origin/feature'\'' by 2 commits.
- (use "git push" to publish your local commits)
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch work
+ Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ (use "git push" to publish your local commits)
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
test_expect_success 'status with status.goalBranch set to non-existent branch' '
(
cd test &&
- git checkout synced_feature3 >/dev/null &&
+ git checkout synced_feature3 &&
git config status.goalBranch origin/nonexistent &&
- git status --long -b
- ) >actual &&
- cat >expect <<-\EOF &&
-On branch synced_feature3
-Your branch is up to date with '\''origin/feature'\''.
+ git status >../actual
+ ) &&
+ cat >expect <<-EOF &&
+ On branch synced_feature3
+ Your branch is up to date with ${SQ}origin/feature${SQ}.
-nothing to commit, working tree clean
-EOF
+ nothing to commit, working tree clean
+ EOF
test_cmp expect actual
'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v10 3/3] use pushRemote and tracking branch
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 1/3] status: show comparison with configured goal branch Harald Nordgren via GitGitGadget
2025-12-30 16:08 ` [PATCH v10 2/3] improve tests Harald Nordgren via GitGitGadget
@ 2025-12-30 16:08 ` Harald Nordgren via GitGitGadget
2026-01-01 23:09 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from " Junio C Hamano
2026-01-02 11:17 ` [PATCH v11] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
4 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-30 16:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Use them for comparisons instead of config variable.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 71 +++++-----
t/t6040-tracking-info.sh | 296 ++++++++-------------------------------
2 files changed, 96 insertions(+), 271 deletions(-)
diff --git a/remote.c b/remote.c
index 7e13c027b5..2317725f7d 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,31 +2237,22 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-static char *get_goal_branch_ref(char **full_ref_out)
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
{
- const char *config_value;
+ const char *push_remote;
const char *resolved;
int flag;
struct strbuf ref_buf = STRBUF_INIT;
- char *slash_pos;
char *ret = NULL;
- if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
- return NULL;
-
- if (!config_value || !*config_value)
+ if (!branch)
return NULL;
- slash_pos = strchr(config_value, '/');
- if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
- warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
- config_value);
+ push_remote = pushremote_for_branch(branch, NULL);
+ if (!push_remote)
return NULL;
- }
- strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
- (int)(slash_pos - config_value), config_value,
- slash_pos + 1);
+ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
resolved = refs_resolve_ref_unsafe(
get_main_ref_store(the_repository),
@@ -2280,38 +2271,44 @@ static char *get_goal_branch_ref(char **full_ref_out)
return ret;
}
-static void format_goal_branch_comparison(struct strbuf *sb,
+static void format_push_branch_comparison(struct strbuf *sb,
const char *branch_refname,
- const char *goal_full,
- const char *goal_short,
+ const char *push_full,
+ const char *push_short,
enum ahead_behind_flags abf)
{
- int goal_ahead = 0, goal_behind = 0;
+ int push_ahead = 0, push_behind = 0;
+ int stat_result;
- if (stat_branch_pair(branch_refname, goal_full,
- &goal_ahead, &goal_behind, abf) <= 0)
+ stat_result = stat_branch_pair(branch_refname, push_full,
+ &push_ahead, &push_behind, abf);
+ if (stat_result < 0)
return;
strbuf_addstr(sb, "\n");
- if (goal_ahead > 0 && goal_behind == 0) {
+ if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ push_short);
+ } else if (push_ahead > 0 && push_behind == 0) {
strbuf_addf(sb,
Q_("Ahead of '%s' by %d commit.\n",
"Ahead of '%s' by %d commits.\n",
- goal_ahead),
- goal_short, goal_ahead);
- } else if (goal_behind > 0 && goal_ahead == 0) {
+ push_ahead),
+ push_short, push_ahead);
+ } else if (push_behind > 0 && push_ahead == 0) {
strbuf_addf(sb,
Q_("Behind '%s' by %d commit.\n",
"Behind '%s' by %d commits.\n",
- goal_behind),
- goal_short, goal_behind);
- } else if (goal_ahead > 0 && goal_behind > 0) {
+ push_behind),
+ push_short, push_behind);
+ } else if (push_ahead > 0 && push_behind > 0) {
strbuf_addf(sb,
Q_("Diverged from '%s' by %d commit.\n",
"Diverged from '%s' by %d commits.\n",
- goal_ahead + goal_behind),
- goal_short, goal_ahead + goal_behind);
+ push_ahead + push_behind),
+ push_short, push_ahead + push_behind);
}
}
@@ -2392,15 +2389,15 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
- char *goal_full = NULL;
- char *goal_short = get_goal_branch_ref(&goal_full);
+ char *push_full = NULL;
+ char *push_short = get_remote_push_branch(branch, &push_full);
- if (goal_short && strcmp(base, goal_short))
- format_goal_branch_comparison(sb, branch->refname, goal_full,
- goal_short, abf);
+ if (push_short && strcmp(base, push_short))
+ format_push_branch_comparison(sb, branch->refname, push_full,
+ push_short, abf);
- free(goal_short);
- free(goal_full);
+ free(push_short);
+ free(push_full);
}
free(base);
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index a875b4c73b..f27ae719ad 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -21,7 +21,6 @@ test_expect_success setup '
git clone . test &&
(
cd test &&
- git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
git checkout -b b1 origin &&
git reset --hard HEAD^ &&
advance d &&
@@ -293,340 +292,169 @@ test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
-test_expect_success 'setup for ahead of non-main tracking branch' '
- (
- cd test &&
- git checkout -b feature origin/main &&
- advance feature1 &&
- git push origin feature &&
- git checkout -b work --track origin/feature &&
- advance work1 &&
- advance work2
- )
-'
-
-test_expect_success 'status shows ahead of both tracked branch and origin/main' '
+test_expect_success 'status tracking origin/main shows only main' '
(
cd test &&
- git checkout work &&
- git config status.goalBranch origin/main &&
+ git checkout b4 &&
git status >../actual
) &&
cat >expect <<-EOF &&
- On branch work
- Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ On branch b4
+ Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits.
(use "git push" to publish your local commits)
- Ahead of ${SQ}origin/main${SQ} by 3 commits.
-
nothing to commit, working tree clean
EOF
test_cmp expect actual
'
-test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
- (
- cd test &&
- git checkout main &&
- git config status.goalBranch origin/main &&
- git checkout work >../actual
- ) &&
- cat >expect <<-EOF &&
- Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
- (use "git push" to publish your local commits)
-
- Ahead of ${SQ}origin/main${SQ} by 3 commits.
- EOF
- test_cmp expect actual
-'
-
-test_expect_success 'status tracking origin/main shows only main' '
- (
- cd test &&
- git checkout b4 &&
- git status >../actual
- ) &&
- test_grep "ahead of .origin/main. by 2 commits" actual &&
- test_grep ! "Ahead of" actual
-'
-
-test_expect_success 'setup for ahead of tracked but diverged from main' '
- (
- cd test &&
- git checkout origin/main &&
- git checkout -b oldfeature &&
- advance oldfeature1 &&
- git push origin oldfeature &&
- git checkout origin/main &&
- advance main_newer &&
- git push origin HEAD:main &&
- git checkout -b work2 --track origin/oldfeature &&
- advance work2_commit
- )
-'
-
-test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
+test_expect_success 'status shows ahead of both origin/main and feature branch' '
(
cd test &&
- git checkout work2 &&
- git config status.goalBranch origin/main &&
+ git checkout -b feature2 origin/main &&
+ git push origin HEAD &&
+ advance work &&
git status >../actual
) &&
cat >expect <<-EOF &&
- On branch work2
- Your branch is ahead of ${SQ}origin/oldfeature${SQ} by 1 commit.
+ On branch feature2
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
(use "git push" to publish your local commits)
- Diverged from ${SQ}origin/main${SQ} by 3 commits.
+ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
nothing to commit, working tree clean
EOF
test_cmp expect actual
'
-test_expect_success 'setup for diverged from tracked but behind main' '
+test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
(
cd test &&
- git fetch origin &&
- git checkout origin/main &&
- git checkout -b work2b &&
- git branch --set-upstream-to=origin/oldfeature &&
- git checkout origin/main &&
- advance main_extra &&
- git push origin HEAD:main
- )
-'
-
-test_expect_success 'status shows diverged from tracked and behind origin/main' '
- (
- cd test &&
- git checkout work2b &&
- git config status.goalBranch origin/main &&
- git status >../actual
+ git checkout feature2 >../actual
) &&
cat >expect <<-EOF &&
- On branch work2b
- Your branch and ${SQ}origin/oldfeature${SQ} have diverged,
- and have 1 and 1 different commits each, respectively.
- (use "git pull" if you want to integrate the remote branch with yours)
-
- Behind ${SQ}origin/main${SQ} by 1 commit.
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
- nothing to commit, working tree clean
+ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
EOF
test_cmp expect actual
'
-test_expect_success 'setup for behind tracked but ahead of main' '
+test_expect_success 'setup for ahead of tracked but diverged from main' '
(
cd test &&
- git fetch origin &&
+ git checkout -b feature4 origin/main &&
+ advance work1 &&
git checkout origin/main &&
- git checkout -b feature3 &&
- advance feature3_1 &&
- advance feature3_2 &&
- advance feature3_3 &&
- git push origin feature3 &&
- git checkout -b work3 --track origin/feature3 &&
- git reset --hard HEAD~2
+ advance work2 &&
+ git push origin HEAD:main &&
+ git checkout feature4 &&
+ advance work3
)
'
-test_expect_success 'status shows behind tracked and ahead of origin/main' '
+test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
(
cd test &&
- git checkout work3 &&
- git config status.goalBranch origin/main &&
+ git checkout feature4 &&
+ git branch --set-upstream-to origin/main &&
+ git push origin HEAD &&
+ advance work &&
git status >../actual
) &&
cat >expect <<-EOF &&
- On branch work3
- Your branch is behind ${SQ}origin/feature3${SQ} by 2 commits, and can be fast-forwarded.
- (use "git pull" to update your local branch)
+ 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)
- Ahead of ${SQ}origin/main${SQ} by 1 commit.
+ Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
nothing to commit, working tree clean
EOF
test_cmp expect actual
'
-test_expect_success 'setup upstream remote preference' '
+test_expect_success 'setup upstream remote' '
(
cd test &&
git remote add upstream ../. &&
git fetch upstream &&
- git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
+ git config remote.pushDefault origin
)
'
-test_expect_success 'status prefers upstream remote over origin for comparison' '
+test_expect_success 'status with upstream remote and push.default set to origin' '
(
cd test &&
- git checkout work &&
- git config status.goalBranch upstream/main &&
+ git checkout -b feature5 upstream/main &&
+ git push origin &&
+ advance work &&
git status >../actual
) &&
cat >expect <<-EOF &&
- On branch work
- Your branch is ahead of ${SQ}origin/feature${SQ} by 2 commits.
+ On branch feature5
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
(use "git push" to publish your local commits)
- Diverged from ${SQ}upstream/main${SQ} by 5 commits.
+ Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
nothing to commit, working tree clean
EOF
test_cmp expect actual
'
-test_expect_success 'setup for up to date with tracked but ahead of default' '
+test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
(
cd test &&
- git checkout origin/feature &&
- git checkout -b synced_feature --track origin/feature &&
- git checkout origin/main &&
- advance main_ahead &&
- git push origin HEAD:main
- )
-'
-
-test_expect_success 'status shows up to date with tracked but diverged from default' '
- (
- cd test &&
- git checkout synced_feature &&
- git config status.goalBranch upstream/main &&
+ 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 synced_feature
- Your branch is up to date with ${SQ}origin/feature${SQ}.
-
- Diverged from ${SQ}upstream/main${SQ} by 3 commits.
-
- nothing to commit, working tree clean
- EOF
- test_cmp expect actual
-'
-
-test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
- (
- cd test &&
- git remote remove upstream &&
- git checkout origin/feature &&
- git checkout -b synced_feature2 --track origin/feature &&
- git checkout origin/main &&
- advance main_ahead2 &&
- git push origin HEAD:main
- )
-'
-
-test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
- (
- cd test &&
- git checkout synced_feature2 &&
- git config status.goalBranch origin/main &&
- git status >../actual
- ) &&
- cat >expect <<-EOF &&
- On branch synced_feature2
- Your branch is up to date with ${SQ}origin/feature${SQ}.
-
- Diverged from ${SQ}origin/main${SQ} by 5 commits.
-
- nothing to commit, working tree clean
- EOF
- test_cmp expect actual
-'
-
-test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
- (
- cd test &&
- git checkout origin/feature &&
- git checkout -b synced_feature3 --track origin/feature
- )
-'
-
-test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
- (
- cd test &&
- git checkout synced_feature3 &&
- git config status.goalBranch origin/main &&
- git status >../actual
- ) &&
- cat >expect <<-EOF &&
- On branch synced_feature3
- Your branch is up to date with ${SQ}origin/feature${SQ}.
-
- Diverged from ${SQ}origin/main${SQ} by 5 commits.
-
- nothing to commit, working tree clean
- EOF
- test_cmp expect actual
-'
+ On branch feature6
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+ (use "git push" to publish your local commits)
-test_expect_success 'status with status.goalBranch unset shows no default comparison' '
- (
- cd test &&
- git checkout synced_feature3 &&
- git config --unset status.goalBranch || true &&
- git status >../actual
- ) &&
- cat >expect <<-EOF &&
- On branch synced_feature3
- Your branch is up to date with ${SQ}origin/feature${SQ}.
+ Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
nothing to commit, working tree clean
EOF
test_cmp expect actual
'
-test_expect_success 'status with status.goalBranch set uses configured branch' '
+test_expect_success 'status with upstream remote and push branch up to date' '
(
cd test &&
- git checkout synced_feature3 &&
- git config status.goalBranch origin/main &&
+ git checkout -b feature7 upstream/main &&
+ git push origin &&
git status >../actual
) &&
cat >expect <<-EOF &&
- On branch synced_feature3
- Your branch is up to date with ${SQ}origin/feature${SQ}.
+ On branch feature7
+ Your branch is up to date with ${SQ}upstream/main${SQ}.
- Diverged from ${SQ}origin/main${SQ} by 5 commits.
+ 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 with status.goalBranch set to different remote/branch' '
+test_expect_success 'checkout shows push branch up to date' '
(
cd test &&
- git checkout work &&
- git config status.goalBranch origin/feature &&
- git status >../actual
- ) &&
- cat >expect <<-EOF &&
- On branch work
- Your branch is ahead of ${SQ}origin/feature${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 with status.goalBranch set to non-existent branch' '
- (
- cd test &&
- git checkout synced_feature3 &&
- git config status.goalBranch origin/nonexistent &&
- git status >../actual
+ git checkout feature7 >../actual
) &&
cat >expect <<-EOF &&
- On branch synced_feature3
- Your branch is up to date with ${SQ}origin/feature${SQ}.
+ Your branch is up to date with ${SQ}upstream/main${SQ}.
- nothing to commit, working tree clean
+ Your branch is up to date with ${SQ}origin/feature7${SQ}.
EOF
test_cmp expect actual
'
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
` (2 preceding siblings ...)
2025-12-30 16:08 ` [PATCH v10 3/3] use pushRemote and tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-01 23:09 ` Junio C Hamano
2026-01-01 23:38 ` Another look? Harald Nordgren
2026-01-02 11:17 ` [PATCH v11] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
4 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-01 23:09 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
> ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
> cc: Ben Knoble ben.knoble@gmail.com
>
> Harald Nordgren (3):
> status: show comparison with configured goal branch
> improve tests
> use pushRemote and tracking branch
Again this seems to do a "step 1 goes in a direction, step 2 fixes
its mistake, step 3 changes course" drunken-man's walk.
The same advice to restructure them into a logical incremental
progression that moves the codebase in one consistent direction to
eventually reach the goal at the end applies.
I see you are now using pushremote_for_branch() that is already used
by branch_get_push(). If that gives us "the other thing" that we
would want to compare, instead of adding yet another configuration
variable users need to be aware of, that is really good.
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-01 23:09 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from " Junio C Hamano
@ 2026-01-01 23:38 ` Harald Nordgren
2026-01-02 9:48 ` Kristoffer Haugsbakk
` (2 more replies)
0 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-01 23:38 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> Again this seems to do a "step 1 goes in a direction, step 2 fixes
> its mistake, step 3 changes course" drunken-man's walk.
>
> The same advice to restructure them into a logical incremental
> progression that moves the codebase in one consistent direction to
> eventually reach the goal at the end applies.
Isn't programming always bit of drunken-man's walk?
I'm very hesitant to restructure my history before I am confident I will
not need any of the old work later -- I would hate to lose history if I
make a mistake.
One option is to keep my code backed up on a separate branch locally, but
this gets problematic as I add more work (endless cherry-picking and
squashing) between local branches before submitting new patches. So now you
know some of my reasoning. I'm not saying I'm right, but it's a bit
fear-based.
With that said, my idea has always been to squash everything into a single
commit before merging this. The whole diff is not that big. I can split it
into code in one commit and tests in another.
As a side-note: In my day job we only allow "squash and merge" on our
GitHub. This gives devs the flexibility to treat their branches as a WIP
area before merging, but still gives a pristine git history after merge.
This feels to me like a good trade-offs. But again, happy to take
instructions on how to do better.
> I see you are now using pushremote_for_branch() that is already used
> by branch_get_push(). If that gives us "the other thing" that we
> would want to compare, instead of adding yet another configuration
> variable users need to be aware of, that is really good.
Thanks for the encouragement! I put a lot of work into the tests as well,
I hope they tell the story of what this code achieves now.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: Another look?
2026-01-01 23:38 ` Another look? Harald Nordgren
@ 2026-01-02 9:48 ` Kristoffer Haugsbakk
2026-01-02 11:20 ` Harald Nordgren
2026-01-04 2:17 ` Junio C Hamano
2026-01-05 21:55 ` D. Ben Knoble
2 siblings, 1 reply; 147+ messages in thread
From: Kristoffer Haugsbakk @ 2026-01-02 9:48 UTC (permalink / raw)
To: Harald Nordgren, Junio C Hamano; +Cc: git, Josh Soref
On Fri, Jan 2, 2026, at 00:38, Harald Nordgren wrote:
>> Again this seems to do a "step 1 goes in a direction, step 2 fixes
>> its mistake, step 3 changes course" drunken-man's walk.
>>
>> The same advice to restructure them into a logical incremental
>> progression that moves the codebase in one consistent direction to
>> eventually reach the goal at the end applies.
>
> Isn't programming always bit of drunken-man's walk?
We don’t have to present the chain of code changes as they “really
happened”. An alternative is to present what will eventually be the
final version as if you had both a borderline perfect plan, foresight,
and execution. And that’s the convention in this project. Because that’s
the natural progression of a patch series; each iteration you get help
to arrive at what looks like the perfect iteration. (Until someone finds
a off-by-one error two years later?)
What *really happened* is always fiction in any case.
> I'm very hesitant to restructure my history before I am confident I will
> not need any of the old work later -- I would hate to lose history if I
> make a mistake.
>
> One option is to keep my code backed up on a separate branch locally, but
> this gets problematic as I add more work (endless cherry-picking and
> squashing) between local branches before submitting new patches. So now you
> know some of my reasoning. I'm not saying I'm right, but it's a bit
> fear-based.
To make a snapshot of each version seems inevitable in my book since you
are encouraged to include a range-diff (and optionally also an
interdiff) between each iteration.
Now you of course have the backup of each version because you can
download the patches that you yourself posted. But that’s more work then
just snapshotting each version, for me at least.
> With that said, my idea has always been to squash everything into a single
> commit before merging this. The whole diff is not that big. I can split it
> into code in one commit and tests in another.
>
> As a side-note: In my day job we only allow "squash and merge" on our
> GitHub. This gives devs the flexibility to treat their branches as a WIP
> area before merging, but still gives a pristine git history after merge.
> This feels to me like a good trade-offs. But again, happy to take
> instructions on how to do better.
If that’s a good trade off, what are the variables involved in the trade
off? So far it seems like:
1. Flexibility to iterate like you want
2. A final history without any back-and-forth noise (pristine/sober
walk)
But a third variable here is
3. A series of logically separated commits
And you don’t get that with that approach. And a good final history is
very important in many people’s eyes.
People call this mandatory squash strategy some word similar to *clean*
presumably because there is no back-and-forth noise. That’s the memetic
contagion. But there are other adjectives as well:
• Bloated: When the mandated squash strategy forces different concerns
(code formatting, whitespace formatting, refactor, bug fix, ...) to be
truncated into one commit
• Lossy: When so many commits get squashed that you cannot, with any
amount of analysis, piece together what lines in the commit message
correspond to some part of the diff (especially likely to happen if
(1) the squash commit message is a bullet list and (2) the commit
messages are just things like “WIP” and “fix”)
Someone might argue that the squash merges will not be large because the
pull requests are not large and the tasks are not large. Or they should
not be. But that demands more of both the project/task management and
the pull request management:
1. Better project management foresight and planning. Notice that we have
traded the rewriting commits strategy of “perfect plan, foresight,
and execution” for demanding more foresight from project
management. Just because we have mandated no more than one commit per
pull request or patch series.
And “perfect plan, foresight, and execution” is not even a
requirement. Just a contrast to the one-commit requirement. A project
could allow contributors to both present “perfect plan, foresight,
and execution” series as well as messy series, with the latter being
squashed at the integrator’s/reviewer’s/mantainer’s discretion.
2. Divide changes into more pull requests or patch series. At least with
vanilla GitHub that causes overhead as you have to manually link all
the pull requests. You might also have to manually link the pull
requests using URLs if the target branch of the PR does not do it for
you.
On the other hand you might have little or no overhead with some
“stacked PR” tool.
The one-commit rule works just as well if not better[1] than the
alternatives in theory. The problem is that the theory demands much more
from project management and pull request handling.
† 1: Part of the motivation is often avoiding merge commits. And if
merge commits always cause point deductions then a perfectly
executed squash merge strategy will always win over some strategy
involving true merges.
Although I would personally choose a rebase-with-trailer strategy
over squash merges here; rebase the series/PR and add a trailer to
each commit which links to the series/PR.
>
>>[snip]
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: Another look?
2026-01-01 23:38 ` Another look? Harald Nordgren
2026-01-02 9:48 ` Kristoffer Haugsbakk
@ 2026-01-04 2:17 ` Junio C Hamano
2026-01-04 2:41 ` Nico Williams
2026-01-05 21:55 ` D. Ben Knoble
2 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-04 2:17 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
>> Again this seems to do a "step 1 goes in a direction, step 2 fixes
>> its mistake, step 3 changes course" drunken-man's walk.
>>
>> The same advice to restructure them into a logical incremental
>> progression that moves the codebase in one consistent direction to
>> eventually reach the goal at the end applies.
>
> Isn't programming always bit of drunken-man's walk?
Yes, but the point is that other people do not have to see you
taking roundabout route (or for that matter, they do not have to see
you coding in your bathrobe like Linus, even if it may be true you
;-).
When other developers later need to figure out what you really
wanted to achieve so that they do not break your intention while
they update your code to fix bugs in it and/or adding new features
on top of it, it would be far easier for them to work on a clean
history.
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Another look?
2026-01-04 2:17 ` Junio C Hamano
@ 2026-01-04 2:41 ` Nico Williams
2026-01-04 4:17 ` Junio C Hamano
0 siblings, 1 reply; 147+ messages in thread
From: Nico Williams @ 2026-01-04 2:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Harald Nordgren, git, gitgitgadget
On Sun, Jan 04, 2026 at 11:17:45AM +0900, Junio C Hamano wrote:
> Harald Nordgren <haraldnordgren@gmail.com> writes:
> > Isn't programming always bit of drunken-man's walk?
>
> Yes, but the point is that other people do not have to see you
> taking roundabout route (or for that matter, they do not have to see
> you coding in your bathrobe like Linus, even if it may be true you
> ;-).
Sun Microsystems, Inc. used a rebase workflow from 1992 to its end
(RIP). Thousands of developers worked with stricly linear history
(merges were not allowed!). Large team projects that had to have their
own alternate repositories and do the Teamware equivalent of `git rebase
--onto` periodically. There was no branching, only forking. Forks were
kept for backporting and for history archival purposes.
It worked like a charm.
Merge workflows are -IMO- toxic and obnoxious. I do NOT want to see any
programmer's "drunken-man's walk", unless it's because I'm interviewing
them. There is no value to preserving that.
> When other developers later need to figure out what you really
> wanted to achieve so that they do not break your intention while
> they update your code to fix bugs in it and/or adding new features
> on top of it, it would be far easier for them to work on a clean
> history.
Hear hear!
Strictly linear history makes it much easier to understand what's going
on. The only merge turds I would allow are fast-forward ones where the
purpose of the merge commit is to document push boundaries in the commit
history, but even this I don't like.
Nico
--
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Another look?
2026-01-04 2:41 ` Nico Williams
@ 2026-01-04 4:17 ` Junio C Hamano
0 siblings, 0 replies; 147+ messages in thread
From: Junio C Hamano @ 2026-01-04 4:17 UTC (permalink / raw)
To: Nico Williams; +Cc: Harald Nordgren, git, gitgitgadget
Nico Williams <nico@cryptonector.com> writes:
> Sun Microsystems, Inc. used a rebase workflow from 1992 to its end
> ...
> It worked like a charm.
That's good for you. But I do not necessarily think merge workflows
are bad, and my advice was certainly *not* about avoiding merges.
A linear logical progression of commits that is about a single theme
is much nicer than drunken-man's walk that is also a linear sequence
of commits. The distinction between them has nothing to do with
merges.
And a history that bundles together a collection of linear logical
progressions with merges of these topics one by one is much easier
than rebasing these unrelated topics into a strictly linear single
strand of pearls, by making it much clear where each of these topics
concludes.
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Another look?
2026-01-01 23:38 ` Another look? Harald Nordgren
2026-01-02 9:48 ` Kristoffer Haugsbakk
2026-01-04 2:17 ` Junio C Hamano
@ 2026-01-05 21:55 ` D. Ben Knoble
2 siblings, 0 replies; 147+ messages in thread
From: D. Ben Knoble @ 2026-01-05 21:55 UTC (permalink / raw)
To: Harald Nordgren; +Cc: gitster, git, gitgitgadget
On Thu, Jan 1, 2026 at 6:38 PM Harald Nordgren <haraldnordgren@gmail.com> wrote:
>
> > Again this seems to do a "step 1 goes in a direction, step 2 fixes
> > its mistake, step 3 changes course" drunken-man's walk.
> >
> > The same advice to restructure them into a logical incremental
> > progression that moves the codebase in one consistent direction to
> > eventually reach the goal at the end applies.
>
> Isn't programming always bit of drunken-man's walk?
>
> I'm very hesitant to restructure my history before I am confident I will
> not need any of the old work later -- I would hate to lose history if I
> make a mistake.
>
> One option is to keep my code backed up on a separate branch locally, but
Git will already do this, more or less! See "git reflog". No need to worry :)
I don't fear-driven development to lead to optimal results ;)
> As a side-note: In my day job we only allow "squash and merge" on our
> GitHub. This gives devs the flexibility to treat their branches as a WIP
> area before merging, but still gives a pristine git history after merge.
> This feels to me like a good trade-offs. But again, happy to take
> instructions on how to do better.
I think others have covered this, but you can both "branch is WIP" and
"clean history" by iterating within a PR. The GitHub UI does not make
this particularly nice [1], but my recipe is essentially
1. Make changes
2. Post range-diff [2] and force-push
[1]: https://benknoble.github.io/blog/2025/03/17/more-range-diff/
[2]: https://benknoble.github.io/blog/2024/10/04/copy-range-diff/
--
D. Ben Knoble
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v11] status: show comparison with push remote tracking branch
2025-12-30 16:08 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch Harald Nordgren via GitGitGadget
` (3 preceding siblings ...)
2026-01-01 23:09 ` [PATCH v10 0/3] status: show additional comparison with push branch when different from " Junio C Hamano
@ 2026-01-02 11:17 ` Harald Nordgren via GitGitGadget
2026-01-02 15:18 ` Phillip Wood
2026-01-02 21:34 ` [PATCH v12 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
4 siblings, 2 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-02 11:17 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 remote's
tracking branch (which may differ from the upstream branch), git status
now shows an additional comparison.
When a push remote is configured (via branch.<name>.pushRemote or
remote.pushDefault), git status shows both the comparison with the
upstream tracking branch (as before) and an additional comparison with
the push remote's tracking branch, if it differs from the upstream
tracking branch. The push branch comparison appears on a separate
line after the upstream branch status, using the same format:
- "Ahead of 'origin/feature' by N commits" when purely ahead
- "Behind 'origin/feature' by N commits" when purely behind
- "Diverged from 'origin/feature' by N commits" when diverged
Example output when tracking upstream/main with pushRemote set to origin:
On branch feature
Your branch is ahead of 'upstream/main' by 2 commits.
(use "git pull" if you want to integrate the remote branch with yours)
Ahead of 'origin/feature' by 5 commits.
The comparison is only shown when a push remote is configured and the
push remote's tracking branch differs from the upstream tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
status: show comparison with push remote tracking branch
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson"
sandals@crustytoothpaste.net cc: Ben Knoble ben.knoble@gmail.com cc:
"Kristoffer Haugsbakk" kristofferhaugsbakk@fastmail.com
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v11
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v11
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v10:
1: 43a75944fb ! 1: 53bb1cb6bb status: show comparison with configured goal branch
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- status: show comparison with configured goal branch
+ status: show comparison with push remote tracking branch
"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 a goal branch (e.g.,
- upstream/main), you can configure status.goalBranch to show an
- additional comparison.
+ but you also want to track progress relative to the push remote's
+ tracking branch (which may differ from the upstream branch), git status
+ now shows an additional comparison.
- Add support for status.goalBranch configuration option that specifies
- a remote/branch to compare against. When set, git status shows both
- the comparison with the tracking branch (as before) and an additional
- comparison with the configured goal branch, if it differs from the
- tracking branch. The goal branch comparison appears on a separate
- line after the tracking branch status, using the same format:
- - "Ahead of 'upstream/main' by N commits" when purely ahead
- - "Behind 'upstream/main' by N commits" when purely behind
- - "Diverged from 'upstream/main' by N commits" when diverged
+ When a push remote is configured (via branch.<name>.pushRemote or
+ remote.pushDefault), git status shows both the comparison with the
+ upstream tracking branch (as before) and an additional comparison with
+ the push remote's tracking branch, if it differs from the upstream
+ tracking branch. The push branch comparison appears on a separate
+ line after the upstream branch status, using the same format:
+ - "Ahead of 'origin/feature' by N commits" when purely ahead
+ - "Behind 'origin/feature' by N commits" when purely behind
+ - "Diverged from 'origin/feature' by N commits" when diverged
- Example output when tracking a feature branch with status.goalBranch
- set to upstream/main:
+ Example output when tracking upstream/main with pushRemote set to origin:
On branch feature
- Your branch is ahead of 'origin/feature' by 2 commits.
- (use "git push" to publish your local commits)
+ Your branch is ahead of 'upstream/main' by 2 commits.
+ (use "git pull" if you want to integrate the remote branch with yours)
- Ahead of 'upstream/main' by 5 commits.
+ Ahead of 'origin/feature' by 5 commits.
- The comparison is only shown when status.goalBranch is configured
- and the goal branch differs from the tracking branch.
+ The comparison is only shown when a push remote is configured and the
+ push remote's tracking branch differs from the upstream tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-+static char *get_goal_branch_ref(char **full_ref_out)
++static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
-+ const char *config_value;
++ const char *push_remote;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
-+ char *slash_pos;
+ char *ret = NULL;
+
-+ if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
++ if (!branch)
+ return NULL;
+
-+ if (!config_value || !*config_value)
++ push_remote = pushremote_for_branch(branch, NULL);
++ if (!push_remote)
+ return NULL;
+
-+ slash_pos = strchr(config_value, '/');
-+ if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
-+ warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
-+ config_value);
-+ return NULL;
-+ }
-+
-+ strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
-+ (int)(slash_pos - config_value), config_value,
-+ slash_pos + 1);
++ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+ return ret;
+}
+
-+static void format_goal_branch_comparison(struct strbuf *sb,
++static void format_push_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
-+ const char *goal_full,
-+ const char *goal_short,
++ const char *push_full,
++ const char *push_short,
+ enum ahead_behind_flags abf)
+{
-+ int goal_ahead = 0, goal_behind = 0;
++ int push_ahead = 0, push_behind = 0;
++ int stat_result;
+
-+ if (stat_branch_pair(branch_refname, goal_full,
-+ &goal_ahead, &goal_behind, abf) <= 0)
++ stat_result = stat_branch_pair(branch_refname, push_full,
++ &push_ahead, &push_behind, abf);
++ if (stat_result < 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
-+ if (goal_ahead > 0 && goal_behind == 0) {
++ if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
++ strbuf_addf(sb,
++ _("Your branch is up to date with '%s'.\n"),
++ push_short);
++ } else if (push_ahead > 0 && push_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
-+ goal_ahead),
-+ goal_short, goal_ahead);
-+ } else if (goal_behind > 0 && goal_ahead == 0) {
++ push_ahead),
++ push_short, push_ahead);
++ } else if (push_behind > 0 && push_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
-+ goal_behind),
-+ goal_short, goal_behind);
-+ } else if (goal_ahead > 0 && goal_behind > 0) {
++ push_behind),
++ push_short, push_behind);
++ } else if (push_ahead > 0 && push_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
-+ goal_ahead + goal_behind),
-+ goal_short, goal_ahead + goal_behind);
++ push_ahead + push_behind),
++ push_short, push_ahead + push_behind);
+ }
+}
+
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
-+ char *goal_full = NULL;
-+ char *goal_short = get_goal_branch_ref(&goal_full);
++ char *push_full = NULL;
++ char *push_short = get_remote_push_branch(branch, &push_full);
+
-+ if (goal_short && strcmp(base, goal_short))
-+ format_goal_branch_comparison(sb, branch->refname, goal_full,
-+ goal_short, abf);
++ if (push_short && strcmp(base, push_short))
++ format_push_branch_comparison(sb, branch->refname, push_full,
++ push_short, abf);
+
-+ free(goal_short);
-+ free(goal_full);
++ free(push_short);
++ free(push_full);
+ }
+
free(base);
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
## t/t6040-tracking-info.sh ##
-@@ t/t6040-tracking-info.sh: test_expect_success setup '
- git clone . test &&
- (
- cd test &&
-+ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
- git checkout -b b1 origin &&
- git reset --hard HEAD^ &&
- advance d &&
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
test_cmp expect actual
'
-+test_expect_success 'setup for ahead of non-main tracking branch' '
-+ (
-+ cd test &&
-+ git checkout -b feature origin/main &&
-+ advance feature1 &&
-+ git push origin feature &&
-+ git checkout -b work --track origin/feature &&
-+ advance work1 &&
-+ advance work2
-+ )
-+'
-+
-+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
++test_expect_success 'status tracking origin/main shows only main' '
+ (
+ cd test &&
-+ git checkout work >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work
-+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
-+ (use "git push" to publish your local commits)
-+
-+Ahead of '\''origin/main'\'' by 3 commits.
-+
-+nothing to commit, working tree clean
-+EOF
++ 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 'checkout shows ahead of both tracked branch and origin/main' '
++test_expect_success 'status shows ahead of both origin/main and feature branch' '
+ (
+ cd test &&
-+ git checkout main >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git checkout work 2>&1
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+Switched to branch '\''work'\''
-+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
-+ (use "git push" to publish your local commits)
-+
-+Ahead of '\''origin/main'\'' by 3 commits.
-+EOF
++ 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.
++ (use "git push" to publish your local commits)
++
++ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
++
++ nothing to commit, working tree clean
++ EOF
+ test_cmp expect actual
+'
+
-+test_expect_success 'status tracking origin/main shows only main' '
++test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
+ (
+ cd test &&
-+ git checkout b4 >/dev/null &&
-+ git status --long -b
-+ ) >actual &&
-+ test_grep "ahead of .origin/main. by 2 commits" actual &&
-+ test_grep ! "Ahead of" actual
++ git checkout feature2 >../actual
++ ) &&
++ cat >expect <<-EOF &&
++ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
++ (use "git push" to publish your local commits)
++
++ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
++ 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 &&
-+ git checkout -b oldfeature &&
-+ advance oldfeature1 &&
-+ git push origin oldfeature &&
-+ git checkout origin/main &&
-+ advance main_newer &&
++ advance work2 &&
+ git push origin HEAD:main &&
-+ git checkout -b work2 --track origin/oldfeature &&
-+ advance work2_commit
-+ )
-+'
-+
-+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
-+ (
-+ cd test &&
-+ git checkout work2 >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work2
-+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
-+ (use "git push" to publish your local commits)
-+
-+Diverged from '\''origin/main'\'' by 3 commits.
-+
-+nothing to commit, working tree clean
-+EOF
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'setup for diverged from tracked but behind main' '
-+ (
-+ cd test &&
-+ git fetch origin &&
-+ git checkout origin/main &&
-+ git checkout -b work2b &&
-+ git branch --set-upstream-to=origin/oldfeature &&
-+ git checkout origin/main &&
-+ advance main_extra &&
-+ git push origin HEAD:main
-+ )
-+'
-+
-+test_expect_success 'status shows diverged from tracked and behind origin/main' '
-+ (
-+ cd test &&
-+ git checkout work2b >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work2b
-+Your branch and '\''origin/oldfeature'\'' have diverged,
-+and have 1 and 1 different commits each, respectively.
-+ (use "git pull" if you want to integrate the remote branch with yours)
-+
-+Behind '\''origin/main'\'' by 1 commit.
-+
-+nothing to commit, working tree clean
-+EOF
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'setup for behind tracked but ahead of main' '
-+ (
-+ cd test &&
-+ git fetch origin &&
-+ git checkout origin/main &&
-+ git checkout -b feature3 &&
-+ advance feature3_1 &&
-+ advance feature3_2 &&
-+ advance feature3_3 &&
-+ git push origin feature3 &&
-+ git checkout -b work3 --track origin/feature3 &&
-+ git reset --hard HEAD~2
++ git checkout feature4 &&
++ advance work3
+ )
+'
+
-+test_expect_success 'status shows behind tracked and ahead of origin/main' '
++test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
+ (
+ cd test &&
-+ git checkout work3 >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work3
-+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
-+ (use "git pull" to update your local branch)
-+
-+Ahead of '\''origin/main'\'' by 1 commit.
-+
-+nothing to commit, working tree clean
-+EOF
++ 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)
++
++ Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
++
++ nothing to commit, working tree clean
++ EOF
+ test_cmp expect actual
+'
+
-+test_expect_success 'setup upstream remote preference' '
++test_expect_success 'setup upstream remote' '
+ (
+ cd test &&
+ git remote add upstream ../. &&
+ git fetch upstream &&
-+ git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
++ git config remote.pushDefault origin
+ )
+'
+
-+test_expect_success 'status prefers upstream remote over origin for comparison' '
++test_expect_success 'status with upstream remote and push.default set to origin' '
+ (
+ cd test &&
-+ git checkout work >/dev/null &&
-+ git config status.goalBranch upstream/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work
-+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
-+ (use "git push" to publish your local commits)
-+
-+Diverged from '\''upstream/main'\'' by 5 commits.
-+
-+nothing to commit, working tree clean
-+EOF
++ 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.
++ (use "git push" to publish your local commits)
++
++ Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
++
++ nothing to commit, working tree clean
++ EOF
+ test_cmp expect actual
+'
+
-+test_expect_success 'setup for up to date with tracked but ahead of default' '
-+ (
-+ cd test &&
-+ git checkout origin/feature &&
-+ git checkout -b synced_feature --track origin/feature &&
-+ git checkout origin/main &&
-+ advance main_ahead &&
-+ git push origin HEAD:main
-+ )
-+'
-+
-+test_expect_success 'status shows up to date with tracked but diverged from default' '
++test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
+ (
+ cd test &&
-+ git checkout synced_feature >/dev/null &&
-+ git config status.goalBranch upstream/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+Diverged from '\''upstream/main'\'' by 3 commits.
-+
-+nothing to commit, working tree clean
-+EOF
++ 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.
++ (use "git push" to publish your local commits)
++
++ Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
++
++ nothing to commit, working tree clean
++ EOF
+ test_cmp expect actual
+'
+
-+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
++test_expect_success 'status with upstream remote and push branch up to date' '
+ (
+ cd test &&
-+ git remote remove upstream &&
-+ git checkout origin/feature &&
-+ git checkout -b synced_feature2 --track origin/feature &&
-+ git checkout origin/main &&
-+ advance main_ahead2 &&
-+ git push origin HEAD:main
-+ )
-+'
-+
-+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
-+ (
-+ cd test &&
-+ git checkout synced_feature2 >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature2
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+Diverged from '\''origin/main'\'' by 5 commits.
-+
-+nothing to commit, working tree clean
-+EOF
++ 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 'setup for up to date with tracked but purely ahead of origin/main' '
++test_expect_success 'checkout shows push branch up to date' '
+ (
+ cd test &&
-+ git checkout origin/feature &&
-+ git checkout -b synced_feature3 --track origin/feature
-+ )
-+'
++ git checkout feature7 >../actual
++ ) &&
++ cat >expect <<-EOF &&
++ Your branch is up to date with ${SQ}upstream/main${SQ}.
+
-+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
-+ (
-+ cd test &&
-+ git checkout synced_feature3 >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature3
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+Diverged from '\''origin/main'\'' by 5 commits.
-+
-+nothing to commit, working tree clean
-+EOF
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
-+ (
-+ cd test &&
-+ git checkout synced_feature3 >/dev/null &&
-+ git config --unset status.goalBranch 2>/dev/null || true &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature3
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+nothing to commit, working tree clean
-+EOF
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'status with status.goalBranch set uses configured branch' '
-+ (
-+ cd test &&
-+ git checkout synced_feature3 >/dev/null &&
-+ git config status.goalBranch origin/main &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature3
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+Diverged from '\''origin/main'\'' by 5 commits.
-+
-+nothing to commit, working tree clean
-+EOF
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'status with status.goalBranch set to different remote/branch' '
-+ (
-+ cd test &&
-+ git checkout work >/dev/null &&
-+ git config status.goalBranch origin/feature &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch work
-+Your branch is ahead of '\''origin/feature'\'' 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 with status.goalBranch set to non-existent branch' '
-+ (
-+ cd test &&
-+ git checkout synced_feature3 >/dev/null &&
-+ git config status.goalBranch origin/nonexistent &&
-+ git status --long -b
-+ ) >actual &&
-+ cat >expect <<-\EOF &&
-+On branch synced_feature3
-+Your branch is up to date with '\''origin/feature'\''.
-+
-+nothing to commit, working tree clean
-+EOF
++ Your branch is up to date with ${SQ}origin/feature7${SQ}.
++ EOF
+ test_cmp expect actual
+'
+
2: e6d24b8b6a < -: ---------- improve tests
3: 13c2a03b0a < -: ---------- use pushRemote and tracking branch
remote.c | 89 +++++++++++++++++++++
t/t6040-tracking-info.sh | 167 +++++++++++++++++++++++++++++++++++++++
2 files changed, 256 insertions(+)
diff --git a/remote.c b/remote.c
index 59b3715120..2317725f7d 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,81 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+ const char *push_remote;
+ const char *resolved;
+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *ret = NULL;
+
+ if (!branch)
+ return NULL;
+
+ push_remote = pushremote_for_branch(branch, NULL);
+ if (!push_remote)
+ return NULL;
+
+ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, &flag);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
+static void format_push_branch_comparison(struct strbuf *sb,
+ const char *branch_refname,
+ const char *push_full,
+ const char *push_short,
+ enum ahead_behind_flags abf)
+{
+ int push_ahead = 0, push_behind = 0;
+ int stat_result;
+
+ stat_result = stat_branch_pair(branch_refname, push_full,
+ &push_ahead, &push_behind, abf);
+ if (stat_result < 0)
+ return;
+
+ strbuf_addstr(sb, "\n");
+
+ if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ push_short);
+ } else if (push_ahead > 0 && push_behind == 0) {
+ strbuf_addf(sb,
+ Q_("Ahead of '%s' by %d commit.\n",
+ "Ahead of '%s' by %d commits.\n",
+ push_ahead),
+ push_short, push_ahead);
+ } else if (push_behind > 0 && push_ahead == 0) {
+ strbuf_addf(sb,
+ Q_("Behind '%s' by %d commit.\n",
+ "Behind '%s' by %d commits.\n",
+ push_behind),
+ push_short, push_behind);
+ } else if (push_ahead > 0 && push_behind > 0) {
+ strbuf_addf(sb,
+ Q_("Diverged from '%s' by %d commit.\n",
+ "Diverged from '%s' by %d commits.\n",
+ push_ahead + push_behind),
+ push_short, push_ahead + push_behind);
+ }
+}
+
/*
* Return true when there is anything to report, otherwise false.
*/
@@ -2258,6 +2333,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2387,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+
+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+ char *push_full = NULL;
+ char *push_short = get_remote_push_branch(branch, &push_full);
+
+ if (push_short && strcmp(base, push_short))
+ format_push_branch_comparison(sb, branch->refname, push_full,
+ push_short, abf);
+
+ free(push_short);
+ free(push_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..f27ae719ad 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,171 @@ 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 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.
+ (use "git push" to publish your local commits)
+
+ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+
+ 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.
+ (use "git push" to publish your local commits)
+
+ Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+ 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)
+
+ Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
+
+ 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.
+ (use "git push" to publish your local commits)
+
+ Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
+
+ 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.
+ (use "git push" to publish your local commits)
+
+ Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
+
+ 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 '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_done
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v11] status: show comparison with push remote tracking branch
2026-01-02 11:17 ` [PATCH v11] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-02 15:18 ` Phillip Wood
2026-01-02 20:27 ` Another look? Harald Nordgren
2026-01-02 21:34 ` [PATCH v12 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
1 sibling, 1 reply; 147+ messages in thread
From: Phillip Wood @ 2026-01-02 15:18 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git
Cc: Harald Nordgren, Junio C Hamano, Kristoffer Haugsbakk,
D. Ben Knoble, brian m . carlson
Hi Harald
On 02/01/2026 11:17, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> "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 remote's
> tracking branch (which may differ from the upstream branch), git status
> now shows an additional comparison.
This is great and it is really good that it is now using the default
push destination rather than a custom config key.
> When a push remote is configured (via branch.<name>.pushRemote or
> remote.pushDefault) git status shows both the comparison with the
> upstream tracking branch (as before) and an additional comparison with
> the push remote's tracking branch, if it differs from the upstream
> tracking branch.
Looking at the tests, it seems that the extra information is shown
whenever the upstream branch differs from the default push destination
even if they are on the same remote. I think that is sensible but this
paragraph should be updated to reflect the fact that one does not need
to set either of the "pushDefault" config settings to benefit from this.
> The push branch comparison appears on a separate
> line after the upstream branch status, using the same format:
> - "Ahead of 'origin/feature' by N commits" when purely ahead
> - "Behind 'origin/feature' by N commits" when purely behind
> - "Diverged from 'origin/feature' by N commits" when diverged
I'm wondering why we don't reuse the existing messages - I don't see how
using a different wording for the push destination compared to the
upstream branch benefits the user. We should adjust the hints that are
shown so that we only recommend pulling from the upstream branch and
only recommend pushing to the default push destination but the
comparisons should be the same. Also I don't find the message "Diverged
from 'origin/feature' by N commits' very helpful, I'd find it more
useful if it gave the ahead/behind count like we do for the upstream branch.
> Example output when tracking upstream/main with pushRemote set to origin:
> On branch feature
> Your branch is ahead of 'upstream/main' by 2 commits.
> (use "git pull" if you want to integrate the remote branch with yours)
Is this a typo? - why are we recommending "git pull" when our branch is
ahead of the upstream branch?
> Ahead of 'origin/feature' by 5 commits.
It would be nice to have a hint suggesting the user runs "git push" here.
> The comparison is only shown when a push remote is configured and the
> push remote's tracking branch differs from the upstream tracking branch.
Looking at the tests, only the second half of that sentence appears to
be true.
> diff --git a/remote.c b/remote.c
> index 59b3715120..2317725f7d 100644
> --- a/remote.c
> +++ b/remote.c
> @@ -2237,6 +2237,81 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
> return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
> }
>
> +static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
> +{
> + const char *push_remote;
> + const char *resolved;
> + int flag;
> + struct strbuf ref_buf = STRBUF_INIT;
> + char *ret = NULL;
> +
> + if (!branch)
> + return NULL;
> +
> + push_remote = pushremote_for_branch(branch, NULL);
> + if (!push_remote)
> + return NULL;
> +
> + strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
Shouldn't we be taking account of the push and fetch refspecs here?
There is no guarantee that $branch maps to refs/remotes/$remote/$branch.
To take a silly example, if we have
remote.$remote.fetch =
refs/heads/$branch:refs/remotes/$remote/abc-$branch
remote.$remote.pull = refs/heads/$branch:refs/heads/xyz-$branch
Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the
message above as "$branch" will be pushed to "xyz-$branch" on the remote
which is fetched to "$remote/abc-xyz-$branch"
> +
> + resolved = refs_resolve_ref_unsafe(
> + get_main_ref_store(the_repository),
> + ref_buf.buf,
> + RESOLVE_REF_READING,
> + NULL, &flag);
As we don't use flag we can pass NULL - see the documentation for this
function in refs.h
> +static void format_push_branch_comparison(struct strbuf *sb,
> + const char *branch_refname,
> + const char *push_full,
> + const char *push_short,
> + enum ahead_behind_flags abf)
> +{
> + int push_ahead = 0, push_behind = 0;
> + int stat_result;
> +
> + stat_result = stat_branch_pair(branch_refname, push_full,
> + &push_ahead, &push_behind, abf);
> + if (stat_result < 0)
> + return;
> +
> + strbuf_addstr(sb, "\n");
As I said above it would be nice if we could use the existing messages
here. Can we have a separate preparatory patch that refactors
fromat_tracking_info() so that we can use the same messages for the
upstream branch and the default push destination?
> @@ -2311,6 +2387,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> strbuf_addstr(sb,
> _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
> }
> +
> + if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
Why do we handle AHEAD_BEHIND_QUICK differently to the upstream branch?
Surely it would be useful to tell the user whether the branch is up to
date with the default push destination or not?
> diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
> index 0b719bbae6..f27ae719ad 100755
> --- a/t/t6040-tracking-info.sh
> +++ b/t/t6040-tracking-info.sh
> [...]
> +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.
> + (use "git push" to publish your local commits)
Why are we still suggesting pushing to the upstream branch when we know
the user is pushing to a different remote branch?
> + Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
Why aren't we suggesting to use "git push" here?
Thanks for working on this. With a few tweaks it will be a really useful
improvement to "git status"
Phillip
> + 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.
> + (use "git push" to publish your local commits)
> +
> + Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
> + 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)
> +
> + Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
> +
> + 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.
> + (use "git push" to publish your local commits)
> +
> + Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
> +
> + 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.
> + (use "git push" to publish your local commits)
> +
> + Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
> +
> + 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 '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_done
>
> base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-02 15:18 ` Phillip Wood
@ 2026-01-02 20:27 ` Harald Nordgren
2026-01-03 10:04 ` Phillip Wood
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-02 20:27 UTC (permalink / raw)
To: phillip.wood123
Cc: ben.knoble, git, gitgitgadget, gitster, haraldnordgren,
kristofferhaugsbakk, phillip.wood, sandals
> Looking at the tests, it seems that the extra information is shown
> whenever the upstream branch differs from the default push destination
> even if they are on the same remote. I think that is sensible but this
> paragraph should be updated to reflect the fact that one does not need to
> set either of the "pushDefault" config settings to benefit from this.
Good catch, will be included in the next diff!
> I'm wondering why we don't reuse the existing messages - I don't see
> how using a different wording for the push destination compared to the
> upstream branch benefits the user. We should adjust the hints that
> are shown so that we only recommend pulling from the upstream branch
> and only recommend pushing to the default push destination but the
> comparisons should be the same. Also I don't find the message "Diverged
> from 'origin/feature' by N commits' very helpful, I'd find it more useful
> if it gave the ahead/behind count like we do for the upstream branch.
Thanks for saying this, I actually did the majority of this work already
but was hesitant to include it in the diff because it increases the surface
area. But I will include it now!
> Shouldn't we be taking account of the push and fetch refspecs here? There
> is no guarantee that $branch maps to refs/remotes/$remote/$branch. To take
> a silly example, if we have
>
> remote.$remote.fetch =
> refs/heads/$branch:refs/remotes/$remote/abc-$branch remote.$remote.pull =
> refs/heads/$branch:refs/heads/xyz-$branch
>
> Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the
> message above as "$branch" will be pushed to "xyz-$branch" on the remote
> which is fetched to "$remote/abc-xyz-$branch"
I don't understand this one, can you maybe explain how to code will need to
change?
> As we don't use flag we can pass NULL - see the documentation for this
> function in refs.h
Thanks, will be included in the next diff!
> Why are we still suggesting pushing to the upstream branch when we know
the user is pushing to a different remote branch?
Thanks, will be included in the next diff!
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Another look?
2026-01-02 20:27 ` Another look? Harald Nordgren
@ 2026-01-03 10:04 ` Phillip Wood
2026-01-03 13:00 ` Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Phillip Wood @ 2026-01-03 10:04 UTC (permalink / raw)
To: Harald Nordgren
Cc: ben.knoble, git, gitgitgadget, gitster, kristofferhaugsbakk,
phillip.wood, sandals
On 02/01/2026 20:27, Harald Nordgren wrote:
>
>> Shouldn't we be taking account of the push and fetch refspecs here? There
>> is no guarantee that $branch maps to refs/remotes/$remote/$branch. To take
>> a silly example, if we have
>>
>> remote.$remote.fetch =
>> refs/heads/$branch:refs/remotes/$remote/abc-$branch remote.$remote.pull =
>> refs/heads/$branch:refs/heads/xyz-$branch
>>
>> Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the
>> message above as "$branch" will be pushed to "xyz-$branch" on the remote
>> which is fetched to "$remote/abc-xyz-$branch"
>
> I don't understand this one, can you maybe explain how to code will need to
> change?
You want to display the remote tracking ref that tracks ref on the
remote that we push to. When git pushes $branch to $remote it checks if
any of the push refspecs remote.$remote.push match $branch. If there is
a match then the branch name is mapped according to the refspec and that
it the ref that is updated in the remote repository. It then takes that
ref and checks the fetch refspecs remote.$remote.fetch and if it finds a
match it maps the ref we've pushed to a remote tracking ref in the local
repository.
It looks like you can find where we would push a branch to with
remote_ref_for_branch() and you can map that to a remote tracking ref
with tracking_for_push_dest(). We could use branch_get_push() but does
more than just map the refs as it checks push.default to see whether
"git push" would actually push the branch.
Thanks
Phillip
^ permalink raw reply [flat|nested] 147+ messages in thread
* Another look?
2026-01-03 10:04 ` Phillip Wood
@ 2026-01-03 13:00 ` Harald Nordgren
0 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-03 13:00 UTC (permalink / raw)
To: phillip.wood123
Cc: ben.knoble, git, gitgitgadget, gitster, haraldnordgren,
kristofferhaugsbakk, phillip.wood, sandals
> You want to display the remote tracking ref that tracks ref on the remote
> that we push to. When git pushes $branch to $remote it checks if any
> of the push refspecs remote.$remote.push match $branch. If there is a
> match then the branch name is mapped according to the refspec and that
> it the ref that is updated in the remote repository. It then takes that
> ref and checks the fetch refspecs remote.$remote.fetch and if it finds a
> match it maps the ref we've pushed to a remote tracking ref in the local
> repository.
>
> It looks like you can find where we would push a branch to with
> remote_ref_for_branch() and you can map that to a remote tracking ref with
> tracking_for_push_dest(). We could use branch_get_push() but does more
> than just map the refs as it checks push.default to see whether "git push"
> would actually push the branch.
Thanks! I have updated the code and added a test for remapped refspec.
With that said, I don't 100% understand what I'm doing with the refspecs, so
maybe you can give it another review?
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v12 0/2] status: show comparison with push remote tracking branch
2026-01-02 11:17 ` [PATCH v11] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-02 15:18 ` Phillip Wood
@ 2026-01-02 21:34 ` Harald Nordgren via GitGitGadget
2026-01-02 21:34 ` [PATCH v12 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
1 sibling, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-02 21:34 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 164 +++++++++++++++++++++++++++++---------
t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
2 files changed, 295 insertions(+), 37 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v12
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v12
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v11:
-: ---------- > 1: a2c160c53e refactor: format_branch_comparison in preparation
1: 53bb1cb6bb ! 2: a586038d1f status: show comparison with push remote tracking branch
@@ Commit message
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 remote's
+ 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 a push remote is configured (via branch.<name>.pushRemote or
- remote.pushDefault), git status shows both the comparison with the
- upstream tracking branch (as before) and an additional comparison with
- the push remote's tracking branch, if it differs from the upstream
- tracking branch. The push branch comparison appears on a separate
- line after the upstream branch status, using the same format:
- - "Ahead of 'origin/feature' by N commits" when purely ahead
- - "Behind 'origin/feature' by N commits" when purely behind
- - "Diverged from 'origin/feature' by N commits" when diverged
+ 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 upstream/main with pushRemote set to origin:
+ Example output when tracking origin/main but push destination is
+ origin/feature:
On branch feature
- Your branch is ahead of 'upstream/main' by 2 commits.
+ 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)
- Ahead of 'origin/feature' by 5 commits.
+ Your branch is ahead of 'origin/feature' by 1 commit.
+ (use "git push" to publish your local commits)
- The comparison is only shown when a push remote is configured and the
- push remote's tracking branch differs from the upstream tracking branch.
+ 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.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+{
+ const char *push_remote;
+ const char *resolved;
-+ int flag;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *ret = NULL;
+
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
-+ NULL, &flag);
++ NULL, NULL);
+
+ if (resolved) {
+ if (full_ref_out)
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+ return ret;
+}
+
-+static void format_push_branch_comparison(struct strbuf *sb,
-+ const char *branch_refname,
-+ const char *push_full,
-+ const char *push_short,
-+ enum ahead_behind_flags abf)
-+{
-+ int push_ahead = 0, push_behind = 0;
-+ int stat_result;
-+
-+ stat_result = stat_branch_pair(branch_refname, push_full,
-+ &push_ahead, &push_behind, abf);
-+ if (stat_result < 0)
-+ return;
-+
-+ strbuf_addstr(sb, "\n");
-+
-+ if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
-+ strbuf_addf(sb,
-+ _("Your branch is up to date with '%s'.\n"),
-+ push_short);
-+ } else if (push_ahead > 0 && push_behind == 0) {
-+ strbuf_addf(sb,
-+ Q_("Ahead of '%s' by %d commit.\n",
-+ "Ahead of '%s' by %d commits.\n",
-+ push_ahead),
-+ push_short, push_ahead);
-+ } else if (push_behind > 0 && push_ahead == 0) {
-+ strbuf_addf(sb,
-+ Q_("Behind '%s' by %d commit.\n",
-+ "Behind '%s' by %d commits.\n",
-+ push_behind),
-+ push_short, push_behind);
-+ } else if (push_ahead > 0 && push_behind > 0) {
-+ strbuf_addf(sb,
-+ Q_("Diverged from '%s' by %d commit.\n",
-+ "Diverged from '%s' by %d commits.\n",
-+ push_ahead + push_behind),
-+ push_short, push_ahead + push_behind);
-+ }
-+}
-+
- /*
- * Return true when there is anything to report, otherwise false.
- */
+ static void format_branch_comparison(struct strbuf *sb,
+ int ahead, int behind,
+ const char *branch_name,
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
++ int push_ours = 0, push_theirs = 0;
++ int push_stat_result = -1;
++ int will_show_push_comparison = 0;
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+
++ if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
++ char *push_full = NULL;
++ char *push_short = get_remote_push_branch(branch, &push_full);
+
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
++ if (push_short && strcmp(base, push_short)) {
++ push_stat_result = stat_branch_pair(branch->refname, push_full,
++ &push_ours, &push_theirs, abf);
++ if (push_stat_result >= 0)
++ will_show_push_comparison = 1;
++ }
++
++ free(push_short);
++ free(push_full);
++ }
++
+ format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+ if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
++ if (!theirs && !will_show_push_comparison &&
++ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
- _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ _(" (use \"git push\" to publish your local commits)\n"));
+ } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ }
}
-+
-+ if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+
++ if (will_show_push_comparison) {
+ char *push_full = NULL;
+ char *push_short = get_remote_push_branch(branch, &push_full);
+
-+ if (push_short && strcmp(base, push_short))
-+ format_push_branch_comparison(sb, branch->refname, push_full,
-+ push_short, abf);
++ if (push_short && strcmp(base, push_short)) {
++ strbuf_addstr(sb, "\n");
++ format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
++ push_ours || push_theirs);
++ if (push_ours > 0 && push_theirs == 0 &&
++ advice_enabled(ADVICE_STATUS_HINTS)) {
++ strbuf_addstr(sb,
++ _(" (use \"git push\" to publish your local commits)\n"));
++ }
++ }
+
+ free(push_short);
+ free(push_full);
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ cat >expect <<-EOF &&
+ On branch feature2
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
-+ (use "git push" to publish your local commits)
+
-+ Ahead of ${SQ}origin/feature2${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
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ ) &&
+ cat >expect <<-EOF &&
+ Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
-+ (use "git push" to publish your local commits)
+
-+ Ahead of ${SQ}origin/feature2${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
+'
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ and have 3 and 1 different commits each, respectively.
+ (use "git pull" if you want to integrate the remote branch with yours)
+
-+ Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
++ 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
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ cat >expect <<-EOF &&
+ On branch feature5
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
-+ (use "git push" to publish your local commits)
+
-+ Ahead of ${SQ}origin/feature5${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
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ cat >expect <<-EOF &&
+ On branch feature6
+ Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
-+ (use "git push" to publish your local commits)
+
-+ Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
++ 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
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v12 1/2] refactor: format_branch_comparison in preparation
2026-01-02 21:34 ` [PATCH v12 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-02 21:34 ` Harald Nordgren via GitGitGadget
2026-01-02 21:34 ` [PATCH v12 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-02 21:34 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 59 insertions(+), 40 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..58093f64b0 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+ int ahead, int behind,
+ const char *branch_name,
+ int upstream_is_gone,
+ enum ahead_behind_flags abf,
+ int sti)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
- } else if (!theirs) {
+ } else if (ahead == 0 && behind == 0) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
+ } else if (ahead > 0 && behind == 0) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
- ours),
- base, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git push\" to publish your local commits)\n"));
- } else if (!ours) {
+ ahead),
+ branch_name, ahead);
+ } else if (behind > 0 && ahead == 0) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
- theirs),
- base, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
- } else {
+ behind),
+ branch_name, behind);
+ } else if (ahead > 0 && behind > 0) {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
"and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- ours + theirs),
- base, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ ahead + behind),
+ branch_name, ahead, behind);
+ }
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+ if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+ if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ } else if (ours && theirs && show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
}
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v12 2/2] status: show comparison with push remote tracking branch
2026-01-02 21:34 ` [PATCH v12 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-02 21:34 ` [PATCH v12 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-02 21:34 ` Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-02 21:34 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 73 ++++++++++++++++-
t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
2 files changed, 240 insertions(+), 1 deletion(-)
diff --git a/remote.c b/remote.c
index 58093f64b0..1663bd9236 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,39 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+ const char *push_remote;
+ const char *resolved;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *ret = NULL;
+
+ if (!branch)
+ return NULL;
+
+ push_remote = pushremote_for_branch(branch, NULL);
+ if (!push_remote)
+ return NULL;
+
+ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, NULL);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
static void format_branch_comparison(struct strbuf *sb,
int ahead, int behind,
const char *branch_name,
@@ -2304,6 +2337,9 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ int push_ours = 0, push_theirs = 0;
+ int push_stat_result = -1;
+ int will_show_push_comparison = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2315,9 +2351,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
+ if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
+ char *push_full = NULL;
+ char *push_short = get_remote_push_branch(branch, &push_full);
+
+ if (push_short && strcmp(base, push_short)) {
+ push_stat_result = stat_branch_pair(branch->refname, push_full,
+ &push_ours, &push_theirs, abf);
+ if (push_stat_result >= 0)
+ will_show_push_comparison = 1;
+ }
+
+ free(push_short);
+ free(push_full);
+ }
+
format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (!theirs && !will_show_push_comparison &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,6 +2382,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
}
+ if (will_show_push_comparison) {
+ char *push_full = NULL;
+ char *push_short = get_remote_push_branch(branch, &push_full);
+
+ if (push_short && strcmp(base, push_short)) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
+ push_ours || push_theirs);
+ if (push_ours > 0 && push_theirs == 0 &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ }
+ }
+
+ free(push_short);
+ free(push_full);
+ }
+
free(base);
return 1;
}
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0b719bbae6..8eb1f3e1f1 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,172 @@ 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 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 '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 '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_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v13 0/2] status: show comparison with push remote tracking branch
2026-01-02 21:34 ` [PATCH v12 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-02 21:34 ` [PATCH v12 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-02 21:34 ` [PATCH v12 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-03 3:08 ` Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 3:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 152 ++++++++++++++++++++++++++---------
t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
2 files changed, 283 insertions(+), 37 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v13
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v13
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v12:
1: a2c160c53e = 1: a2c160c53e refactor: format_branch_comparison in preparation
2: a586038d1f ! 2: 891239211e status: show comparison with push remote tracking branch
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
-+ int push_ours = 0, push_theirs = 0;
-+ int push_stat_result = -1;
-+ int will_show_push_comparison = 0;
++ int push_ours, push_theirs, push_sti;
++ char *full_push = NULL;
++ char *push = NULL;
++ int show_push_comparison = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
full_base, 0);
-+ if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
-+ char *push_full = NULL;
-+ char *push_short = get_remote_push_branch(branch, &push_full);
-+
-+ if (push_short && strcmp(base, push_short)) {
-+ push_stat_result = stat_branch_pair(branch->refname, push_full,
-+ &push_ours, &push_theirs, abf);
-+ if (push_stat_result >= 0)
-+ will_show_push_comparison = 1;
-+ }
-+
-+ free(push_short);
-+ free(push_full);
++ push = get_remote_push_branch(branch, &full_push);
++ if (push && strcmp(base, push)) {
++ push_sti = stat_branch_pair(branch->refname, full_push,
++ &push_ours, &push_theirs, abf);
++ if (push_sti >= 0)
++ show_push_comparison = 1;
+ }
+
format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
-+ if (!theirs && !will_show_push_comparison &&
++ if (!theirs && !show_push_comparison &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
}
-+ if (will_show_push_comparison) {
-+ char *push_full = NULL;
-+ char *push_short = get_remote_push_branch(branch, &push_full);
-+
-+ if (push_short && strcmp(base, push_short)) {
-+ strbuf_addstr(sb, "\n");
-+ format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
-+ push_ours || push_theirs);
-+ if (push_ours > 0 && push_theirs == 0 &&
-+ advice_enabled(ADVICE_STATUS_HINTS)) {
++ if (show_push_comparison) {
++ strbuf_addstr(sb, "\n");
++ format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
++ if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
++ if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ }
+ }
-+
-+ free(push_short);
-+ free(push_full);
+ }
+
free(base);
++ free(full_push);
++ free(push);
return 1;
}
+
## t/t6040-tracking-info.sh ##
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v13 1/2] refactor: format_branch_comparison in preparation
2026-01-03 3:08 ` [PATCH v13 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-03 3:08 ` Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 3:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 59 insertions(+), 40 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..58093f64b0 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+ int ahead, int behind,
+ const char *branch_name,
+ int upstream_is_gone,
+ enum ahead_behind_flags abf,
+ int sti)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
- } else if (!theirs) {
+ } else if (ahead == 0 && behind == 0) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
+ } else if (ahead > 0 && behind == 0) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
- ours),
- base, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git push\" to publish your local commits)\n"));
- } else if (!ours) {
+ ahead),
+ branch_name, ahead);
+ } else if (behind > 0 && ahead == 0) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
- theirs),
- base, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
- } else {
+ behind),
+ branch_name, behind);
+ } else if (ahead > 0 && behind > 0) {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
"and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- ours + theirs),
- base, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ ahead + behind),
+ branch_name, ahead, behind);
+ }
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+ if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+ if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ } else if (ours && theirs && show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
}
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v13 2/2] status: show comparison with push remote tracking branch
2026-01-03 3:08 ` [PATCH v13 0/2] " Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-03 3:08 ` Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 3:08 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 61 +++++++++++++-
t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
2 files changed, 228 insertions(+), 1 deletion(-)
diff --git a/remote.c b/remote.c
index 58093f64b0..c90441fe42 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,39 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+ const char *push_remote;
+ const char *resolved;
+ struct strbuf ref_buf = STRBUF_INIT;
+ char *ret = NULL;
+
+ if (!branch)
+ return NULL;
+
+ push_remote = pushremote_for_branch(branch, NULL);
+ if (!push_remote)
+ return NULL;
+
+ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
+ ref_buf.buf,
+ RESOLVE_REF_READING,
+ NULL, NULL);
+
+ if (resolved) {
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ }
+
+ strbuf_release(&ref_buf);
+ return ret;
+}
+
static void format_branch_comparison(struct strbuf *sb,
int ahead, int behind,
const char *branch_name,
@@ -2304,6 +2337,10 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
+ int show_push_comparison = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2315,9 +2352,18 @@ 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)) {
+ push_sti = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_sti >= 0)
+ show_push_comparison = 1;
+ }
+
format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (!theirs && !show_push_comparison &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,7 +2376,20 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
}
+ if (show_push_comparison) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
+ if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+ if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ }
+ }
+ }
+
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..8eb1f3e1f1 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,172 @@ 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 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 '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 '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_done
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v14 0/2] status: show comparison with push remote tracking branch
2026-01-03 3:08 ` [PATCH v13 0/2] " Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-03 3:08 ` [PATCH v13 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-03 13:00 ` Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 13:00 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 171 ++++++++++++++++++++++++-------
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 344 insertions(+), 37 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v14
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v14
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v13:
1: a2c160c53e = 1: a2c160c53e refactor: format_branch_comparison in preparation
2: 891239211e ! 2: b9b2f15498 status: show comparison with push remote tracking branch
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+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;
-+ struct strbuf ref_buf = STRBUF_INIT;
-+ char *ret = NULL;
++ char *ret;
+
+ if (!branch)
+ return NULL;
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+ if (!push_remote)
+ return NULL;
+
-+ strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
++ 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);
++
++ if (!tracking_ref)
++ return NULL;
+
+ resolved = refs_resolve_ref_unsafe(
+ get_main_ref_store(the_repository),
-+ ref_buf.buf,
++ tracking_ref,
+ RESOLVE_REF_READING,
+ NULL, NULL);
+
-+ if (resolved) {
-+ if (full_ref_out)
-+ *full_ref_out = xstrdup(resolved);
-+ ret = refs_shorten_unambiguous_ref(
-+ get_main_ref_store(the_repository), resolved, 0);
++ if (!resolved) {
++ free(tracking_ref);
++ return NULL;
+ }
+
-+ strbuf_release(&ref_buf);
++ if (full_ref_out)
++ *full_ref_out = xstrdup(resolved);
++ ret = refs_shorten_unambiguous_ref(
++ get_main_ref_store(the_repository), resolved, 0);
++ free(tracking_ref);
+ return ret;
+}
+
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ 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
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v14 1/2] refactor: format_branch_comparison in preparation
2026-01-03 13:00 ` [PATCH v14 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-03 13:00 ` Harald Nordgren via GitGitGadget
2026-01-04 4:40 ` Junio C Hamano
2026-01-03 13:00 ` [PATCH v14 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 13:00 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 59 insertions(+), 40 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..58093f64b0 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+ int ahead, int behind,
+ const char *branch_name,
+ int upstream_is_gone,
+ enum ahead_behind_flags abf,
+ int sti)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
- } else if (!theirs) {
+ } else if (ahead == 0 && behind == 0) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
+ } else if (ahead > 0 && behind == 0) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
- ours),
- base, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git push\" to publish your local commits)\n"));
- } else if (!ours) {
+ ahead),
+ branch_name, ahead);
+ } else if (behind > 0 && ahead == 0) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
- theirs),
- base, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git pull\" to update your local branch)\n"));
- } else {
+ behind),
+ branch_name, behind);
+ } else if (ahead > 0 && behind > 0) {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
"and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- ours + theirs),
- base, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ ahead + behind),
+ branch_name, ahead, behind);
+ }
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+ if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+ if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ } else if (ours && theirs && show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
}
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v14 1/2] refactor: format_branch_comparison in preparation
2026-01-03 13:00 ` [PATCH v14 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-04 4:40 ` Junio C Hamano
2026-01-04 10:27 ` Another look? Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-04 4:40 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> Refactor format_branch_comparison function in preparation for showing
> comparison with push remote tracking branch.
>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
> remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
> 1 file changed, 59 insertions(+), 40 deletions(-)
>
> diff --git a/remote.c b/remote.c
> index 59b3715120..58093f64b0 100644
> --- a/remote.c
> +++ b/remote.c
> @@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
> return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
> }
>
> -/*
> - * Return true when there is anything to report, otherwise false.
> - */
> -int format_tracking_info(struct branch *branch, struct strbuf *sb,
> - enum ahead_behind_flags abf,
> - int show_divergence_advice)
> +static void format_branch_comparison(struct strbuf *sb,
> + int ahead, int behind,
> + const char *branch_name,
> + int upstream_is_gone,
> + enum ahead_behind_flags abf,
> + int sti)
> {
> - int ours, theirs, sti;
> - const char *full_base;
> - char *base;
> - int upstream_is_gone = 0;
> -
> - sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
> - if (sti < 0) {
> - if (!full_base)
> - return 0;
> - upstream_is_gone = 1;
> - }
> -
> - base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
> - full_base, 0);
> if (upstream_is_gone) {
> strbuf_addf(sb,
> _("Your branch is based on '%s', but the upstream is gone.\n"),
> - base);
> + branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addstr(sb,
> _(" (use \"git branch --unset-upstream\" to fixup)\n"));
> } else if (!sti) {
> strbuf_addf(sb,
> _("Your branch is up to date with '%s'.\n"),
> - base);
> + branch_name);
So, we used to say this when stat-tracking-info gave 0, i.e., ours
and theirs have not diverged. But now you decided to make the
caller responsible for making the call to stat_tracking_info(), it
seems to me that this if/else-if arm is somewhat redundant given
that the caller can and will signal the same thing with both ahead
and behind being zero.
IOW, it smells to me that leaving "sti" as a parameter to this
function is an incomplete refactoring---I say "smell" because I
haven't seen the other, new, caller that will be added in the future
step of this patch series.
And if the new calling convention is to let the caller be
responsible for calling stat_tracking_info() and figuring out the
base branch name, it probably is also better to have the caller
handle upstream_is_gone case. This is especially true if your plan
is to reuse this "branch comparison" helper to compare a branch with
another branch that is *NOT* its upstream (e.g., where the result is
pushed to).
> } else if (abf == AHEAD_BEHIND_QUICK) {
> strbuf_addf(sb,
> _("Your branch and '%s' refer to different commits.\n"),
> - base);
> + branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> "git status --ahead-behind");
> - } else if (!theirs) {
As the code already handled !sti, which is equivalent to (!theirs &&
!ours), in the original, !theirs here meant (!theirs && ours), which
is (ahead && !behind) in the new world order, which we see a few lines
below.
> + } else if (ahead == 0 && behind == 0) {
And this is what the above (!sti) should have checked for, after the
helper function lost the sti parameter.
Do not compare with 0 for equality. Instead write it like so:
} else if (!ahead && !behind) {
I won't repeat for other style violations of the same kind.
> + strbuf_addf(sb,
> + _("Your branch is up to date with '%s'.\n"),
> + branch_name);
> + } else if (ahead > 0 && behind == 0) {
> strbuf_addf(sb,
> Q_("Your branch is ahead of '%s' by %d commit.\n",
> "Your branch is ahead of '%s' by %d commits.\n",
> - ours),
> - base, ours);
> - if (advice_enabled(ADVICE_STATUS_HINTS))
> - strbuf_addstr(sb,
> - _(" (use \"git push\" to publish your local commits)\n"));
> - } else if (!ours) {
> + ahead),
> + branch_name, ahead);
> + } else if (behind > 0 && ahead == 0) {
> strbuf_addf(sb,
> Q_("Your branch is behind '%s' by %d commit, "
> "and can be fast-forwarded.\n",
> "Your branch is behind '%s' by %d commits, "
> "and can be fast-forwarded.\n",
> - theirs),
> - base, theirs);
> - if (advice_enabled(ADVICE_STATUS_HINTS))
> - strbuf_addstr(sb,
> - _(" (use \"git pull\" to update your local branch)\n"));
> - } else {
> + behind),
> + branch_name, behind);
> + } else if (ahead > 0 && behind > 0) {
> strbuf_addf(sb,
> Q_("Your branch and '%s' have diverged,\n"
> "and have %d and %d different commit each, "
> @@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> "Your branch and '%s' have diverged,\n"
> "and have %d and %d different commits each, "
> "respectively.\n",
> - ours + theirs),
> - base, ours, theirs);
> - if (show_divergence_advice &&
> - advice_enabled(ADVICE_STATUS_HINTS))
> + ahead + behind),
> + branch_name, ahead, behind);
> + }
> +}
> +
> +/*
> + * Return true when there is anything to report, otherwise false.
> + */
> +int format_tracking_info(struct branch *branch, struct strbuf *sb,
> + enum ahead_behind_flags abf,
> + int show_divergence_advice)
> +{
> + int ours, theirs, sti;
> + const char *full_base;
> + char *base;
> + int upstream_is_gone = 0;
> +
> + sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
> + if (sti < 0) {
> + if (!full_base)
> + return 0;
> + upstream_is_gone = 1;
> + }
> +
> + base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
> + full_base, 0);
> +
> + format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
> + if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
> + if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
> + strbuf_addstr(sb,
> + _(" (use \"git push\" to publish your local commits)\n"));
> + } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
> + strbuf_addstr(sb,
> + _(" (use \"git pull\" to update your local branch)\n"));
> + } else if (ours && theirs && show_divergence_advice &&
> + advice_enabled(ADVICE_STATUS_HINTS)) {
> strbuf_addstr(sb,
> _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
> + }
> }
> +
> free(base);
> return 1;
> }
Overall I think this is a reasonable direction to go, even though
passing the "sti" thing is iffy, and the implementation in the
function may have small rooms for improvements.
I didn't look at minute details to ensure that the output for all
cases are identical to that of the original, though.
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-04 4:40 ` Junio C Hamano
@ 2026-01-04 10:27 ` Harald Nordgren
2026-01-04 10:48 ` Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-04 10:27 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> And if the new calling convention is to let the caller be responsible for
> calling stat_tracking_info() and figuring out the base branch name, it
> probably is also better to have the caller handle upstream_is_gone case.
> This is especially true if your plan is to reuse this "branch comparison"
> helper to compare a branch with another branch that is *NOT* its upstream
> (e.g., where the result is pushed to).
Done, will be in the next patch!
> IOW, it smells to me that leaving "sti" as a parameter to this function
> is an incomplete refactoring---I say "smell" because I haven't seen the
> other, new, caller that will be added in the future step of this patch
> series.
>
> [...]
>
> As the code already handled !sti, which is equivalent to (!theirs &&
> !ours), in the original, !theirs here meant (!theirs && ours), which is
> (ahead && !behind) in the new world order, which we see a few lines below.
This sounds reasonable, however, if I replace '!sti' with 'ahead && !behind'
then old tests are breaking, one why to avoid them breaking is to switch the
order of these cases, to this, would that be acceptable?
``` } else if (abf == AHEAD_BEHIND_QUICK) { strbuf_addf(sb, _("Your
branch and '%s' refer to different commits.\n"), branch_name); if
(advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addf(sb, _(" (use \"%s\" for
details)\n"), "git status --ahead-behind"); } else if (!theirs && !ours) {
strbuf_addf(sb, _("Your branch is up to date with '%s'.\n"), branch_name);
```
> Do not compare with 0 for equality. Instead write it like so:
Good point! I will upate it!
Thanks for you continued attention to this, much appreciated!
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-04 10:27 ` Another look? Harald Nordgren
@ 2026-01-04 10:48 ` Harald Nordgren
2026-01-05 1:16 ` Junio C Hamano
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-04 10:48 UTC (permalink / raw)
To: haraldnordgren; +Cc: git, gitgitgadget, gitster
Sorry, here's what the code block will look like:
```
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs && !ours) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
branch_name);
```
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: Another look?
2026-01-04 10:48 ` Harald Nordgren
@ 2026-01-05 1:16 ` Junio C Hamano
0 siblings, 0 replies; 147+ messages in thread
From: Junio C Hamano @ 2026-01-05 1:16 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
> Sorry, here's what the code block will look like:
>
> ```
> } else if (abf == AHEAD_BEHIND_QUICK) {
> strbuf_addf(sb,
> _("Your branch and '%s' refer to different commits.\n"),
> branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> "git status --ahead-behind");
> } else if (!theirs && !ours) {
> strbuf_addf(sb,
> _("Your branch is up to date with '%s'.\n"),
> branch_name);
> ```
I did not check what comes before or after this block, but the above
looks like a reversed ordering. If you have "the branches are the
same" check first, it would make more sense, as after ruling out
that case, QUICK can short-cut comparison and asy "they are
different", and presumably after these two else/if arms, you'd have
cases for "theirs && !ours -> they are ahead of us", "!theirs &&
ours -> we are ahead of them", and "theirs && ours -> we diverged"
to handle.
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v14 2/2] status: show comparison with push remote tracking branch
2026-01-03 13:00 ` [PATCH v14 0/2] " Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-03 13:00 ` Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-03 13:00 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 80 ++++++++++++++-
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 289 insertions(+), 1 deletion(-)
diff --git a/remote.c b/remote.c
index 58093f64b0..f5d690d377 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,58 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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 ahead, int behind,
const char *branch_name,
@@ -2304,6 +2356,10 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
+ int show_push_comparison = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2315,9 +2371,18 @@ 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)) {
+ push_sti = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_sti >= 0)
+ show_push_comparison = 1;
+ }
+
format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (!theirs && !show_push_comparison &&
+ advice_enabled(ADVICE_STATUS_HINTS)) {
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,7 +2395,20 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
}
}
+ if (show_push_comparison) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
+ if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+ if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ }
+ }
+ }
+
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..68f298bf3a 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,214 @@ 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 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 '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 '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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v15 0/2] status: show comparison with push remote tracking branch
2026-01-03 13:00 ` [PATCH v14 0/2] " Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 1/2] refactor: format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-03 13:00 ` [PATCH v14 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-04 11:53 ` Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 11:53 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 168 ++++++++++++++++++++++++-------
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 342 insertions(+), 36 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v15
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v15
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v14:
1: a2c160c53e ! 1: cf4e9779c5 refactor: format_branch_comparison in preparation
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- refactor: format_branch_comparison in preparation
+ refactor format_branch_comparison in preparation
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
-+ int ahead, int behind,
++ int ours, int theirs,
+ const char *branch_name,
-+ int upstream_is_gone,
+ enum ahead_behind_flags abf,
-+ int sti)
++ int show_divergence_advice)
{
- int ours, theirs, sti;
- const char *full_base;
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
+- if (upstream_is_gone) {
+- strbuf_addf(sb,
+- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
-+ branch_name);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
- strbuf_addf(sb,
- _("Your branch is up to date with '%s'.\n"),
+- if (advice_enabled(ADVICE_STATUS_HINTS))
+- strbuf_addstr(sb,
+- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+- } else if (!sti) {
+- strbuf_addf(sb,
+- _("Your branch is up to date with '%s'.\n"),
- base);
-+ branch_name);
- } else if (abf == AHEAD_BEHIND_QUICK) {
+- } else if (abf == AHEAD_BEHIND_QUICK) {
++ if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
-- } else if (!theirs) {
-+ } else if (ahead == 0 && behind == 0) {
++ } else if (!ours && !theirs) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
-+ } else if (ahead > 0 && behind == 0) {
+ } else if (!theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
-- ours),
+ ours),
- base, ours);
-- if (advice_enabled(ADVICE_STATUS_HINTS))
-- strbuf_addstr(sb,
-- _(" (use \"git push\" to publish your local commits)\n"));
-- } else if (!ours) {
-+ ahead),
-+ branch_name, ahead);
-+ } else if (behind > 0 && ahead == 0) {
- strbuf_addf(sb,
- Q_("Your branch is behind '%s' by %d commit, "
- "and can be fast-forwarded.\n",
++ branch_name, ours);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
-- theirs),
+ theirs),
- base, theirs);
-- if (advice_enabled(ADVICE_STATUS_HINTS))
-- strbuf_addstr(sb,
-- _(" (use \"git pull\" to update your local branch)\n"));
-- } else {
-+ behind),
-+ branch_name, behind);
-+ } else if (ahead > 0 && behind > 0) {
- strbuf_addf(sb,
- Q_("Your branch and '%s' have diverged,\n"
- "and have %d and %d different commit each, "
++ branch_name, theirs);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- "Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
-- ours + theirs),
+ ours + theirs),
- base, ours, theirs);
-- if (show_divergence_advice &&
-- advice_enabled(ADVICE_STATUS_HINTS))
-+ ahead + behind),
-+ branch_name, ahead, behind);
-+ }
++ branch_name, ours, theirs);
+ if (show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
+}
+
+/*
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
-+ format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
-+ if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-+ if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
-+ strbuf_addstr(sb,
-+ _(" (use \"git push\" to publish your local commits)\n"));
-+ } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
++ if (upstream_is_gone) {
++ strbuf_addf(sb,
++ _("Your branch is based on '%s', but the upstream is gone.\n"),
++ base);
++ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
-+ _(" (use \"git pull\" to update your local branch)\n"));
-+ } else if (ours && theirs && show_divergence_advice &&
-+ advice_enabled(ADVICE_STATUS_HINTS)) {
- strbuf_addstr(sb,
- _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
-+ }
- }
++ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
++ } else {
++ format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
++ }
+
free(base);
return 1;
2: b9b2f15498 ! 2: a435cf4ce4 status: show comparison with push remote tracking branch
@@ Commit message
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## remote.c ##
+@@
+
+ enum map_direction { FROM_SRC, FROM_DST };
+
++enum branch_type {
++ PUSH = 1 << 0,
++ PULL = 1 << 1
++};
++
+ struct counted_string {
+ size_t len;
+ const char *s;
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+}
+
static void format_branch_comparison(struct strbuf *sb,
- int ahead, int behind,
+ int ours, int theirs,
const char *branch_name,
+ enum ahead_behind_flags abf,
++ enum branch_type bt,
+ int show_divergence_advice)
+ {
+ if (abf == AHEAD_BEHIND_QUICK) {
+@@ remote.c: 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 ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git push\" to publish your local commits)\n"));
+ } else if (!ours) {
+@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
+ "and can be fast-forwarded.\n",
+ theirs),
+ branch_name, theirs);
+- if (advice_enabled(ADVICE_STATUS_HINTS))
++ if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" to update your local branch)\n"));
+ } else {
+@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
+ "respectively.\n",
+ ours + theirs),
+ branch_name, ours, theirs);
+- if (show_divergence_advice &&
++ if ((bt & PULL) &&
++ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
++ enum branch_type base_bt = PUSH | PULL;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
-+ int show_push_comparison = 0;
++ enum branch_type push_bt = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ if (push && strcmp(base, push)) {
+ push_sti = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
-+ if (push_sti >= 0)
-+ show_push_comparison = 1;
++ if (push_sti >= 0) {
++ base_bt = PULL;
++ push_bt = PUSH;
++ }
+ }
+
- format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
- if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-- if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
-+ if (!theirs && !show_push_comparison &&
-+ advice_enabled(ADVICE_STATUS_HINTS)) {
- strbuf_addstr(sb,
- _(" (use \"git push\" to publish your local commits)\n"));
- } else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- }
- }
-
-+ if (show_push_comparison) {
-+ strbuf_addstr(sb, "\n");
-+ format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
-+ if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-+ if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
-+ strbuf_addstr(sb,
-+ _(" (use \"git push\" to publish your local commits)\n"));
-+ }
-+ }
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+- format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
++ format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
++ show_divergence_advice);
+ }
+
++ if (push_bt & PUSH) {
++ strbuf_addstr(sb, "\n");
++ format_branch_comparison(sb, push_ours, push_theirs, push, abf,
++ push_bt, 0);
+ }
+
free(base);
+ free(full_push);
+ free(push);
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v15 1/2] refactor format_branch_comparison in preparation
2026-01-04 11:53 ` [PATCH v15 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-04 11:53 ` Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 11:53 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 85 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 49 insertions(+), 36 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..b6a9e14376 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,51 +2237,29 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
- strbuf_addf(sb,
- _("Your branch is up to date with '%s'.\n"),
- base);
- } else if (abf == AHEAD_BEHIND_QUICK) {
+ if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
+ } else if (!ours && !theirs) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
} else if (!theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2270,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2283,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v15 2/2] status: show comparison with push remote tracking branch
2026-01-04 11:53 ` [PATCH v15 0/2] " Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-04 11:53 ` Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 11:53 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 91 ++++++++++++++++-
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 297 insertions(+), 4 deletions(-)
diff --git a/remote.c b/remote.c
index b6a9e14376..3a8706395a 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,11 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum branch_type {
+ PUSH = 1 << 0,
+ PULL = 1 << 1
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,10 +2242,63 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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 ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ enum branch_type bt,
int show_divergence_advice)
{
if (abf == AHEAD_BEHIND_QUICK) {
@@ -2260,7 +2318,7 @@ 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 ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2271,7 +2329,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2284,7 +2342,8 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
+ if ((bt & PULL) &&
+ show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2302,6 +2361,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ enum branch_type base_bt = PUSH | PULL;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
+ enum branch_type push_bt = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2313,6 +2377,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)) {
+ push_sti = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_sti >= 0) {
+ base_bt = PULL;
+ push_bt = PUSH;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2321,10 +2395,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, ours, theirs, base, abf, show_divergence_advice);
+ format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
+ show_divergence_advice);
+ }
+
+ if (push_bt & PUSH) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, push_ours, push_theirs, push, abf,
+ push_bt, 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..68f298bf3a 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,214 @@ 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 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 '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 '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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v16 0/2] status: show comparison with push remote tracking branch
2026-01-04 11:53 ` [PATCH v15 0/2] " Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-04 11:53 ` [PATCH v15 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-04 23:21 ` Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 23:21 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 171 ++++++++++++++++++++++++-------
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 345 insertions(+), 36 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v16
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v16
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v15:
1: cf4e9779c5 = 1: cf4e9779c5 refactor format_branch_comparison in preparation
2: a435cf4ce4 ! 2: 06cb483f61 status: show comparison with push remote tracking branch
@@ remote.c
enum map_direction { FROM_SRC, FROM_DST };
-+enum branch_type {
-+ PUSH = 1 << 0,
-+ PULL = 1 << 1
++enum branch_mode_flags {
++ BRANCH_MODE_PULL = (1 << 0),
++ BRANCH_MODE_PUSH = (1 << 1),
+};
+
struct counted_string {
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+
+ if (full_ref_out)
+ *full_ref_out = xstrdup(resolved);
++
+ ret = refs_shorten_unambiguous_ref(
+ get_main_ref_store(the_repository), resolved, 0);
+ free(tracking_ref);
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
-+ enum branch_type bt,
++ enum branch_mode_flags advice_flags,
int show_divergence_advice)
{
if (abf == AHEAD_BEHIND_QUICK) {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
ours),
branch_name, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
-+ if ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
++ if ((advice_flags & BRANCH_MODE_PUSH) &&
++ advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
-+ if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
++ if ((advice_flags & BRANCH_MODE_PULL) &&
++ advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
-+ if ((bt & PULL) &&
++ if ((advice_flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
-+ enum branch_type base_bt = PUSH | PULL;
++ enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
-+ enum branch_type push_bt = 0;
++ enum branch_mode_flags push_branch_modes = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ push_sti = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_sti >= 0) {
-+ base_bt = PULL;
-+ push_bt = PUSH;
++ base_branch_modes = BRANCH_MODE_PULL;
++ push_branch_modes = BRANCH_MODE_PUSH;
+ }
+ }
+
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
} else {
- format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
-+ format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
-+ show_divergence_advice);
++ format_branch_comparison(sb, ours, theirs, base, abf,
++ base_branch_modes, show_divergence_advice);
+ }
+
-+ if (push_bt & PUSH) {
++ if (push_branch_modes & BRANCH_MODE_PUSH) {
+ strbuf_addstr(sb, "\n");
+ format_branch_comparison(sb, push_ours, push_theirs, push, abf,
-+ push_bt, 0);
++ push_branch_modes, 0);
}
free(base);
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v16 1/2] refactor format_branch_comparison in preparation
2026-01-04 23:21 ` [PATCH v16 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-04 23:21 ` Harald Nordgren via GitGitGadget
2026-01-05 2:05 ` Junio C Hamano
2026-01-04 23:21 ` [PATCH v16 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 23:21 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 85 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 49 insertions(+), 36 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..b6a9e14376 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,51 +2237,29 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
- strbuf_addf(sb,
- _("Your branch is up to date with '%s'.\n"),
- base);
- } else if (abf == AHEAD_BEHIND_QUICK) {
+ if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
+ } else if (!ours && !theirs) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
+ branch_name);
} else if (!theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2270,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2283,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v16 1/2] refactor format_branch_comparison in preparation
2026-01-04 23:21 ` [PATCH v16 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-05 2:05 ` Junio C Hamano
2026-01-05 9:15 ` Another look? Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-05 2:05 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> - } else if (!sti) {
> - strbuf_addf(sb,
> - _("Your branch is up to date with '%s'.\n"),
> - base);
> - } else if (abf == AHEAD_BEHIND_QUICK) {
> + if (abf == AHEAD_BEHIND_QUICK) {
> strbuf_addf(sb,
> _("Your branch and '%s' refer to different commits.\n"),
> - base);
> + branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> "git status --ahead-behind");
> + } else if (!ours && !theirs) {
> + strbuf_addf(sb,
> + _("Your branch is up to date with '%s'.\n"),
> + branch_name);
We used to check if there is nothing to report (i.e., !sti is a
signal from stat_tracking_info() that there are no differences
between the branches) and reported that first, so when abf was
checked, we knew that there are some differences. Now, your patch
reverses the order so whether there is or is not a change, abf
codepath will always report "you have differences!".
This smells iffy.
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-05 2:05 ` Junio C Hamano
@ 2026-01-05 9:15 ` Harald Nordgren
2026-01-05 9:46 ` Harald Nordgren
2026-01-05 12:28 ` Junio C Hamano
0 siblings, 2 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-05 9:15 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> > - } else if (!sti) {
> > - strbuf_addf(sb,
> > - _("Your branch is up to date with '%s'.\n"),
> > - base);
> > - } else if (abf == AHEAD_BEHIND_QUICK) {
> > + if (abf == AHEAD_BEHIND_QUICK) {
> > strbuf_addf(sb,
> > _("Your branch and '%s' refer to different commits.\n"),
> > - base);
> > + branch_name);
> > if (advice_enabled(ADVICE_STATUS_HINTS))
> > strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> > "git status --ahead-behind");
> > + } else if (!ours && !theirs) {
> > + strbuf_addf(sb,
> > + _("Your branch is up to date with '%s'.\n"),
> > + branch_name);
>
> We used to check if there is nothing to report (i.e., !sti is a
> signal from stat_tracking_info() that there are no differences
> between the branches) and reported that first, so when abf was
> checked, we knew that there are some differences. Now, your patch
> reverses the order so whether there is or is not a change, abf
> codepath will always report "you have differences!".
Agreed! This change was done to get rid of sti as a parameter. Maybe I
misunderstood your previous comment around the sti.
I will bring back sti as a parameter to ’format_branch_comparison’ now,
I don’t see any other way to solve this.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-05 9:15 ` Another look? Harald Nordgren
@ 2026-01-05 9:46 ` Harald Nordgren
2026-01-05 12:28 ` Junio C Hamano
1 sibling, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-05 9:46 UTC (permalink / raw)
To: haraldnordgren; +Cc: git, gitgitgadget, gitster
I'm also adding better test coverage for '--no-ahead-behind', it's not good
that that feature can silently break.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: Another look?
2026-01-05 9:15 ` Another look? Harald Nordgren
2026-01-05 9:46 ` Harald Nordgren
@ 2026-01-05 12:28 ` Junio C Hamano
2026-01-05 13:16 ` Harald Nordgren
1 sibling, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-05 12:28 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
>> > - } else if (!sti) {
>> > - strbuf_addf(sb,
>> > - _("Your branch is up to date with '%s'.\n"),
>> > - base);
>> > - } else if (abf == AHEAD_BEHIND_QUICK) {
>> > + if (abf == AHEAD_BEHIND_QUICK) {
>> > strbuf_addf(sb,
>> > _("Your branch and '%s' refer to different commits.\n"),
>> > - base);
>> > + branch_name);
>> > if (advice_enabled(ADVICE_STATUS_HINTS))
>> > strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
>> > "git status --ahead-behind");
>> > + } else if (!ours && !theirs) {
>> > + strbuf_addf(sb,
>> > + _("Your branch is up to date with '%s'.\n"),
>> > + branch_name);
>>
>> We used to check if there is nothing to report (i.e., !sti is a
>> signal from stat_tracking_info() that there are no differences
>> between the branches) and reported that first, so when abf was
>> checked, we knew that there are some differences. Now, your patch
>> reverses the order so whether there is or is not a change, abf
>> codepath will always report "you have differences!".
>
> Agreed! This change was done to get rid of sti as a parameter. Maybe I
> misunderstood your previous comment around the sti.
>
> I will bring back sti as a parameter to ’format_branch_comparison’ now,
> I don’t see any other way to solve this.
Please don't. Unless my assumption, which is that in the old code
"!sti" and "!ours && !theirs" is equivalent, is wrong, all you need
to do around that part is to first check "if (!ours && !theirs)" and
say "your branch is up to date with...", and then have the check
"else if (abf == ABQ)" next. That way, when we check abf we know
the branches are different.
^ permalink raw reply [flat|nested] 147+ messages in thread* Another look?
2026-01-05 12:28 ` Junio C Hamano
@ 2026-01-05 13:16 ` Harald Nordgren
2026-01-05 22:13 ` Junio C Hamano
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-05 13:16 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> Please don't. Unless my assumption, which is that in the old code
> "!sti" and "!ours && !theirs" is equivalent, is wrong, all you need
> to do around that part is to first check "if (!ours && !theirs)" and
> say "your branch is up to date with...", and then have the check
> "else if (abf == ABQ)" next. That way, when we check abf we know
> the branches are different.
It seems to be an incorrect assumption. This code change breaks several
tests including old ones:
```
diff --git a/remote.c b/remote.c
index 1f87b85b22..8db4fcd7b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2303,7 +2303,7 @@ static void format_branch_comparison(struct strbuf *sb,
enum branch_mode_flags advice_flags,
int show_divergence_advice)
{
- if (!sti) {
+ if (!ours && !theirs) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
branch_name);
```
Harald
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: Another look?
2026-01-05 13:16 ` Harald Nordgren
@ 2026-01-05 22:13 ` Junio C Hamano
2026-01-06 0:08 ` ABQ Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-05 22:13 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
>> Please don't. Unless my assumption, which is that in the old code
>> "!sti" and "!ours && !theirs" is equivalent, is wrong, all you need
>> to do around that part is to first check "if (!ours && !theirs)" and
>> say "your branch is up to date with...", and then have the check
>> "else if (abf == ABQ)" next. That way, when we check abf we know
>> the branches are different.
>
> It seems to be an incorrect assumption. This code change breaks several
> tests including old ones:
>
> ```
> diff --git a/remote.c b/remote.c
> index 1f87b85b22..8db4fcd7b5 100644
> --- a/remote.c
> +++ b/remote.c
> @@ -2303,7 +2303,7 @@ static void format_branch_comparison(struct strbuf *sb,
> enum branch_mode_flags advice_flags,
> int show_divergence_advice)
> {
> - if (!sti) {
> + if (!ours && !theirs) {
> strbuf_addf(sb,
> _("Your branch is up to date with '%s'.\n"),
> branch_name);
> ```
>
>
> Harald
That is unexpected.
Looking at what stat_branch_pair() does, before returning 0, the
function always clears *num_theirs and *num_ours, so there is
something else going on.
If your caller is *not* initializing ours and theirs, and if it is
not detecting an error return from stat_tracking_info, and the test
code is trying to see when stat_branch_info() signals failure by
returning -1, then I can understand why the above change makes a
difference (i.e., your code above with or without sti -> ours/theirs
change is broken), but then, that should be handled at the caller of
stat_tracking_info() by noticing its return value being negative, I
would have to say.
^ permalink raw reply [flat|nested] 147+ messages in thread* ABQ
2026-01-05 22:13 ` Junio C Hamano
@ 2026-01-06 0:08 ` Harald Nordgren
2026-01-08 10:19 ` ABQ Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-06 0:08 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
Note that this change also makes tests fail on the 'master' branch, so not
an issue introdued by my code:
```
diff --git a/remote.c b/remote.c
index 59b3715120..f84f2747b2 100644
--- a/remote.c
+++ b/remote.c
@@ -2265,7 +2265,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+ } else if (!ours && !theirs) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
base);
```
If you have the time, it might be interesting to run
'./t6040-tracking-info.sh' and to see what happens for the different cases.
It's failing for the ABQ cases, maybe because of this:
```
if (abf == AHEAD_BEHIND_QUICK)
return 1;
```
I would argue this is getting outside of the scope of push branch
comparison, so maybe better to do this as a follow-up? What do you say?
Harald
^ permalink raw reply related [flat|nested] 147+ messages in thread
* [PATCH v16 2/2] status: show comparison with push remote tracking branch
2026-01-04 23:21 ` [PATCH v16 0/2] " Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-04 23:21 ` Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-04 23:21 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 94 +++++++++++++++++-
t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
2 files changed, 300 insertions(+), 4 deletions(-)
diff --git a/remote.c b/remote.c
index b6a9e14376..74a394c2a6 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),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,10 +2242,64 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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 ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ enum branch_mode_flags advice_flags,
int show_divergence_advice)
{
if (abf == AHEAD_BEHIND_QUICK) {
@@ -2260,7 +2319,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))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2271,7 +2331,8 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if ((advice_flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2284,7 +2345,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 &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2302,6 +2364,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;
+ 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) {
@@ -2313,6 +2380,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)) {
+ 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;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2321,10 +2398,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, ours, theirs, base, abf, show_divergence_advice);
+ format_branch_comparison(sb, 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_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..68f298bf3a 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,214 @@ 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 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 '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 '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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v17 0/2] status: show comparison with push remote tracking branch
2026-01-04 23:21 ` [PATCH v16 0/2] " Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-04 23:21 ` [PATCH v16 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-05 10:17 ` Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-05 10:17 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 169 ++++++++++++++++++++-----
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 397 insertions(+), 34 deletions(-)
base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v17
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v17
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v16:
1: cf4e9779c5 ! 1: b62a9feb4d refactor format_branch_comparison in preparation
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-+static void format_branch_comparison(struct strbuf *sb,
-+ int ours, int theirs,
-+ const char *branch_name,
-+ enum ahead_behind_flags abf,
-+ int show_divergence_advice)
- {
+-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
-- strbuf_addf(sb,
-- _("Your branch is up to date with '%s'.\n"),
++static void format_branch_comparison(struct strbuf *sb,
++ int sti,
++ int ours, int theirs,
++ const char *branch_name,
++ enum ahead_behind_flags abf,
++ int show_divergence_advice)
++{
++ if (!sti) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
- base);
-- } else if (abf == AHEAD_BEHIND_QUICK) {
-+ if (abf == AHEAD_BEHIND_QUICK) {
++ branch_name);
+ } else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
-+ } else if (!ours && !theirs) {
-+ strbuf_addf(sb,
-+ _("Your branch is up to date with '%s'.\n"),
-+ branch_name);
- } else if (!theirs) {
- strbuf_addf(sb,
+@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
@@ remote.c: 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, ours, theirs, base, abf, show_divergence_advice);
++ format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
2: 06cb483f61 ! 2: 1348542edc status: show comparison with push remote tracking branch
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+}
+
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 (abf == AHEAD_BEHIND_QUICK) {
+ if (!sti) {
+@@ remote.c: 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) &&
++ advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
+ "git status --ahead-behind");
+ } else if (!theirs) {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
"Your branch is ahead of '%s' by %d commits.\n",
ours),
@@ remote.c: 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, ours, theirs, base, abf, show_divergence_advice);
-+ format_branch_comparison(sb, ours, theirs, base, abf,
+- 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_ours, push_theirs, push, abf,
++ format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf,
+ push_branch_modes, 0);
}
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ 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 &&
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ 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 &&
@@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
+ 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 &&
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-05 10:17 ` [PATCH v17 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-05 10:17 ` Harald Nordgren via GitGitGadget
2026-01-09 14:56 ` Phillip Wood
2026-01-05 10:17 ` [PATCH v17 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-05 10:17 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 48 insertions(+), 34 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..7163a8ec28 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ int sti,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ if (!sti) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-05 10:17 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-09 14:56 ` Phillip Wood
2026-01-09 15:23 ` Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Phillip Wood @ 2026-01-09 14:56 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git; +Cc: Harald Nordgren, Junio C Hamano
Hi Harald
On 05/01/2026 10:17, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> +static void format_branch_comparison(struct strbuf *sb,
> + int sti,
I wondered why we needed to pass sti as well as ours and theirs but it
is because when we're using AHEAD_BEHIND_QUICK our and theirs are always
zero and so we need to check sti to see if the branch is up to date.
Perhaps we could make this a boolean called 'up_to_date' ?
> + int ours, int theirs,
> + const char *branch_name,
> + enum ahead_behind_flags abf,
> + int show_divergence_advice)
This could be 'bool' not 'int'
Everything else looks fine - it is a faithful conversion from the
original and it makes sense to check if the upstream is gone in the caller.
Thanks
Phillip
> +{
> + if (!sti) {
> strbuf_addf(sb,
> _("Your branch is up to date with '%s'.\n"),
> - base);
> + branch_name);
> } else if (abf == AHEAD_BEHIND_QUICK) {
> strbuf_addf(sb,
> _("Your branch and '%s' refer to different commits.\n"),
> - base);
> + branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> "git status --ahead-behind");
> @@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> Q_("Your branch is ahead of '%s' by %d commit.\n",
> "Your branch is ahead of '%s' by %d commits.\n",
> ours),
> - base, ours);
> + branch_name, ours);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addstr(sb,
> _(" (use \"git push\" to publish your local commits)\n"));
> @@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> "Your branch is behind '%s' by %d commits, "
> "and can be fast-forwarded.\n",
> theirs),
> - base, theirs);
> + branch_name, theirs);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addstr(sb,
> _(" (use \"git pull\" to update your local branch)\n"));
> @@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> "and have %d and %d different commits each, "
> "respectively.\n",
> ours + theirs),
> - base, ours, theirs);
> + branch_name, ours, theirs);
> if (show_divergence_advice &&
> advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addstr(sb,
> _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
> }
> +}
> +
> +/*
> + * Return true when there is anything to report, otherwise false.
> + */
> +int format_tracking_info(struct branch *branch, struct strbuf *sb,
> + enum ahead_behind_flags abf,
> + int show_divergence_advice)
> +{
> + int ours, theirs, sti;
> + const char *full_base;
> + char *base;
> + int upstream_is_gone = 0;
> +
> + sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
> + if (sti < 0) {
> + if (!full_base)
> + return 0;
> + upstream_is_gone = 1;
> + }
> +
> + base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
> + full_base, 0);
> +
> + if (upstream_is_gone) {
> + strbuf_addf(sb,
> + _("Your branch is based on '%s', but the upstream is gone.\n"),
> + base);
> + if (advice_enabled(ADVICE_STATUS_HINTS))
> + strbuf_addstr(sb,
> + _(" (use \"git branch --unset-upstream\" to fixup)\n"));
> + } else {
> + format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
> + }
> +
> free(base);
> return 1;
> }
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-09 14:56 ` Phillip Wood
@ 2026-01-09 15:23 ` Harald Nordgren
0 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-09 15:23 UTC (permalink / raw)
To: phillip.wood123; +Cc: git, gitgitgadget, gitster, haraldnordgren
> I wondered why we needed to pass sti as well as ours and theirs but it
> is because when we're using AHEAD_BEHIND_QUICK our and theirs are always
> zero and so we need to check sti to see if the branch is up to date.
> Perhaps we could make this a boolean called 'up_to_date' ?
Yes, this is the reason. And I tried a lot of things to get around it. But
yes, let's pass this boolean instead.
> This could be 'bool' not 'int'
>
> Everything else looks fine - it is a faithful conversion from the
> original and it makes sense to check if the upstream is gone in the caller.
Done, will be in the next patch.
I didn't update the signature of 'format_tracking_info' to have a bool
there too, because it makes the surface area bigger of also updating the .h
file there.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v17 2/2] status: show comparison with push remote tracking branch
2026-01-05 10:17 ` [PATCH v17 0/2] " Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-05 10:17 ` Harald Nordgren via GitGitGadget
2026-01-09 14:56 ` Phillip Wood
2026-01-09 16:41 ` [PATCH v18 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-05 10:17 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 97 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 354 insertions(+), 5 deletions(-)
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),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,11 +2242,65 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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) &&
+ 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))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2333,8 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if ((advice_flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -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 &&
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;
+ 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)) {
+ 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;
+ }
+ }
+
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v17 2/2] status: show comparison with push remote tracking branch
2026-01-05 10:17 ` [PATCH v17 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-09 14:56 ` Phillip Wood
2026-01-09 16:00 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Phillip Wood @ 2026-01-09 14:56 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git; +Cc: Harald Nordgren
Hi Harald
On 05/01/2026 10:17, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> "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
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-09 14:56 ` Phillip Wood
@ 2026-01-09 16:00 ` Harald Nordgren
2026-01-09 16:22 ` Ben Knoble
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-09 16:00 UTC (permalink / raw)
To: phillip.wood123; +Cc: git, gitgitgadget, haraldnordgren, phillip.wood
> Using an enum for a set of flags is a bit confusing.
The point of the flag and the bitmasking is to selectively turn off the push
and pull advice advice from the relevant branch when the push branch
comparison is active.
In an earlier implementation the advice logic was moved to the caller
instead 'format_branch_comparison', but it's more faithful to the original
to have the advice logic inside 'format_branch_comparison'. Maybe I
misunderstood your comments around this?
Would happily take a suggestion on a nicer way to handle it.
> 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.
I'm not getting that to work without tests breaking.
> I think it would be simpler to just return the full refname and let the
> caller shorten it.
We could to that but what's the benefit? Shouldn't a helper function reduce
the work for the caller, not the other way around? 🤗
> Why are we checking for BRANCH_MODE_PUSH here? Don't we want to show
> this advice regardless of the mode?
Yeah, that's a good point. However, this will often lead to the advice
being shown twice, see this test:
```
status --no-ahead-behind shows diverged from origin/main and ahead of feature branch
```
> 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)
Done, will be in the next batch!
> 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?
This will already have been shown the the pull branch (the upstream
branch).
And in the cases when it's NOT shown for the pull branch but only the push
branch has diverged, then pulling won't solve the problem anyway. (Unless
you would try to pull from your push branch.)
> Here we set an enum to a value that is not a member of the enum.
I use bitmasking to enable both modes by default. But see my comments above
maybe there is a nicer way to handle all of this.
> 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.
Possibly this can be done in a nicer way too. But at least I try to
encapsulate the complexity to only here, to allow the rest of the code to
be more straightforward. A good trade-off I think.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-09 16:00 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
@ 2026-01-09 16:22 ` Ben Knoble
2026-01-09 16:32 ` Patrick Steinhardt
0 siblings, 1 reply; 147+ messages in thread
From: Ben Knoble @ 2026-01-09 16:22 UTC (permalink / raw)
To: Harald Nordgren; +Cc: phillip.wood123, git, gitgitgadget, phillip.wood
> Le 9 janv. 2026 à 11:07, Harald Nordgren <haraldnordgren@gmail.com> a écrit :
>
>
>>
>> Using an enum for a set of flags is a bit confusing.
>
> The point of the flag and the bitmasking is to selectively turn off the push
> and pull advice advice from the relevant branch when the push branch
> comparison is active.
>
> In an earlier implementation the advice logic was moved to the caller
> instead 'format_branch_comparison', but it's more faithful to the original
> to have the advice logic inside 'format_branch_comparison'. Maybe I
> misunderstood your comments around this?
>
> Would happily take a suggestion on a nicer way to handle it.
I think other uses in Git declare a bunch of integer constants for the bitfields, not an enum. Why? Because or-ing flags together creates a value not in the enum…
>> Here we set an enum to a value that is not a member of the enum.
>
> I use bitmasking to enable both modes by default. But see my comments above
> maybe there is a nicer way to handle all of this.
…which explains this comment.
IOW: set of flags makes sense, but there’s a more usual way to set that up?
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-09 16:22 ` Ben Knoble
@ 2026-01-09 16:32 ` Patrick Steinhardt
2026-01-09 18:03 ` Harald Nordgren
0 siblings, 1 reply; 147+ messages in thread
From: Patrick Steinhardt @ 2026-01-09 16:32 UTC (permalink / raw)
To: Ben Knoble
Cc: Harald Nordgren, phillip.wood123, git, gitgitgadget, phillip.wood
On Fri, Jan 09, 2026 at 11:22:33AM -0500, Ben Knoble wrote:
>
> > Le 9 janv. 2026 à 11:07, Harald Nordgren <haraldnordgren@gmail.com> a écrit :
> >
> >
> >>
> >> Using an enum for a set of flags is a bit confusing.
> >
> > The point of the flag and the bitmasking is to selectively turn off the push
> > and pull advice advice from the relevant branch when the push branch
> > comparison is active.
> >
> > In an earlier implementation the advice logic was moved to the caller
> > instead 'format_branch_comparison', but it's more faithful to the original
> > to have the advice logic inside 'format_branch_comparison'. Maybe I
> > misunderstood your comments around this?
> >
> > Would happily take a suggestion on a nicer way to handle it.
>
> I think other uses in Git declare a bunch of integer constants for the
> bitfields, not an enum. Why? Because or-ing flags together creates a
> value not in the enum…
We nowadays typically declare the flags as enum, but when accepting the
bitfield we use `unsigned`.
Patrick
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-09 16:32 ` Patrick Steinhardt
@ 2026-01-09 18:03 ` Harald Nordgren
0 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-09 18:03 UTC (permalink / raw)
To: ps
Cc: ben.knoble, git, gitgitgadget, haraldnordgren, phillip.wood123,
phillip.wood
> > I think other uses in Git declare a bunch of integer constants for the
> > bitfields, not an enum. Why? Because or-ing flags together creates a
> > value not in the enum…
>
> We nowadays typically declare the flags as enum, but when accepting the
> bitfield we use `unsigned`.
Good points! I have updated it to remove the name branch_mode_flags now,
define the enums and pass them around as unsigned now. Will be in the next
patch.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v18 0/2] status: show comparison with push remote tracking branch
2026-01-05 10:17 ` [PATCH v17 0/2] " Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-05 10:17 ` [PATCH v17 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-09 16:41 ` Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 16:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 174 ++++++++++++++++++++------
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 401 insertions(+), 35 deletions(-)
base-commit: d529f3a197364881746f558e5652f0236131eb86
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v18
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v18
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v17:
1: b62a9feb4d ! 1: 451d7a4986 refactor format_branch_comparison in preparation
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
-+ int sti,
++ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
-+ int show_divergence_advice)
++ bool show_divergence_advice)
+{
-+ if (!sti) {
++ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
@@ remote.c: 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, show_divergence_advice);
+ }
+
free(base);
2: 1348542edc ! 2: 02476eb8e6 status: show comparison with push remote tracking branch
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
+}
+
static void format_branch_comparison(struct strbuf *sb,
- int sti,
+ bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ enum branch_mode_flags advice_flags,
- int show_divergence_advice)
+ bool show_divergence_advice)
{
- if (!sti) {
++ 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);
++ bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
++ show_divergence_advice &&
++ advice_enabled(ADVICE_STATUS_HINTS);
++
+ if (up_to_date) {
+ strbuf_addf(sb,
+ _("Your branch is up to date with '%s'.\n"),
@@ remote.c: 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) &&
-+ advice_enabled(ADVICE_STATUS_HINTS))
++ if (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
ours),
branch_name, ours);
- if (advice_enabled(ADVICE_STATUS_HINTS))
-+ if ((advice_flags & BRANCH_MODE_PUSH) &&
-+ advice_enabled(ADVICE_STATUS_HINTS))
++ if (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
-+ if ((advice_flags & BRANCH_MODE_PULL) &&
-+ advice_enabled(ADVICE_STATUS_HINTS))
++ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
-+ if ((advice_flags & BRANCH_MODE_PULL) &&
-+ show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+- advice_enabled(ADVICE_STATUS_HINTS))
++ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+ }
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
@@ remote.c: 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,
+- 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);
++ format_branch_comparison(sb, !push_sti, push_ours, push_theirs, push, abf,
++ push_branch_modes, show_divergence_advice);
}
free(base);
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v18 1/2] refactor format_branch_comparison in preparation
2026-01-09 16:41 ` [PATCH v18 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-09 16:41 ` Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 16:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 48 insertions(+), 34 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..eba013b6b4 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ bool show_divergence_advice)
+{
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v18 2/2] status: show comparison with push remote tracking branch
2026-01-09 16:41 ` [PATCH v18 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-09 16:41 ` Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 16:41 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 102 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 358 insertions(+), 6 deletions(-)
diff --git a/remote.c b/remote.c
index eba013b6b4..4c3f18c3c1 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),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ enum branch_mode_flags advice_flags,
bool show_divergence_advice)
{
+ 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);
+ bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ 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 (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2261,7 +2328,7 @@ 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 (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
@@ -2303,6 +2369,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;
+ 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 +2385,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)) {
+ 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;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2403,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, show_divergence_advice);
}
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v19 0/2] status: show comparison with push remote tracking branch
2026-01-09 16:41 ` [PATCH v18 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-09 16:41 ` [PATCH v18 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-09 18:40 ` Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 18:40 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com cc: Patrick Steinhardt ps@pks.im
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 174 ++++++++++++++++++++------
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 401 insertions(+), 35 deletions(-)
base-commit: d529f3a197364881746f558e5652f0236131eb86
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v19
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v19
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v18:
1: 451d7a4986 = 1: 451d7a4986 refactor format_branch_comparison in preparation
2: 02476eb8e6 ! 2: dc8ab23158 status: show comparison with push remote tracking branch
@@ remote.c
enum map_direction { FROM_SRC, FROM_DST };
-+enum branch_mode_flags {
++enum {
+ BRANCH_MODE_PULL = (1 << 0),
+ BRANCH_MODE_PUSH = (1 << 1),
+};
@@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
-+ enum branch_mode_flags advice_flags,
++ unsigned flags,
bool show_divergence_advice)
{
-+ bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
++ bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
-+ bool want_pull_advice = (advice_flags & BRANCH_MODE_PULL) &&
++ bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
-+ bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
++ bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
@@ remote.c: 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;
++ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
-+ enum branch_mode_flags push_branch_modes = 0;
++ unsigned push_branch_modes = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v19 1/2] refactor format_branch_comparison in preparation
2026-01-09 18:40 ` [PATCH v19 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-09 18:40 ` Harald Nordgren via GitGitGadget
2026-01-10 2:02 ` Junio C Hamano
2026-01-09 18:40 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 18:40 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 48 insertions(+), 34 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..eba013b6b4 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ bool show_divergence_advice)
+{
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v19 1/2] refactor format_branch_comparison in preparation
2026-01-09 18:40 ` [PATCH v19 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-10 2:02 ` Junio C Hamano
0 siblings, 0 replies; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 2:02 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> +static void format_branch_comparison(struct strbuf *sb,
> + bool up_to_date,
> + int ours, int theirs,
> + const char *branch_name,
> + enum ahead_behind_flags abf,
> + bool show_divergence_advice)
> +{
> + if (up_to_date) {
> strbuf_addf(sb,
> _("Your branch is up to date with '%s'.\n"),
> - base);
> + branch_name);
> } else if (abf == AHEAD_BEHIND_QUICK) {
> strbuf_addf(sb,
> _("Your branch and '%s' refer to different commits.\n"),
> - base);
> + branch_name);
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
> "git status --ahead-behind");
OK. We _could_, just like we moved the "upstream_gone" case to the
caller, move the "up to date" case to the caller as well, but this
reads well now. And we do not need the (!ours && !theirs) case in
this if/else if/... cascade. Very good.
> +/*
> + * Return true when there is anything to report, otherwise false.
> + */
> +int format_tracking_info(struct branch *branch, struct strbuf *sb,
> + enum ahead_behind_flags abf,
> + int show_divergence_advice)
> +{
> + int ours, theirs, sti;
> + const char *full_base;
> + char *base;
> + int upstream_is_gone = 0;
> +
> + sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
> + if (sti < 0) {
> + if (!full_base)
> + return 0;
> + upstream_is_gone = 1;
> + }
> +
> + base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
> + full_base, 0);
> +
> + if (upstream_is_gone) {
> + strbuf_addf(sb,
> + _("Your branch is based on '%s', but the upstream is gone.\n"),
> + base);
> + if (advice_enabled(ADVICE_STATUS_HINTS))
> + strbuf_addstr(sb,
> + _(" (use \"git branch --unset-upstream\" to fixup)\n"));
> + } else {
> + format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
> + }
> +
> free(base);
> return 1;
> }
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v19 2/2] status: show comparison with push remote tracking branch
2026-01-09 18:40 ` [PATCH v19 0/2] " Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-09 18:40 ` Harald Nordgren via GitGitGadget
2026-01-10 2:13 ` Junio C Hamano
2026-01-10 2:21 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Junio C Hamano
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2 siblings, 2 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-09 18:40 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 102 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 358 insertions(+), 6 deletions(-)
diff --git a/remote.c b/remote.c
index eba013b6b4..b8d2d1360a 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,11 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ BRANCH_MODE_PULL = (1 << 0),
+ BRANCH_MODE_PUSH = (1 << 1),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ unsigned flags,
bool show_divergence_advice)
{
+ bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ 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 (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2261,7 +2328,7 @@ 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 (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
@@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
+ unsigned push_branch_modes = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2314,6 +2385,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)) {
+ 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;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2403,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, show_divergence_advice);
}
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v19 2/2] status: show comparison with push remote tracking branch
2026-01-09 18:40 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-10 2:13 ` Junio C Hamano
2026-01-10 11:14 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
2026-01-10 2:21 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Junio C Hamano
1 sibling, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 2:13 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> @@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
> "respectively.\n",
> ours + theirs),
> branch_name, ours, theirs);
> - if (show_divergence_advice &&
> - advice_enabled(ADVICE_STATUS_HINTS))
> + if (want_divergence_advice)
> strbuf_addstr(sb,
> _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
> }
This is not a new issue introduced by this series, but it is curious
there is "ours + theirs" there #leftoverbits.
It is part of ngetext() aka Q_() call, used this way:
} 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);
But in this if/else if/... cascade, we have ruled out cases where
either/both of ours and theirs is 0 already, so ours + theirs has to
be at least two (because each has to be at least one). Q_() based
on a value that is always plural would always use the latter form
(i.e., "commits").
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 2:13 ` Junio C Hamano
@ 2026-01-10 11:14 ` Harald Nordgren
2026-01-10 17:34 ` Junio C Hamano
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-10 11:14 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> This is not a new issue introduced by this series, but it is curious
> there is "ours + theirs" there #leftoverbits.
>
> It is part of ngetext() aka Q_() call, used this way:
Agreed, I thought about this one too. Will update it!
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 11:14 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
@ 2026-01-10 17:34 ` Junio C Hamano
0 siblings, 0 replies; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 17:34 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
>> This is not a new issue introduced by this series, but it is curious
>> there is "ours + theirs" there #leftoverbits.
>>
>> It is part of ngetext() aka Q_() call, used this way:
>
> Agreed, I thought about this one too. Will update it!
Please do so as a separate patch, not mix it into a patch that does
something else.
I do not think we mind even if it is left outside the current topic
we are discussing, if it distracts the main theme of the topic.
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v19 2/2] status: show comparison with push remote tracking branch
2026-01-09 18:40 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-10 2:13 ` Junio C Hamano
@ 2026-01-10 2:21 ` Junio C Hamano
2026-01-10 11:06 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
1 sibling, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 2:21 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> @@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
> const char *full_base;
> char *base;
> int upstream_is_gone = 0;
> + unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
> + int push_ours, push_theirs, push_sti;
> + char *full_push = NULL;
> + char *push = NULL;
> + unsigned push_branch_modes = 0;
>
> sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
> if (sti < 0) {
> @@ -2314,6 +2385,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)) {
> + push_sti = stat_branch_pair(branch->refname, full_push,
> + &push_ours, &push_theirs, abf);
Why is this variable called "push_sti"? Calling the return value of
stat_tracking_info() "sti" was klumsy but understandable. It would
have been much easier to follow the code if the variable were named
after what it _means_ in this particular caller's code flow, like
"cmp_fetch" (comparison on the fetching side, by convention negative
signals an error, and zero signals 'the same'). Perhaps rename "sti"
and "push_sti" at the same time to make them more symmetric?
Other than this minor nit, this step looks nicely done (and the
previous one is also good).
Thanks.
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 2:21 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Junio C Hamano
@ 2026-01-10 11:06 ` Harald Nordgren
2026-01-10 14:44 ` Harald Nordgren
2026-01-10 17:31 ` Junio C Hamano
0 siblings, 2 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-10 11:06 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
It's following my naming scheme where each push version of a base variable
tacks on _push at the end. Since the push logic introduces a second version
of most variables, it's hard to make it fully logical when the base versions
still retain their "unqualified" names.
I can rename the sti's to 'cmp_fetch' and 'cmp_fetch_push', but does it
help?
Probably best would be to create a struct for the common variables between
base and push cases. But that's a large refactoring, so maybe better done
as a separate follow-up to all of this instead? Here's what is looks like:
```
diff --git a/remote.c b/remote.c
index eba013b6b4..581a62c266 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,41 +2237,47 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+struct format_branch {
+ int ours, theirs;
+ int cmp_fetch;
+ bool up_to_date;
+ const char *full;
+ char *name;
+};
+
static void format_branch_comparison(struct strbuf *sb,
- bool up_to_date,
- int ours, int theirs,
- const char *branch_name,
+ const struct format_branch *branch,
enum ahead_behind_flags abf,
bool show_divergence_advice)
{
- if (up_to_date) {
+ if (branch->up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- branch_name);
+ branch->name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- branch_name);
+ branch->name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
- } else if (!theirs) {
+ } else if (!branch->theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
- ours),
- branch_name, ours);
+ branch->ours),
+ branch->name, branch->ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
- } else if (!ours) {
+ } else if (!branch->ours) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
- theirs),
- branch_name, theirs);
+ branch->theirs),
+ branch->name, branch->theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2283,8 +2289,8 @@ static void format_branch_comparison(struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- ours + theirs),
- branch_name, ours, theirs);
+ branch->ours + branch->theirs),
+ branch->name, branch->ours, branch->theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
@@ -2299,33 +2305,32 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
enum ahead_behind_flags abf,
int show_divergence_advice)
{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
int upstream_is_gone = 0;
+ struct format_branch base;
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
+ base.cmp_fetch = stat_tracking_info(branch, &base.ours, &base.theirs, &base.full, 0, abf);
+ base.up_to_date = !base.cmp_fetch;
+ if (base.cmp_fetch < 0) {
+ if (!base.full)
return 0;
upstream_is_gone = 1;
}
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
+ base.name = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ base.full, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
+ base.name);
if (advice_enabled(ADVICE_STATUS_HINTS))
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, &base, abf, show_divergence_advice);
}
- free(base);
+ free(base.name);
return 1;
}
```
Harald
^ permalink raw reply related [flat|nested] 147+ messages in thread* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 11:06 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
@ 2026-01-10 14:44 ` Harald Nordgren
2026-01-10 17:31 ` Junio C Hamano
1 sibling, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-10 14:44 UTC (permalink / raw)
To: haraldnordgren; +Cc: git, gitgitgadget, gitster
> Why is this variable called "push_sti"? Calling the return value of
> stat_tracking_info() "sti" was klumsy but understandable. It would
> have been much easier to follow the code if the variable were named
> after what it _means_ in this particular caller's code flow, like
> "cmp_fetch" (comparison on the fetching side, by convention negative
> signals an error, and zero signals 'the same'). Perhaps rename "sti"
> and "push_sti" at the same time to make them more symmetric?
I renamed to 'push_cmp_fetch' and 'push_cmp_fetch' now. I think on my first
reading I misunderstood your comment.
> Other than this minor nit, this step looks nicely done (and the
> previous one is also good).
Thanks a lot!
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 11:06 ` [PATCH v17 1/2] refactor format_branch_comparison in preparation Harald Nordgren
2026-01-10 14:44 ` Harald Nordgren
@ 2026-01-10 17:31 ` Junio C Hamano
2026-01-10 20:04 ` Harald Nordgren
1 sibling, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 17:31 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
> I can rename the sti's to 'cmp_fetch' and 'cmp_fetch_push', but does it
> help?
Not at all. Unless the contrast were "something_fetch" vs
"something_push", that is. And that something being cryptic "sti"
(recall my comment on it, being the name of the function that
returns the value, which is less understandable than using words
that signals what the variable _means_), would not make much sense
for the "push" direction, as "sti" is not even an abbreviation for
the function that gives the value. "cmp" (or "compare" for that
matter) still has the ambiguity "compare with what and what for?",
but at least it would be better than "sti".
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 17:31 ` Junio C Hamano
@ 2026-01-10 20:04 ` Harald Nordgren
2026-01-11 3:39 ` Junio C Hamano
0 siblings, 1 reply; 147+ messages in thread
From: Harald Nordgren @ 2026-01-10 20:04 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> Not at all. Unless the contrast were "something_fetch" vs
> "something_push", that is. And that something being cryptic "sti"
> (recall my comment on it, being the name of the function that
> returns the value, which is less understandable than using words
> that signals what the variable _means_), would not make much sense
> for the "push" direction, as "sti" is not even an abbreviation for
> the function that gives the value. "cmp" (or "compare" for that
> matter) still has the ambiguity "compare with what and what for?",
> but at least it would be better than "sti".
Maybe simplest would be 'upstream_diff' and 'push_diff'? But this begs the
question of why not all the old 'ours', 'theirs', etc variables are missing
an 'upstream_' prefix.
I feel like this can turn into endless refactoring, latest version now has
'cmp_fetch' and 'push_cmp_fetch'.
I have on my TODO list to fix the pluralization of the "diverged" text
after this patch series has been merged. I'm also working on refactoring
this by introducing a struct to pass data to 'format_branch_comparison'.
This has the very nice benefit on natural namespacing of variables when
they become fields in struct variables like 'upstream_branch' and
'push_branch'.
In the meantime I think I will leave this patch series for now 🤗
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread
* Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation
2026-01-10 20:04 ` Harald Nordgren
@ 2026-01-11 3:39 ` Junio C Hamano
0 siblings, 0 replies; 147+ messages in thread
From: Junio C Hamano @ 2026-01-11 3:39 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, gitgitgadget
Harald Nordgren <haraldnordgren@gmail.com> writes:
> In the meantime I think I will leave this patch series for now 🤗
I think we are reaching the point of diminishing returns after
polishing the series enough at the 22nd iteration.
Let's wait for a few days to see if what others find in the series
and then mark the topic for 'next'. Hopefully any minor nits can be
addressed later as a follow-up, just like the Q_(ours+theirs) thing.
Thanks.
^ permalink raw reply [flat|nested] 147+ messages in thread
* [PATCH v20 0/2] status: show comparison with push remote tracking branch
2026-01-09 18:40 ` [PATCH v19 0/2] " Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-09 18:40 ` [PATCH v19 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-10 13:30 ` Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (3 more replies)
2 siblings, 4 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 13:30 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com cc: Patrick Steinhardt ps@pks.im
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 183 ++++++++++++++++++++-------
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 403 insertions(+), 42 deletions(-)
base-commit: d529f3a197364881746f558e5652f0236131eb86
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v20
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v20
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v19:
1: 451d7a4986 ! 1: bb3e00863b refactor format_branch_comparison in preparation
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- "and have %d and %d different commits each, "
- "respectively.\n",
- ours + theirs),
+ } 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),
- base, ours, theirs);
++ "Your branch and '%s' have diverged,\n"
++ "and have %d and %d different commits each, respectively.\n",
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
2: dc8ab23158 ! 2: 050197eac3 status: show comparison with push remote tracking branch
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
- "respectively.\n",
- ours + theirs),
+ "Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commits each, respectively.\n",
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v20 1/2] refactor format_branch_comparison in preparation
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-10 13:30 ` Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
` (2 subsequent siblings)
3 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 13:30 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 91 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 50 insertions(+), 41 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..b053d4e443 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ bool show_divergence_advice)
+{
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,25 +2271,55 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(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),
- base, ours, theirs);
+ "Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commits each, respectively.\n",
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, sti;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (sti < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v20 2/2] status: show comparison with push remote tracking branch
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-10 13:30 ` Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 0/2] " Harald Nordgren via GitGitGadget
2026-01-10 17:41 ` [PATCH v20 0/2] " Junio C Hamano
3 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 13:30 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 102 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 358 insertions(+), 6 deletions(-)
diff --git a/remote.c b/remote.c
index b053d4e443..dd8f94fa23 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,11 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ BRANCH_MODE_PULL = (1 << 0),
+ BRANCH_MODE_PUSH = (1 << 1),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ unsigned flags,
bool show_divergence_advice)
{
+ bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ 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 (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2261,7 +2328,7 @@ 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 (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2280,8 +2347,7 @@ static void format_branch_comparison(struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, respectively.\n",
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
@@ -2298,6 +2364,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_sti;
+ char *full_push = NULL;
+ char *push = NULL;
+ unsigned push_branch_modes = 0;
sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (sti < 0) {
@@ -2309,6 +2380,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)) {
+ 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;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2317,10 +2398,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, show_divergence_advice);
}
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v21 0/2] status: show comparison with push remote tracking branch
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-10 13:30 ` [PATCH v20 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-10 15:24 ` Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
` (2 more replies)
2026-01-10 17:41 ` [PATCH v20 0/2] " Junio C Hamano
3 siblings, 3 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 15:24 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com cc: Patrick Steinhardt ps@pks.im
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 183 ++++++++++++++++++++-------
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 403 insertions(+), 42 deletions(-)
base-commit: d529f3a197364881746f558e5652f0236131eb86
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v21
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v21
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v20:
1: bb3e00863b ! 1: ce1f1eebb5 refactor format_branch_comparison in preparation
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
-+ int ours, theirs, sti;
++ int ours, theirs, cmp_fetch;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
-+ sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-+ if (sti < 0) {
++ cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
++ if (cmp_fetch < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
@@ remote.c: 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, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
2: 050197eac3 ! 2: 51d8486fe0 status: show comparison with push remote tracking branch
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
-+ int push_ours, push_theirs, push_sti;
++ int push_ours, push_theirs, push_cmp_fetch;
+ char *full_push = NULL;
+ char *push = NULL;
+ unsigned push_branch_modes = 0;
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
+ cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (cmp_fetch < 0) {
@@ remote.c: 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)) {
-+ push_sti = stat_branch_pair(branch->refname, full_push,
++ push_cmp_fetch = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
-+ if (push_sti >= 0) {
++ if (push_cmp_fetch >= 0) {
+ base_branch_modes = BRANCH_MODE_PULL;
+ push_branch_modes = BRANCH_MODE_PUSH;
+ }
@@ remote.c: 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,
+- format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
++ format_branch_comparison(sb, !cmp_fetch, 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,
++ format_branch_comparison(sb, !push_cmp_fetch, push_ours, push_theirs, push, abf,
+ push_branch_modes, show_divergence_advice);
}
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v21 1/2] refactor format_branch_comparison in preparation
2026-01-10 15:24 ` [PATCH v21 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-10 15:24 ` Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 15:24 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 91 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 50 insertions(+), 41 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..af078817a4 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ bool show_divergence_advice)
+{
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,25 +2271,55 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(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),
- base, ours, theirs);
+ "Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commits each, respectively.\n",
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, cmp_fetch;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (cmp_fetch < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v21 2/2] status: show comparison with push remote tracking branch
2026-01-10 15:24 ` [PATCH v21 0/2] " Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-10 15:24 ` Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 0/2] " Harald Nordgren via GitGitGadget
2 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 15:24 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 102 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 358 insertions(+), 6 deletions(-)
diff --git a/remote.c b/remote.c
index af078817a4..40e4ebb3b7 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,11 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ BRANCH_MODE_PULL = (1 << 0),
+ BRANCH_MODE_PUSH = (1 << 1),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ unsigned flags,
bool show_divergence_advice)
{
+ bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ 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 (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2261,7 +2328,7 @@ 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 (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2280,8 +2347,7 @@ static void format_branch_comparison(struct strbuf *sb,
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, respectively.\n",
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
@@ -2298,6 +2364,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_cmp_fetch;
+ char *full_push = NULL;
+ char *push = NULL;
+ unsigned push_branch_modes = 0;
cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (cmp_fetch < 0) {
@@ -2309,6 +2380,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)) {
+ push_cmp_fetch = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_cmp_fetch >= 0) {
+ base_branch_modes = BRANCH_MODE_PULL;
+ push_branch_modes = BRANCH_MODE_PUSH;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2317,10 +2398,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, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ format_branch_comparison(sb, !cmp_fetch, 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_cmp_fetch, push_ours, push_theirs, push, abf,
+ push_branch_modes, show_divergence_advice);
}
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v22 0/2] status: show comparison with push remote tracking branch
2026-01-10 15:24 ` [PATCH v21 0/2] " Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-10 15:24 ` [PATCH v21 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
@ 2026-01-10 19:56 ` Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
2 siblings, 2 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 19:56 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren
cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
cc: Nico Williams nico@cryptonector.com cc: Patrick Steinhardt ps@pks.im
Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch
remote.c | 174 ++++++++++++++++++++------
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 401 insertions(+), 35 deletions(-)
base-commit: d529f3a197364881746f558e5652f0236131eb86
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v22
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v22
Pull-Request: https://github.com/git/git/pull/2138
Range-diff vs v21:
1: ce1f1eebb5 ! 1: 4aa4f1abc8 refactor format_branch_comparison in preparation
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(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),
+@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ "and have %d and %d different commits each, "
+ "respectively.\n",
+ ours + theirs),
- base, ours, theirs);
-+ "Your branch and '%s' have diverged,\n"
-+ "and have %d and %d different commits each, respectively.\n",
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
2: 51d8486fe0 ! 2: b7e29887d9 status: show comparison with push remote tracking branch
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
- "Your branch and '%s' have diverged,\n"
- "and have %d and %d different commits each, respectively.\n",
+ "respectively.\n",
+ ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
--
gitgitgadget
^ permalink raw reply [flat|nested] 147+ messages in thread* [PATCH v22 1/2] refactor format_branch_comparison in preparation
2026-01-10 19:56 ` [PATCH v22 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-10 19:56 ` Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 2/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
1 sibling, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 19:56 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
1 file changed, 48 insertions(+), 34 deletions(-)
diff --git a/remote.c b/remote.c
index 59b3715120..d5a6486026 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
- enum ahead_behind_flags abf,
- int show_divergence_advice)
-{
- int ours, theirs, sti;
- const char *full_base;
- char *base;
- int upstream_is_gone = 0;
-
- sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
- if (sti < 0) {
- if (!full_base)
- return 0;
- upstream_is_gone = 1;
- }
-
- base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- full_base, 0);
- if (upstream_is_gone) {
- strbuf_addf(sb,
- _("Your branch is based on '%s', but the upstream is gone.\n"),
- base);
- if (advice_enabled(ADVICE_STATUS_HINTS))
- strbuf_addstr(sb,
- _(" (use \"git branch --unset-upstream\" to fixup)\n"));
- } else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+ bool up_to_date,
+ int ours, int theirs,
+ const char *branch_name,
+ enum ahead_behind_flags abf,
+ bool show_divergence_advice)
+{
+ if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
- base);
+ branch_name);
} else if (abf == AHEAD_BEHIND_QUICK) {
strbuf_addf(sb,
_("Your branch and '%s' refer to different commits.\n"),
- base);
+ branch_name);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
ours),
- base, ours);
+ branch_name, ours);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
theirs),
- base, theirs);
+ branch_name, theirs);
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
"and have %d and %d different commits each, "
"respectively.\n",
ours + theirs),
- base, ours, theirs);
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+ enum ahead_behind_flags abf,
+ int show_divergence_advice)
+{
+ int ours, theirs, cmp_fetch;
+ const char *full_base;
+ char *base;
+ int upstream_is_gone = 0;
+
+ cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+ if (cmp_fetch < 0) {
+ if (!full_base)
+ return 0;
+ upstream_is_gone = 1;
+ }
+
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
+
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_enabled(ADVICE_STATUS_HINTS))
+ strbuf_addstr(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else {
+ format_branch_comparison(sb, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ }
+
free(base);
return 1;
}
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread* [PATCH v22 2/2] status: show comparison with push remote tracking branch
2026-01-10 19:56 ` [PATCH v22 0/2] " Harald Nordgren via GitGitGadget
2026-01-10 19:56 ` [PATCH v22 1/2] refactor format_branch_comparison in preparation Harald Nordgren via GitGitGadget
@ 2026-01-10 19:56 ` Harald Nordgren via GitGitGadget
1 sibling, 0 replies; 147+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-01-10 19:56 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
From: Harald Nordgren <haraldnordgren@gmail.com>
"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 comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
remote.c | 102 ++++++++++++++-
t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
2 files changed, 358 insertions(+), 6 deletions(-)
diff --git a/remote.c b/remote.c
index d5a6486026..f9aa4c61bc 100644
--- a/remote.c
+++ b/remote.c
@@ -29,6 +29,11 @@
enum map_direction { FROM_SRC, FROM_DST };
+enum {
+ BRANCH_MODE_PULL = (1 << 0),
+ BRANCH_MODE_PUSH = (1 << 1),
+};
+
struct counted_string {
size_t len;
const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
}
+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);
+
+ 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)
+ *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,
bool up_to_date,
int ours, int theirs,
const char *branch_name,
enum ahead_behind_flags abf,
+ unsigned flags,
bool show_divergence_advice)
{
+ bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+ bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+ show_divergence_advice &&
+ advice_enabled(ADVICE_STATUS_HINTS);
+
if (up_to_date) {
strbuf_addf(sb,
_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ 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 (want_push_advice)
strbuf_addf(sb, _(" (use \"%s\" for details)\n"),
"git status --ahead-behind");
} else if (!theirs) {
@@ -2261,7 +2328,7 @@ 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 (want_push_advice)
strbuf_addstr(sb,
_(" (use \"git push\" to publish your local commits)\n"));
} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
"and can be fast-forwarded.\n",
theirs),
branch_name, theirs);
- if (advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_pull_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
"respectively.\n",
ours + theirs),
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))
+ if (want_divergence_advice)
strbuf_addstr(sb,
_(" (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
}
@@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
const char *full_base;
char *base;
int upstream_is_gone = 0;
+ unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+ int push_ours, push_theirs, push_cmp_fetch;
+ char *full_push = NULL;
+ char *push = NULL;
+ unsigned push_branch_modes = 0;
cmp_fetch = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
if (cmp_fetch < 0) {
@@ -2314,6 +2385,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)) {
+ push_cmp_fetch = stat_branch_pair(branch->refname, full_push,
+ &push_ours, &push_theirs, abf);
+ if (push_cmp_fetch >= 0) {
+ base_branch_modes = BRANCH_MODE_PULL;
+ push_branch_modes = BRANCH_MODE_PUSH;
+ }
+ }
+
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2403,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, !cmp_fetch, ours, theirs, base, abf, show_divergence_advice);
+ format_branch_comparison(sb, !cmp_fetch, 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_cmp_fetch, push_ours, push_theirs, push, abf,
+ push_branch_modes, show_divergence_advice);
}
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
--
gitgitgadget
^ permalink raw reply related [flat|nested] 147+ messages in thread
* Re: [PATCH v20 0/2] status: show comparison with push remote tracking branch
2026-01-10 13:30 ` [PATCH v20 0/2] status: show comparison with push remote tracking branch Harald Nordgren via GitGitGadget
` (2 preceding siblings ...)
2026-01-10 15:24 ` [PATCH v21 0/2] " Harald Nordgren via GitGitGadget
@ 2026-01-10 17:41 ` Junio C Hamano
2026-01-10 19:06 ` Harald Nordgren
3 siblings, 1 reply; 147+ messages in thread
From: Junio C Hamano @ 2026-01-10 17:41 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
Here is a place for you to explain what helps people to decypher the
differences since the previous iteration in Range-diff (which is not
very easy to read).
> cc: Chris Torek chris.torek@gmail.com cc: Yee Cheng Chin
> ychin.macvim@gmail.com cc: "brian m. carlson" sandals@crustytoothpaste.net
> cc: Ben Knoble ben.knoble@gmail.com cc: "Kristoffer Haugsbakk"
> kristofferhaugsbakk@fastmail.com cc: Phillip Wood phillip.wood123@gmail.com
> cc: Nico Williams nico@cryptonector.com cc: Patrick Steinhardt ps@pks.im
I am not sure what good these lines are doing (to GGG).
> Harald Nordgren (2):
> refactor format_branch_comparison in preparation
> status: show comparison with push remote tracking branch
>
> remote.c | 183 ++++++++++++++++++++-------
> t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
> 2 files changed, 403 insertions(+), 42 deletions(-)
>
>
> base-commit: d529f3a197364881746f558e5652f0236131eb86
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v20
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v20
> Pull-Request: https://github.com/git/git/pull/2138
>
> Range-diff vs v19:
>
> 1: 451d7a4986 ! 1: bb3e00863b refactor format_branch_comparison in preparation
> @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
> if (advice_enabled(ADVICE_STATUS_HINTS))
> strbuf_addstr(sb,
> _(" (use \"git pull\" to update your local branch)\n"));
> -@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
> - "and have %d and %d different commits each, "
> - "respectively.\n",
> - ours + theirs),
> + } 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),
> - base, ours, theirs);
> ++ "Your branch and '%s' have diverged,\n"
> ++ "and have %d and %d different commits each, respectively.\n",
> + branch_name, ours, theirs);
> if (show_divergence_advice &&
> advice_enabled(ADVICE_STATUS_HINTS))
Could you not mix the ours+theirs thing into the same step? Either
make it a standalone patch to clean up before or after your main 2
patches, or leave it totally outside the series and send it after
this series settles.
^ permalink raw reply [flat|nested] 147+ messages in thread* Re: [PATCH v20 0/2] status: show comparison with push remote tracking branch
2026-01-10 17:41 ` [PATCH v20 0/2] " Junio C Hamano
@ 2026-01-10 19:06 ` Harald Nordgren
0 siblings, 0 replies; 147+ messages in thread
From: Harald Nordgren @ 2026-01-10 19:06 UTC (permalink / raw)
To: gitster; +Cc: git, gitgitgadget, haraldnordgren
> Here is a place for you to explain what helps people to decypher the
> differences since the previous iteration in Range-diff (which is not
> very easy to read).
?
> Could you not mix the ours+theirs thing into the same step? Either
> make it a standalone patch to clean up before or after your main 2
> patches, or leave it totally outside the series and send it after
> this series settles.
Got it, I'll revert that change.
Harald
^ permalink raw reply [flat|nested] 147+ messages in thread