git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] status: show default branch comparison when tracking non-default branch
@ 2025-12-23  0:53 Harald Nordgren via GitGitGadget
  2025-12-23  5:32 ` Junio C Hamano
                   ` (2 more replies)
  0 siblings, 3 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-23  0:53 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

When a branch tracks a non-default remote branch (e.g.,
origin/feature), git status now also displays how the branch
compares to the default branch (origin/main or upstream/main).
This helps users understand if their branch has drifted from the
main development line even when it's in sync with its tracking
branch.

The comparison is shown as a separate line after the tracking
branch status:
- "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 default branch is determined dynamically by checking:
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 default branch.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    status: show default branch comparison when tracking non-default branch
    
    When a branch tracks a non-default remote branch (e.g., origin/feature),
    git status now also displays how the branch compares to the default
    branch (origin/main or upstream/main). This helps users understand if
    their branch has drifted from the main development line even when it's
    in sync with its tracking branch.
    
    The comparison is shown as a separate line after the tracking branch
    status:
    
     * "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
    
    The default branch is determined dynamically by checking:
    
     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 default branch.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v1
Pull-Request: https://github.com/git/git/pull/2138

 remote.c                 | 101 ++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 347 insertions(+)

diff --git a/remote.c b/remote.c
index 59b3715120..9be5e5aa22 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,6 +2237,95 @@ 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 const 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;
+	const 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_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;
+	const 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_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_full);
+}
+
 /*
  * Return true when there is anything to report, otherwise false.
  */
@@ -2248,6 +2337,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 +2348,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 +2362,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 +2380,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 +2393,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 +2409,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

base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23  0:53 [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
@ 2025-12-23  5:32 ` Junio C Hamano
  2025-12-23 10:24   ` Harald Nordgren
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
  2025-12-23 23:11 ` [PATCH] status: show default branch comparison when tracking non-default branch Yee Cheng Chin
  2 siblings, 1 reply; 66+ messages in thread
From: Junio C Hamano @ 2025-12-23  5:32 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>
>
> When a branch tracks a non-default remote branch (e.g.,
> origin/feature), git status now also displays how the branch
> compares to the default branch (origin/main or upstream/main).

"now" meaning what"?

The usual way to compose a log message of this project is to

 - Give an observation on how the current system works in the
   present tense (so no need to say "Currently X is Y", or
   "Previously X was Y" to describe the state before your change;
   just "X is Y" is enough), and discuss what you perceive as a
   problem in it.

 - Propose a solution (optional---often, problem description
   trivially leads to an obvious solution in reader's minds).

 - Give commands to somebody editing the codebase to "make it so",
   instead of saying "This commit does X".

in this order.

So if you are following the convention, "now also displays" ought to
be about what the current code without this patch does, but I am
sensing that it probably is not the case.

Start your explanation by decribing what the users see in "git
status" output without this patch.  Perhaps like

    "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).

That is the "giving an observation" part.  And then describe why
that comparison with a single remote branch may be insufficient to
learn the current status.  Your reasoning might be something like

    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.

That's the "discuss your problem with the status quo" part.

Only after that, propose to show two sets of comparison.

> This helps users understand if their branch has drifted from the
> main development line even when it's in sync with its tracking
> branch.

Describe what does it help to know that after that sentence, like
"... to get the feel of when to start thinking about rebasing", or
something.

> The comparison is shown as a separate line after the tracking
> branch status:
> - "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

In other words, exactly the same way as what we show with the
tracking branch?

The triangular workflow involves two remote things.  One is where
you pull from to catch up.  After building on top, you push to
somewhere else to publish your work.  This may be a different branch
in the same repository you pull from, or a branch in a completely
different repository.  What you pushed out may be processed by
others and may come back in the branch you pull from eventually to
complete the triangle.

In such a triangular workflow, comparison with these two remote
things may be needed. One with the branch you forked your work from
to know how much work _other_ people added to the branch to learn
when to start thinking about catching up, and with the branch you
are pushing your work to to know how much work you are holding
locally without pushing out.

I am not sure what you mean by the word "default" here, though.

You seem to be using the "what would a new user get when they clone
the remote (by virtue of their HEAD pointing at that branch)", but
I am not sure if that is a good way to determine the other remote
thing to compare with.

Even if one remote branch you pull from (but not push to) has a name
that is not one of those usual ones like 'main', 'master', 'trunk',
'default', you would want to compare with it in addition to where
you are pushing to.  So branch.<name>.merge + branch.<name>.remote
that defines where you pull from is one thing to compare with.  To
learn the other, the destination of a push of this branch, would
involve poking at remote.pushdefault, branch.<name>.pushRemote,
branch.<name>.remote to find out which remote repository it goes,
and then remote.<remote>.push to find out where this branch goes,
but the helper functions to learn all that are already available.

So, I think the topic addresses a good problem, its presentation
needs a bit more work, and its design (not the implementation) of
how to figure out the other thing to compare I am not sure about.

Thanks.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23  5:32 ` Junio C Hamano
@ 2025-12-23 10:24   ` Harald Nordgren
  2025-12-23 11:36     ` Harald Nordgren
  2025-12-23 13:32     ` Junio C Hamano
  0 siblings, 2 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-23 10:24 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren

> In other words, exactly the same way as what we show with the
> tracking branch?
> 
> The triangular workflow involves two remote things.  One is where
> you pull from to catch up.  After building on top, you push to
> somewhere else to publish your work.  This may be a different branch
> in the same repository you pull from, or a branch in a completely
> different repository.  What you pushed out may be processed by
> others and may come back in the branch you pull from eventually to
> complete the triangle.
> 
> In such a triangular workflow, comparison with these two remote
> things may be needed. One with the branch you forked your work from
> to know how much work _other_ people added to the branch to learn
> when to start thinking about catching up, and with the branch you
> are pushing your work to to know how much work you are holding
> locally without pushing out.

Yes, it's the same as when tracking master/main, but with far less complexity for the end-user. For many years I have had the habit of running these on my feature branches:

	git checkout -b feature_branch
	git branch --set-upstream-to origin/$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')

	# To merge in other's code early
	git pull --rebase origin $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')

	# To push to correct branch, because I'm tracking default instead "feature_branch"
	git push origin $(git rev-parse --abbrev-ref HEAD)

I have found this very hard to explain the benefits of this to other developers. Git is already scary to many, they are afraid of losing work, so they would rather not mess with the tracking branches and break the regular "git push" functionality.

For me, I feel blind of I can't see how my branch compares the master/main at all times.

> I am not sure what you mean by the word "default" here, though.
> 
> You seem to be using the "what would a new user get when they clone
> the remote (by virtue of their HEAD pointing at that branch)", but
> I am not sure if that is a good way to determine the other remote
> thing to compare with.
>
> Even if one remote branch you pull from (but not push to) has a name
> that is not one of those usual ones like 'main', 'master', 'trunk',
> 'default'

Agreed, this should be as agnostic as possible. "Default" might be me using GitHub terminology. However, it seems that 'git symbolic-ref refs/remotes/upstream/HEAD' always produces the desires result, so for the sake of discussion we can call it "upstream/HEAD" instead of "default".

Harald

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 10:24   ` Harald Nordgren
@ 2025-12-23 11:36     ` Harald Nordgren
  2025-12-23 12:23       ` Chris Torek
  2025-12-23 13:32     ` Junio C Hamano
  1 sibling, 1 reply; 66+ messages in thread
From: Harald Nordgren @ 2025-12-23 11:36 UTC (permalink / raw)
  To: haraldnordgren; +Cc: git, gitgitgadget, gitster

(Adding another comment to my own thread to reply to Junio C Hamano)

> branch.<name>.merge + branch.<name>.remote
> that defines where you pull from is one thing to compare with.  To
> learn the other, the destination of a push of this branch, would
> involve poking at remote.pushdefault, branch.<name>.pushRemote,
> branch.<name>.remote to find out which remote repository it goes,
> and then remote.<remote>.push to find out where this branch goes,
> but the helper functions to learn all that are already available.

When a new branch is created it has no push settings:

	git checkout -b ahead_of_main_status__tmp2

	git push
	fatal: The current branch ahead_of_main_status__tmp2 has no upstream branch.
	To push the current branch and set the remote as upstream, use

    	git push --set-upstream origin ahead_of_main_status__tmp2

	To have this happen automatically for branches without a tracking
	upstream, see 'push.autoSetupRemote' in 'git help config'.

Once the users runs that suggested command

	git push --set-upstream origin ahead_of_main_status__tmp2

then the 'branch.<name>.merge' and 'branch.<name>.remote' no longer hold the reference to "upstream/HEAD".

For sure, it would be great to re-use previous logic for this, but can it really be done without new logic?

Harald

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 11:36     ` Harald Nordgren
@ 2025-12-23 12:23       ` Chris Torek
  2025-12-23 14:18         ` Harald Nordgren
  0 siblings, 1 reply; 66+ messages in thread
From: Chris Torek @ 2025-12-23 12:23 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, gitgitgadget, gitster

On Tue, Dec 23, 2025 at 3:36 AM Harald Nordgren
<haraldnordgren@gmail.com> wrote:
> Once the users runs that suggested command
>
>         git push --set-upstream origin ahead_of_main_status__tmp2
>
> then the 'branch.<name>.merge' and 'branch.<name>.remote' no longer hold the reference to "upstream/HEAD".

Right.

And, as Junio noted, to:

>> learn the other, the destination of a push of this branch, would
>> involve poking at remote.pushdefault, branch.<name>.pushRemote,
>> branch.<name>.remote to find out which remote repository it goes,
>> and then remote.<remote>.push to find out where this branch goes,

That is, there are some separate configuration items that can change
where `git push` goes.

Using `git push --set-upstream` sets ones that affect `git pull`, `git status`,
and `git push`, but it's possible to set ones that affect only `git push`.

That still leaves `git status` with the problem you (Harald) have observed,
so perhaps the path forward is to have `git status` check things like
branch.<name>.pushRemote to see if they exist and differ from
branch.<name>.remote.

Chris

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 10:24   ` Harald Nordgren
  2025-12-23 11:36     ` Harald Nordgren
@ 2025-12-23 13:32     ` Junio C Hamano
  2025-12-23 14:09       ` Harald Nordgren
  1 sibling, 1 reply; 66+ messages in thread
From: Junio C Hamano @ 2025-12-23 13:32 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, gitgitgadget

Harald Nordgren <haraldnordgren@gmail.com> writes:

>> You seem to be using the "what would a new user get when they clone
>> the remote (by virtue of their HEAD pointing at that branch)", but
>> I am not sure if that is a good way to determine the other remote
>> thing to compare with.
>>
>> Even if one remote branch you pull from (but not push to) has a name
>> that is not one of those usual ones like 'main', 'master', 'trunk',
>> 'default'
>
> Agreed, this should be as agnostic as possible. "Default" might be
> me using GitHub terminology. However, it seems that 'git
> symbolic-ref refs/remotes/upstream/HEAD' always produces the
> desires result, so for the sake of discussion we can call it
> "upstream/HEAD" instead of "default".

I didn't exactly question the terminology, but was wondering more
about the wisdom of using remotes/*/HEAD.

If a project uses the same remote repository to maintain its
maintenance and development tracks, their HEAD might point at the
'main' (used for development), but some of your branches you used to
work on fixes that can later be merged to the maintenance track, it
is likely that you'll fork from their 'maint', and while you keep
polishing your fixes, you may push your 'fix' branch to their 'fix'
branch.  Comparing your 'fix' with their 'fix' is what we already
do, and it gives two thirds of what you need, but the missing
comparison is with their 'maint', not with their HEAD that points at
their 'main'.

Thanks.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 13:32     ` Junio C Hamano
@ 2025-12-23 14:09       ` Harald Nordgren
  0 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-23 14:09 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren

> If a project uses the same remote repository to maintain its
> maintenance and development tracks, their HEAD might point at the
> 'main' (used for development), but some of your branches you used to
> work on fixes that can later be merged to the maintenance track, it
> is likely that you'll fork from their 'maint', and while you keep
> polishing your fixes, you may push your 'fix' branch to their 'fix'
> branch.  Comparing your 'fix' with their 'fix' is what we already
> do, and it gives two thirds of what you need, but the missing
> comparison is with their 'maint', not with their HEAD that points at
> their 'main'.

That's a fair point. I am used to trunk-based development, but I recognize
that there are other paradigms out there.

One possibly solution could be to make this adjustable in repo-wide config
(maybe 'repo.settings.defaultBranch=maint') and when unset it compares to
'refs/remotes/upstream/HEAD'?

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 12:23       ` Chris Torek
@ 2025-12-23 14:18         ` Harald Nordgren
  2025-12-23 14:22           ` Chris Torek
  0 siblings, 1 reply; 66+ messages in thread
From: Harald Nordgren @ 2025-12-23 14:18 UTC (permalink / raw)
  To: chris.torek; +Cc: git, gitgitgadget, gitster, haraldnordgren

> That still leaves `git status` with the problem you (Harald) have observed,
> so perhaps the path forward is to have `git status` check things like
> branch.<name>.pushRemote to see if they exist and differ from
> branch.<name>.remote.

I played around with pushRemote and I'm not getting it to do something
that's useful for me here. Granted I'm not an expert there so happy to take
some hints on how to use it more specifically.

I checked some of my other projects and it seems pushRemote doesn't get
set, even when working with one fork and one upstream repo:

	$ git remote -v
	HaraldNordgren	git@github.com:HaraldNordgren/brew.git (fetch)
	HaraldNordgren	git@github.com:HaraldNordgren/brew.git (push)
	origin	git@github.com:Homebrew/brew.git (fetch)
	origin	git@github.com:Homebrew/brew.git (push)

Harald

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 14:18         ` Harald Nordgren
@ 2025-12-23 14:22           ` Chris Torek
  0 siblings, 0 replies; 66+ messages in thread
From: Chris Torek @ 2025-12-23 14:22 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, gitgitgadget, gitster

On Tue, Dec 23, 2025 at 6:18 AM Harald Nordgren
<haraldnordgren@gmail.com> wrote:
> I played around with pushRemote and I'm not getting it to do something
> that's useful for me here. Granted I'm not an expert there so happy to take
> some hints on how to use it more specifically.

I've never actually used it myself. You do have to set it manually
(with `git config` for instance).

I've never been completely convinced that Git's triangular workflow
setups are The Right Way, or even a Good Way. I've always just
done everything manually. But they're documented, so they ought
to work.

Chris

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v2 0/2] status: show default branch comparison when tracking non-default branch
  2025-12-23  0:53 [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-23  5:32 ` Junio C Hamano
@ 2025-12-23 22:54 ` Harald Nordgren via GitGitGadget
  2025-12-23 22:54   ` [PATCH v2 1/2] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
                     ` (3 more replies)
  2025-12-23 23:11 ` [PATCH] status: show default branch comparison when tracking non-default branch Yee Cheng Chin
  2 siblings, 4 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-23 22:54 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren

cc: Chris Torek chris.torek@gmail.com

Harald Nordgren (2):
  status: show comparison with upstream default branch
  Simplify default branch comparison logic

 remote.c                 |  83 +++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 329 insertions(+)


base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v2
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v1:

 1:  c27a806dc9 ! 1:  a3800aed18 status: show default branch comparison when tracking non-default branch
     @@ Metadata
      Author: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Commit message ##
     -    status: show default branch comparison when tracking non-default branch
     +    status: show comparison with upstream default branch
      
     -    When a branch tracks a non-default remote branch (e.g.,
     -    origin/feature), git status now also displays how the branch
     -    compares to the default branch (origin/main or upstream/main).
     -    This helps users understand if their branch has drifted from the
     -    main development line even when it's in sync with its 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" (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).
      
     -    The comparison is shown as a separate line after the tracking
     -    branch 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.
     +
     +    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 default branch is determined dynamically by checking:
     +    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 default branch.
     +    branch but differs from the upstream default 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 const char *get_default_remote_ref(char **full_ref_out)
     ++static char *get_default_remote_ref(char **full_ref_out)
      +{
      +	int flag;
      +	const char *resolved;
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +static int is_default_remote_branch(const char *name)
      +{
      +	char *default_full = NULL;
     -+	const char *default_short;
     ++	char *default_short;
      +	int result = 0;
      +
      +	default_short = get_default_remote_ref(&default_full);
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +
      +	result = !strcmp(name, default_short);
      +
     ++	free(default_short);
      +	free(default_full);
      +	return result;
      +}
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +{
      +	int default_ours = 0, default_theirs = 0;
      +	char *default_full = NULL;
     -+	const char *default_short;
     ++	char *default_short;
      +
      +	default_short = get_default_remote_ref(&default_full);
      +	if (!default_short)
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +
      +	if (stat_branch_pair(branch_refname, default_full,
      +			     &default_ours, &default_theirs, abf) <= 0) {
     ++		free(default_short);
      +		free(default_full);
      +		return;
      +	}
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +			default_short, default_ours + default_theirs);
      +	}
      +
     ++	free(default_short);
      +	free(default_full);
      +}
      +
 -:  ---------- > 2:  417f2075fb Simplify default branch comparison logic

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v2 1/2] status: show comparison with upstream default branch
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
@ 2025-12-23 22:54   ` Harald Nordgren via GitGitGadget
  2025-12-24  1:30     ` brian m. carlson
  2025-12-23 22:54   ` [PATCH v2 2/2] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-23 22:54 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] 66+ messages in thread

* [PATCH v2 2/2] Simplify default branch comparison logic
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
  2025-12-23 22:54   ` [PATCH v2 1/2] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-23 22:54   ` Harald Nordgren via GitGitGadget
  2025-12-24  0:00   ` [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
  3 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-23 22:54 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] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23  0:53 [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-23  5:32 ` Junio C Hamano
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
@ 2025-12-23 23:11 ` Yee Cheng Chin
  2025-12-23 23:59   ` Harald Nordgren
  2025-12-24  0:38   ` Junio C Hamano
  2 siblings, 2 replies; 66+ messages in thread
From: Yee Cheng Chin @ 2025-12-23 23:11 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren

> The default branch is determined dynamically by checking:
> 1. refs/remotes/upstream/HEAD (if upstream remote exists)
> 2. refs/remotes/origin/HEAD (fallback)

I feel like this is making a lot of assumptions regarding remotes.
"origin" and "upstream" are not inherently special names for remotes.
I personally have different Git repositories where they could mean
slightly different things, and I don't use the "upstream" wording
myself (I sometimes use "official" for the upstream branch, and/or
"ychin" for my own fork's remote). Feels like we should not be
imposing such a hard-coded value when nothing else in Git enforces it.

Also, when there are multiple remotes, it's not always clear which one
the user actually cares about. It's not always clear if they care
about the upstream or the downstream remote, of a third one that
actually matters more.

This would also work poorly with detached branches (e.g. the popular
'gh-pages' branches in a lot of repositories), or permanently foked
branches like fixed versions (e.g. v2.x legacy branch when the
software main branch moved to v3.0). Seems like for this to work well
it would need to be configurable per branch. Even on a repository
level there would likely be lots of edge cases with each branch having
its unique circumstances.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 23:11 ` [PATCH] status: show default branch comparison when tracking non-default branch Yee Cheng Chin
@ 2025-12-23 23:59   ` Harald Nordgren
  2025-12-24  0:55     ` Yee Cheng Chin
  2025-12-24  0:38   ` Junio C Hamano
  1 sibling, 1 reply; 66+ messages in thread
From: Harald Nordgren @ 2025-12-23 23:59 UTC (permalink / raw)
  To: ychin.macvim; +Cc: git, gitgitgadget, haraldnordgren

These are very fair points, maybe a reason not to have this feature on by default then.

For me, I work against clear origin/master, origin/master, origin/develop branches at my dayjob, and when I do open-source there is a default branch to work against. The GitHub 'gh' tool defaults to upstream and origin repos, which is why I chose those. But I agree it might not be right for everyone.

Maybe what I wrote in a previous message about making this configurable via 'repo.settings.defaultBranch' (maybe call it 'repo.settings.statusGoalBranch') would be useful, but having it off by default instead of on by default as I originally suggested.

I feel strongly that it should be able to be set be repo-specific (and globally). Having it only per branch defeats a big part of it. Should be straightforward git config to have a repo-wide rule but still allow disabling it for e.g. 'gh-pages', I hope?


Harald

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
  2025-12-23 22:54   ` [PATCH v2 1/2] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
  2025-12-23 22:54   ` [PATCH v2 2/2] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
@ 2025-12-24  0:00   ` Harald Nordgren
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
  3 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-24  0:00 UTC (permalink / raw)
  To: gitgitgadget; +Cc: git, haraldnordgren

By the way, I'm using my own version of git while working on this, and I'm loving it 😅

	$ /Users/Harald/git-repos/github.com/git/git/git status
	On branch ahead_of_main_status
	Your branch and 'origin/ahead_of_main_status' have diverged,
	and have 1 and 1 different commits each, respectively.
	  (use "git pull" if you want to integrate the remote branch with yours)

	Ahead of 'upstream/master' by 2 commits.

	nothing to commit, working tree clean

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 23:11 ` [PATCH] status: show default branch comparison when tracking non-default branch Yee Cheng Chin
  2025-12-23 23:59   ` Harald Nordgren
@ 2025-12-24  0:38   ` Junio C Hamano
  2025-12-24  0:49     ` Yee Cheng Chin
  2025-12-24  1:12     ` Harald Nordgren
  1 sibling, 2 replies; 66+ messages in thread
From: Junio C Hamano @ 2025-12-24  0:38 UTC (permalink / raw)
  To: Yee Cheng Chin; +Cc: Harald Nordgren via GitGitGadget, git, Harald Nordgren

Yee Cheng Chin <ychin.macvim@gmail.com> writes:

>> The default branch is determined dynamically by checking:
>> 1. refs/remotes/upstream/HEAD (if upstream remote exists)
>> 2. refs/remotes/origin/HEAD (fallback)
>
> I feel like this is making a lot of assumptions regarding remotes.
> "origin" and "upstream" are not inherently special names for remotes.

Good point.  There is a mechanism to determine where a branch would
be pushed to with "git push", and where the new material to update
the branch would come from with "git pull", and these places need to
be considered when doing comparisons.  This series seems to punt on
determining both repository and branch and instead uses a hardcoded
"upstream" (or "origin") and "HEAD", which is not satisfactory.

> I personally have different Git repositories where they could mean
> slightly different things, and I don't use the "upstream" wording
> myself (I sometimes use "official" for the upstream branch, and/or
> "ychin" for my own fork's remote). Feels like we should not be
> imposing such a hard-coded value when nothing else in Git enforces it.
>
> Also, when there are multiple remotes, it's not always clear which one
> the user actually cares about. It's not always clear if they care
> about the upstream or the downstream remote, of a third one that
> actually matters more.
>
> This would also work poorly with detached branches (e.g. the popular
> 'gh-pages' branches in a lot of repositories), or permanently foked
> branches like fixed versions (e.g. v2.x legacy branch when the
> software main branch moved to v3.0). Seems like for this to work well
> it would need to be configurable per branch. Even on a repository
> level there would likely be lots of edge cases with each branch having
> its unique circumstances.

Yes, unrelated branches like gh-pages gives us a very good example
to think about.

Thanks.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-24  0:38   ` Junio C Hamano
@ 2025-12-24  0:49     ` Yee Cheng Chin
  2025-12-24  1:44       ` Junio C Hamano
  2025-12-24  1:12     ` Harald Nordgren
  1 sibling, 1 reply; 66+ messages in thread
From: Yee Cheng Chin @ 2025-12-24  0:49 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Harald Nordgren via GitGitGadget, git, Harald Nordgren

On Tue, Dec 23, 2025 at 4:38 PM Junio C Hamano <gitster@pobox.com> wrote:
> Good point.  There is a mechanism to determine where a branch would
> be pushed to with "git push", and where the new material to update
> the branch would come from with "git pull", and these places need to
> be considered when doing comparisons.  This series seems to punt on
> determining both repository and branch and instead uses a hardcoded
> "upstream" (or "origin") and "HEAD", which is not satisfactory.

I may be speaking for the author here, but I think the reason why the
upstream discovery (or alternatively an additional configuration in
the repo) feature was suggested is that not everyone uses the
mechanism you described (which I think is the "pushRemote" config).
It's true that if you *do* indeed have separate push/pull remotes for
a branch, then both should ideally be shown in `git status`. However,
oftentimes when you set up a feature branch it may not be correct /
desirable to set up separate push/pull remotes for those, especially
if you need to collaborate with people on the feature and therefore do
need to pull from the same feature's branch. In this case, I think the
author still wants `git status` to be able to show the diversion from
the original upstream master/main.

Personally I wonder if this feature gets added, people will start
asking for a list of "upstream branches" to be able to be compared to,
rather than just a fixed upstream/HEAD or origin/HEAD. But maybe I'm
thinking too far ahead.

But for example, I maintain MacVim, which is a downstream fork of Vim.
As such, "upstream" is a fluid concept for me depending on what I'm
doing. It could mean deviation between ychin/macvim vs
macvim-dev/macvim, or macvim-dev/macvim vs vim/vim. For complicated
forks, it's feasible someone may ask for the ability to see the branch
comparison against multiple branches concurrently.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-23 23:59   ` Harald Nordgren
@ 2025-12-24  0:55     ` Yee Cheng Chin
  0 siblings, 0 replies; 66+ messages in thread
From: Yee Cheng Chin @ 2025-12-24  0:55 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, gitgitgadget

> I feel strongly that it should be able to be set be repo-specific (and globally). Having it only per branch defeats a big part of it. Should be straightforward git config to have a repo-wide rule but still allow disabling it for e.g. 'gh-pages', I hope?

I don't have that much of an opinion on per-repo / per-branch
configuration but I just wanted to point out that some branches would
benefit from the ability to turn this off. Perhaps it could be a two
layered setting that could be both configured per-repo / per-branch
but I don't know if this makes it too complicated.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-24  0:38   ` Junio C Hamano
  2025-12-24  0:49     ` Yee Cheng Chin
@ 2025-12-24  1:12     ` Harald Nordgren
  1 sibling, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-24  1:12 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren, ychin.macvim

I see push and pull remotes mentioned a lot here. And I want to find out how much of the problem that can solve. Let’s say you have two remotes ’upstream’ and ’fork’.

Can push and pull remotes be set up in a way so the user can run these bare commands without and additional args or flags:

    git push # push to fork
    git pull # pull from upstream

And if so, will ’git status’ also show the status against upstream?

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH v2 1/2] status: show comparison with upstream default branch
  2025-12-23 22:54   ` [PATCH v2 1/2] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-24  1:30     ` brian m. carlson
  2025-12-24  1:46       ` Junio C Hamano
  0 siblings, 1 reply; 66+ messages in thread
From: brian m. carlson @ 2025-12-24  1:30 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren

[-- Attachment #1: Type: text/plain, Size: 992 bytes --]

On 2025-12-23 at 22:54:07, Harald Nordgren via GitGitGadget wrote:
> +static char *get_default_remote_ref(char **full_ref_out)
> +{
> +	int flag;
> +	const char *resolved;
> +	static const char *remotes[] = { "upstream", "origin", NULL };

This should definitely be configurable, not hard-coded.  For instance, I
have this in my gitconfig:

    [clone]
        defaultRemoteName = def

so my default remote name is `def` (short for "default", as in the
default place to push) and this code will never work.

Another reason to have this be configurable is that some projects (like
a company I used to work at) have tooling that demands a specific remote
naming convention for tooling to work properly.  In this particular
case, `upstream` would have been the right choice, but I also worked at a
company where English was the common language.  Many people may prefer
to use names suitable to their local language.
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-24  0:49     ` Yee Cheng Chin
@ 2025-12-24  1:44       ` Junio C Hamano
  2025-12-24 10:24         ` Harald Nordgren
  0 siblings, 1 reply; 66+ messages in thread
From: Junio C Hamano @ 2025-12-24  1:44 UTC (permalink / raw)
  To: Yee Cheng Chin; +Cc: Harald Nordgren via GitGitGadget, git, Harald Nordgren

Yee Cheng Chin <ychin.macvim@gmail.com> writes:

> I may be speaking for the author here, but I think the reason why the
> upstream discovery (or alternatively an additional configuration in
> the repo) feature was suggested is that not everyone uses the
> mechanism you described (which I think is the "pushRemote" config).

The mention of pushRemote by Harald is a red-herring.  My point is
that branch.<name>.remote tells what remote we interact with (and
pushRemote is used as a fallback), so there is no need to, and it is
actively wrong to, assume "upstream" or "origin".  Even if you push
back to the same repository as you pull from, you may push to a
different branch there, in which case you do not need pushRemote,
but you'd need to learn what configured remote you are using anyway.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH v2 1/2] status: show comparison with upstream default branch
  2025-12-24  1:30     ` brian m. carlson
@ 2025-12-24  1:46       ` Junio C Hamano
  0 siblings, 0 replies; 66+ messages in thread
From: Junio C Hamano @ 2025-12-24  1:46 UTC (permalink / raw)
  To: brian m. carlson; +Cc: Harald Nordgren via GitGitGadget, git, Harald Nordgren

"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> On 2025-12-23 at 22:54:07, Harald Nordgren via GitGitGadget wrote:
>> +static char *get_default_remote_ref(char **full_ref_out)
>> +{
>> +	int flag;
>> +	const char *resolved;
>> +	static const char *remotes[] = { "upstream", "origin", NULL };
>
> This should definitely be configurable, not hard-coded.  For instance, I
> have this in my gitconfig:
>
>     [clone]
>         defaultRemoteName = def
>
> so my default remote name is `def` (short for "default", as in the
> default place to push) and this code will never work.

But there does not need any new configuration.  "git pull" and "git
push" on the branch is equipped to figure out where they pull from
and push to, so the new feature should be able to figure out these
(often the same, but not necessarily) repositories the same way.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v3 0/3] status: show default branch comparison when tracking non-default branch
  2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
                     ` (2 preceding siblings ...)
  2025-12-24  0:00   ` [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren
@ 2025-12-24  9:31   ` Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 1/3] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
                       ` (3 more replies)
  3 siblings, 4 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24  9:31 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 (3):
  status: show comparison with upstream default branch
  Simplify default branch comparison logic
  Use repo.settings.statusGoalBranch config for status comparison

 remote.c                 |  93 ++++++++++++
 t/t6040-tracking-info.sh | 317 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 410 insertions(+)


base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v3
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v2:

 1:  a3800aed18 = 1:  a3800aed18 status: show comparison with upstream default branch
 2:  417f2075fb = 2:  417f2075fb Simplify default branch comparison logic
 -:  ---------- > 3:  c9ec5d9610 Use repo.settings.statusGoalBranch config for status comparison

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v3 1/3] status: show comparison with upstream default branch
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
@ 2025-12-24  9:31     ` Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 2/3] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24  9:31 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] 66+ messages in thread

* [PATCH v3 2/3] Simplify default branch comparison logic
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 1/3] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-24  9:31     ` Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 3/3] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  3 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24  9:31 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] 66+ messages in thread

* [PATCH v3 3/3] Use repo.settings.statusGoalBranch config for status comparison
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 1/3] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
  2025-12-24  9:31     ` [PATCH v3 2/3] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
@ 2025-12-24  9:31     ` Harald Nordgren via GitGitGadget
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  3 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24  9:31 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] 66+ messages in thread

* [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch
  2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
                       ` (2 preceding siblings ...)
  2025-12-24  9:31     ` [PATCH v3 3/3] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
@ 2025-12-24 10:19     ` Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 1/4] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
                         ` (4 more replies)
  3 siblings, 5 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:19 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 (4):
  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

 remote.c                 |  92 ++++++++++++
 t/t6040-tracking-info.sh | 317 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 409 insertions(+)


base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v4
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v3:

 1:  a3800aed18 = 1:  a3800aed18 status: show comparison with upstream default branch
 2:  417f2075fb = 2:  417f2075fb Simplify default branch comparison logic
 3:  c9ec5d9610 = 3:  c9ec5d9610 Use repo.settings.statusGoalBranch config for status comparison
 -:  ---------- > 4:  0e308141da Rename default_remote to goal_branch

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v4 1/4] status: show comparison with upstream default branch
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:19       ` Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 2/4] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
                         ` (3 subsequent siblings)
  4 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:19 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] 66+ messages in thread

* [PATCH v4 2/4] Simplify default branch comparison logic
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 1/4] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:19       ` Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 3/4] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
                         ` (2 subsequent siblings)
  4 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:19 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] 66+ messages in thread

* [PATCH v4 3/4] Use repo.settings.statusGoalBranch config for status comparison
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 1/4] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 2/4] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
@ 2025-12-24 10:19       ` Harald Nordgren via GitGitGadget
  2025-12-24 10:19       ` [PATCH v4 4/4] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  4 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:19 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] 66+ messages in thread

* [PATCH v4 4/4] Rename default_remote to goal_branch
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
                         ` (2 preceding siblings ...)
  2025-12-24 10:19       ` [PATCH v4 3/4] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
@ 2025-12-24 10:19       ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  4 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:19 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] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-24  1:44       ` Junio C Hamano
@ 2025-12-24 10:24         ` Harald Nordgren
  0 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-24 10:24 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren, ychin.macvim

> This series seems to punt on
> determining both repository and branch and instead uses a hardcoded
> "upstream" (or "origin") and "HEAD", which is not satisfactory.

I have made a change to the code to not assume "upstream" or "origin" as remote names. Now there is a config setting 'repo.settings.statusGoalBranch' to enable this the check.

I side-stepped the discussion about pushRemotes since what Yee Cheng Chin brought up seems to kill the possibility of determining comparison branch dynamically. Let me know what you think of the latest code and I can handle it if needed 🤗

> I don't have that much of an opinion on per-repo / per-branch
> configuration but I just wanted to point out that some branches would
> benefit from the ability to turn this off. Perhaps it could be a two
> layered setting that could be both configured per-repo / per-branch
> but I don't know if this makes it too complicated.

I skipped this for now because I couldn't find a precedent in the sourc code for config settings that are "on repo-wide, but turned off for some branches". I could dig into this if we need it. I would like to hear Junio C Hamano's thoughts on this specific thing too 🤗


Harald

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch
  2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
                         ` (3 preceding siblings ...)
  2025-12-24 10:19       ` [PATCH v4 4/4] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38       ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 1/5] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
                           ` (5 more replies)
  4 siblings, 6 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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 (5):
  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

 remote.c                 |  92 +++++++++++
 t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 432 insertions(+)


base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v5
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v4:

 1:  a3800aed18 = 1:  a3800aed18 status: show comparison with upstream default branch
 2:  417f2075fb = 2:  417f2075fb Simplify default branch comparison logic
 3:  c9ec5d9610 = 3:  c9ec5d9610 Use repo.settings.statusGoalBranch config for status comparison
 4:  0e308141da = 4:  0e308141da Rename default_remote to goal_branch
 -:  ---------- > 5:  441678939f Add warning for malformed statusGoalBranch config

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH v5 1/5] status: show comparison with upstream default branch
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38         ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 2/5] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
                           ` (4 subsequent siblings)
  5 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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] 66+ messages in thread

* [PATCH v5 2/5] Simplify default branch comparison logic
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 1/5] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38         ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 3/5] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
                           ` (3 subsequent siblings)
  5 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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] 66+ messages in thread

* [PATCH v5 3/5] Use repo.settings.statusGoalBranch config for status comparison
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 1/5] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 2/5] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38         ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 4/5] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
                           ` (2 subsequent siblings)
  5 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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] 66+ messages in thread

* [PATCH v5 4/5] Rename default_remote to goal_branch
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
                           ` (2 preceding siblings ...)
  2025-12-24 10:38         ` [PATCH v5 3/5] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38         ` Harald Nordgren via GitGitGadget
  2025-12-24 10:38         ` [PATCH v5 5/5] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
  2025-12-24 23:41         ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  5 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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] 66+ messages in thread

* [PATCH v5 5/5] Add warning for malformed statusGoalBranch config
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
                           ` (3 preceding siblings ...)
  2025-12-24 10:38         ` [PATCH v5 4/5] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
@ 2025-12-24 10:38         ` Harald Nordgren via GitGitGadget
  2025-12-24 23:41         ` [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
  5 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 10:38 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] 66+ messages in thread

* [PATCH v6 0/6] status: show default branch comparison when tracking non-default branch
  2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
                           ` (4 preceding siblings ...)
  2025-12-24 10:38         ` [PATCH v5 5/5] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
@ 2025-12-24 23:41         ` Harald Nordgren via GitGitGadget
  2025-12-24 23:41           ` [PATCH v6 1/6] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
                             ` (7 more replies)
  5 siblings, 8 replies; 66+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2025-12-24 23: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 (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

 remote.c                 |  92 +++++++++++
 t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 432 insertions(+)


base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2138%2FHaraldNordgren%2Fahead_of_main_status-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2138/HaraldNordgren/ahead_of_main_status-v6
Pull-Request: https://github.com/git/git/pull/2138

Range-diff vs v5:

 1:  a3800aed18 = 1:  a3800aed18 status: show comparison with upstream default branch
 2:  417f2075fb = 2:  417f2075fb Simplify default branch comparison logic
 3:  c9ec5d9610 = 3:  c9ec5d9610 Use repo.settings.statusGoalBranch config for status comparison
 4:  0e308141da = 4:  0e308141da Rename default_remote to goal_branch
 5:  441678939f = 5:  441678939f Add warning for malformed statusGoalBranch config
 -:  ---------- > 6:  242dbbae44 Change config key to status.compareBranch

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  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
  2025-12-26  1:59               ` Junio C Hamano
  0 siblings, 1 reply; 66+ messages in thread
From: Harald Nordgren @ 2025-12-25  9:45 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren

Got it! I'd rather just squash all the commits then, the total diff is
quite small still.


Harald

^ permalink raw reply	[flat|nested] 66+ 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; 66+ 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] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-25  9:45             ` [PATCH] " Harald Nordgren
@ 2025-12-26  1:59               ` Junio C Hamano
  2025-12-26 10:58                 ` Harald Nordgren
  0 siblings, 1 reply; 66+ messages in thread
From: Junio C Hamano @ 2025-12-26  1:59 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, gitgitgadget

Harald Nordgren <haraldnordgren@gmail.com> writes:

> Got it! I'd rather just squash all the commits then, the total diff is
> quite small still.

Well, that is quite different from what I meant, but let's see what
others may say.

Happy holidays.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH] status: show default branch comparison when tracking non-default branch
  2025-12-26  1:59               ` Junio C Hamano
@ 2025-12-26 10:58                 ` Harald Nordgren
  0 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-26 10:58 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren

Thanks, I appreciate all your help! I feel like a bit of a bull in a china shop here.


Merry Christmas!

^ permalink raw reply	[flat|nested] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ 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; 66+ 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] 66+ messages in thread

* Code review?
  2025-12-28 11:46               ` [PATCH v8] status: show comparison with configured goal branch Junio C Hamano
@ 2025-12-28 15:46                 ` Harald Nordgren
  0 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-28 15:46 UTC (permalink / raw)
  To: gitster; +Cc: git, gitgitgadget, haraldnordgren

Thanks a lot, I sent a new patch!

^ permalink raw reply	[flat|nested] 66+ 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                   ` Code review? Harald Nordgren
  0 siblings, 2 replies; 66+ 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] 66+ 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                   ` Code review? Harald Nordgren
  1 sibling, 1 reply; 66+ 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] 66+ 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; 66+ 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] 66+ messages in thread

* Triangular workflows
  2025-12-28 23:09                     ` Ben Knoble
@ 2025-12-29 12:17                       ` Harald Nordgren
  0 siblings, 0 replies; 66+ messages in thread
From: Harald Nordgren @ 2025-12-29 12:17 UTC (permalink / raw)
  To: ben.knoble
  Cc: chris.torek, git, gitgitgadget, gitster, haraldnordgren, sandals,
	ychin.macvim

Hi Ben!

That would be very nice, thanks!


Harald

^ permalink raw reply	[flat|nested] 66+ 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
                                     ` (2 more replies)
  2 siblings, 3 replies; 66+ 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] 66+ 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
  2025-12-30 16:08                   ` [PATCH v10 3/3] use pushRemote and tracking branch Harald Nordgren via GitGitGadget
  2 siblings, 0 replies; 66+ 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] 66+ 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 siblings, 0 replies; 66+ 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] 66+ 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
  2 siblings, 0 replies; 66+ 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] 66+ 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; 66+ 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] 66+ messages in thread

end of thread, other threads:[~2025-12-30 16:08 UTC | newest]

Thread overview: 66+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-23  0:53 [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
2025-12-23  5:32 ` Junio C Hamano
2025-12-23 10:24   ` Harald Nordgren
2025-12-23 11:36     ` Harald Nordgren
2025-12-23 12:23       ` Chris Torek
2025-12-23 14:18         ` Harald Nordgren
2025-12-23 14:22           ` Chris Torek
2025-12-23 13:32     ` Junio C Hamano
2025-12-23 14:09       ` Harald Nordgren
2025-12-23 22:54 ` [PATCH v2 0/2] " Harald Nordgren via GitGitGadget
2025-12-23 22:54   ` [PATCH v2 1/2] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
2025-12-24  1:30     ` brian m. carlson
2025-12-24  1:46       ` Junio C Hamano
2025-12-23 22:54   ` [PATCH v2 2/2] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
2025-12-24  0:00   ` [PATCH] status: show default branch comparison when tracking non-default branch Harald Nordgren
2025-12-24  9:31   ` [PATCH v3 0/3] " Harald Nordgren via GitGitGadget
2025-12-24  9:31     ` [PATCH v3 1/3] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
2025-12-24  9:31     ` [PATCH v3 2/3] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
2025-12-24  9:31     ` [PATCH v3 3/3] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
2025-12-24 10:19     ` [PATCH v4 0/4] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
2025-12-24 10:19       ` [PATCH v4 1/4] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
2025-12-24 10:19       ` [PATCH v4 2/4] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
2025-12-24 10:19       ` [PATCH v4 3/4] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
2025-12-24 10:19       ` [PATCH v4 4/4] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
2025-12-24 10:38       ` [PATCH v5 0/5] status: show default branch comparison when tracking non-default branch Harald Nordgren via GitGitGadget
2025-12-24 10:38         ` [PATCH v5 1/5] status: show comparison with upstream default branch Harald Nordgren via GitGitGadget
2025-12-24 10:38         ` [PATCH v5 2/5] Simplify default branch comparison logic Harald Nordgren via GitGitGadget
2025-12-24 10:38         ` [PATCH v5 3/5] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
2025-12-24 10:38         ` [PATCH v5 4/5] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
2025-12-24 10:38         ` [PATCH v5 5/5] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
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           ` [PATCH v6 3/6] Use repo.settings.statusGoalBranch config for status comparison Harald Nordgren via GitGitGadget
2025-12-24 23:41           ` [PATCH v6 4/6] Rename default_remote to goal_branch Harald Nordgren via GitGitGadget
2025-12-24 23:41           ` [PATCH v6 5/6] Add warning for malformed statusGoalBranch config Harald Nordgren via GitGitGadget
2025-12-24 23:41           ` [PATCH v6 6/6] Change config key to status.compareBranch 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] " Harald Nordgren
2025-12-26  1:59               ` Junio C Hamano
2025-12-26 10:58                 ` Harald Nordgren
2025-12-25  9:45           ` [PATCH v7] status: additional comparison with goal branch Harald Nordgren via GitGitGadget
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 19:37                 ` Junio C Hamano
2025-12-28 20:16                   ` Harald Nordgren
2025-12-28 23:09                     ` Ben Knoble
2025-12-29 12:17                       ` Triangular workflows Harald Nordgren
2025-12-30 16:08                   ` 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: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
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                 ` [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                   ` [PATCH v10 3/3] use pushRemote and tracking branch Harald Nordgren via GitGitGadget
2025-12-23 23:11 ` [PATCH] status: show default branch comparison when tracking non-default branch Yee Cheng Chin
2025-12-23 23:59   ` Harald Nordgren
2025-12-24  0:55     ` Yee Cheng Chin
2025-12-24  0:38   ` Junio C Hamano
2025-12-24  0:49     ` Yee Cheng Chin
2025-12-24  1:44       ` Junio C Hamano
2025-12-24 10:24         ` Harald Nordgren
2025-12-24  1:12     ` Harald Nordgren

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).