Skip to contentSkip to conversation

status: show default branch comparison when tracking non-default branch

PATCH Superseded Harald Nordgren via GitGitGadget 12 participants 259 messages Dec 23, 2025 – Mar 9 Documentation Page 1 of 2

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 status: show default branch comparison when tracking non-default branch

From: Harald Nordgren <[email protected]>

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.

---
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(+)
remote.c +101 -0
index 59b3715120..9be5e5aa22 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda

Signature · 1 line
gitgitgadget
#1 lore raw
Quoted 5 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

Quoted 3 lines

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.

Quoted 5 lines

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.

#2 lore raw
Quoted 11 lines

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.

Quoted 6 lines

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.

Quoted 10 lines

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

#3 lore raw

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

Quoted 7 lines

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

#4 lore raw
Quoted 5 lines · Harald Nordgren

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:

Quoted 4 lines
Quoted 4 lines

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

#5 lore raw
Quoted 4 lines

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	[email protected]:HaraldNordgren/brew.git (fetch)
	HaraldNordgren	[email protected]:HaraldNordgren/brew.git (push)
	origin	[email protected]:Homebrew/brew.git (fetch)
	origin	[email protected]:Homebrew/brew.git (push)

Harald

#6 lore raw
Quoted 3 lines · Harald Nordgren

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

#8 lore raw
In reply to Harald Nordgren
Quoted 9 lines · Harald Nordgren
Quoted 8 lines

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'

Quoted 5 lines

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.

#9 lore raw
Quoted 10 lines

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'?

#10 lore raw

v2 cover status: show default branch comparison when tracking non-default branch

In reply to Harald Nordgren via GitGitGadget
cc Chris Torek [email protected]

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 <[email protected]>

## 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.

@@ 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

Signature · 1 line
gitgitgadget
#11 lore raw

PATCH v2 1/2 status: show comparison with upstream default branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 | 104 +++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+)
remote.c +104 -0
index 59b3715120..b2a1e980b1 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#12 lore raw

Re: [PATCH v2 1/2] status: show comparison with upstream default branch

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2244

Quoted 5 lines · Harald Nordgren via GitGitGadget
remote.c +5 -0
+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.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#13 lore raw

Re: [PATCH v2 1/2] status: show comparison with upstream default branch

Quoted 10 lines · brian m. carlson

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2244

Quoted 5 lines · Harald Nordgren via GitGitGadget
remote.c +5 -0
+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:

Quoted 5 lines

[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.

#15 lore raw

PATCH v2 2/2 Simplify default branch comparison logic

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

This maintains the same functionality while reducing ref resolution calls from multiple to one, and eliminating unnecessary memory allocations.

---
 remote.c | 59 ++++++++++++++++++--------------------------------------
 1 file changed, 19 insertions(+), 40 deletions(-)
remote.c +19 -40
index b2a1e980b1..f3831ef3be 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#16 lore raw
In reply to Harald Nordgren via GitGitGadget

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

#17 lore raw

v3 cover status: show default branch comparison when tracking non-default branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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
Signature · 1 line
gitgitgadget
#18 lore raw

PATCH v3 1/3 status: show comparison with upstream default branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 | 104 +++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+)
remote.c +104 -0
index 59b3715120..b2a1e980b1 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#19 lore raw

PATCH v3 2/3 Simplify default branch comparison logic

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

This maintains the same functionality while reducing ref resolution calls from multiple to one, and eliminating unnecessary memory allocations.

---
 remote.c | 59 ++++++++++++++++++--------------------------------------
 1 file changed, 19 insertions(+), 40 deletions(-)
remote.c +19 -40
index b2a1e980b1..f3831ef3be 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#20 lore raw

PATCH v3 3/3 Use repo.settings.statusGoalBranch config for status comparison

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Replace hardcoded upstream/origin preference with configurable repo.settings.statusGoalBranch setting. When unset, skip the default branch comparison entirely.

---
 remote.c                 | 48 ++++++++++++++++-----------
 t/t6040-tracking-info.sh | 71 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 19 deletions(-)
remote.c +29 -19
index f3831ef3be..edb6d374e7 100644
@@ -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,
t/t6040-tracking-info.sh +71 -0

129 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#21 lore raw

v4 cover status: show default branch comparison when tracking non-default branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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
Signature · 1 line
gitgitgadget
#22 lore raw

PATCH v4 1/4 status: show comparison with upstream default branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 | 104 +++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+)
remote.c +104 -0
index 59b3715120..b2a1e980b1 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#23 lore raw

PATCH v4 2/4 Simplify default branch comparison logic

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

This maintains the same functionality while reducing ref resolution calls from multiple to one, and eliminating unnecessary memory allocations.

---
 remote.c | 59 ++++++++++++++++++--------------------------------------
 1 file changed, 19 insertions(+), 40 deletions(-)
remote.c +19 -40
index b2a1e980b1..f3831ef3be 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#24 lore raw

PATCH v4 3/4 Use repo.settings.statusGoalBranch config for status comparison

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Replace hardcoded upstream/origin preference with configurable repo.settings.statusGoalBranch setting. When unset, skip the default branch comparison entirely.

---
 remote.c                 | 48 ++++++++++++++++-----------
 t/t6040-tracking-info.sh | 71 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 19 deletions(-)
remote.c +29 -19
index f3831ef3be..edb6d374e7 100644
@@ -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,
t/t6040-tracking-info.sh +71 -0

129 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#25 lore raw

PATCH v4 4/4 Rename default_remote to goal_branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 remote.c | 63 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 31 insertions(+), 32 deletions(-)
remote.c +31 -32
index edb6d374e7..99c0e18df4 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#26 lore raw

v5 cover status: show default branch comparison when tracking non-default branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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
Signature · 1 line
gitgitgadget
#27 lore raw

PATCH v5 1/5 status: show comparison with upstream default branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 | 104 +++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+)
remote.c +104 -0
index 59b3715120..b2a1e980b1 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#28 lore raw

PATCH v5 2/5 Simplify default branch comparison logic

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

This maintains the same functionality while reducing ref resolution calls from multiple to one, and eliminating unnecessary memory allocations.

---
 remote.c | 59 ++++++++++++++++++--------------------------------------
 1 file changed, 19 insertions(+), 40 deletions(-)
remote.c +19 -40
index b2a1e980b1..f3831ef3be 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#29 lore raw

PATCH v5 3/5 Use repo.settings.statusGoalBranch config for status comparison

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Replace hardcoded upstream/origin preference with configurable repo.settings.statusGoalBranch setting. When unset, skip the default branch comparison entirely.

---
 remote.c                 | 48 ++++++++++++++++-----------
 t/t6040-tracking-info.sh | 71 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 19 deletions(-)
remote.c +29 -19
index f3831ef3be..edb6d374e7 100644
@@ -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,
t/t6040-tracking-info.sh +71 -0

129 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#30 lore raw

PATCH v5 4/5 Rename default_remote to goal_branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 remote.c | 63 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 31 insertions(+), 32 deletions(-)
remote.c +31 -32
index edb6d374e7..99c0e18df4 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#31 lore raw

PATCH v5 5/5 Add warning for malformed statusGoalBranch config

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 t/t6040-tracking-info.sh | 49 +++++++++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 13 deletions(-)
t/t6040-tracking-info.sh +36 -13
index 00dadc03e7..598dd89483 100755
@@ -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
 '
Signature · 1 line
gitgitgadget
#32 lore raw

v6 cover status: show default branch comparison when tracking non-default branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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
Signature · 1 line
gitgitgadget
#33 lore raw

PATCH v6 1/6 status: show comparison with upstream default branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 | 104 +++++++++++++++++
 t/t6040-tracking-info.sh | 246 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 350 insertions(+)
remote.c +104 -0
index 59b3715120..b2a1e980b1 100644
@@ -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;
t/t6040-tracking-info.sh +246 -0

256 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#34 lore raw

PATCH v6 2/6 Simplify default branch comparison logic

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

This maintains the same functionality while reducing ref resolution calls from multiple to one, and eliminating unnecessary memory allocations.

---
 remote.c | 59 ++++++++++++++++++--------------------------------------
 1 file changed, 19 insertions(+), 40 deletions(-)
remote.c +19 -40
index b2a1e980b1..f3831ef3be 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#35 lore raw

PATCH v6 3/6 Use repo.settings.statusGoalBranch config for status comparison

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Replace hardcoded upstream/origin preference with configurable repo.settings.statusGoalBranch setting. When unset, skip the default branch comparison entirely.

---
 remote.c                 | 48 ++++++++++++++++-----------
 t/t6040-tracking-info.sh | 71 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 19 deletions(-)
remote.c +29 -19
index f3831ef3be..edb6d374e7 100644
@@ -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,
t/t6040-tracking-info.sh +71 -0

129 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#36 lore raw

PATCH v6 4/6 Rename default_remote to goal_branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 remote.c | 63 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 31 insertions(+), 32 deletions(-)
remote.c +31 -32
index edb6d374e7..99c0e18df4 100644
@@ -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;
 }
 
Signature · 1 line
gitgitgadget
#37 lore raw

PATCH v6 5/6 Add warning for malformed statusGoalBranch config

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 t/t6040-tracking-info.sh | 49 +++++++++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 13 deletions(-)
t/t6040-tracking-info.sh +36 -13
index 00dadc03e7..598dd89483 100755
@@ -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
 '
Signature · 1 line
gitgitgadget
#38 lore raw

PATCH v6 6/6 Change config key to status.compareBranch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Update from repo.settings.statusGoalBranch to follow standard git config pattern status.compareBranch.

---
 remote.c                 |  4 ++--
 t/t6040-tracking-info.sh | 34 +++++++++++++++++-----------------
 2 files changed, 19 insertions(+), 19 deletions(-)
remote.c +2 -2
index 99c0e18df4..7e13c027b5 100644
@@ -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;
 	}
t/t6040-tracking-info.sh +17 -17
index 598dd89483..fe34ddf0ab 100755
@@ -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 &&
Signature · 1 line
gitgitgadget
#39 lore raw
In reply to Harald Nordgren via GitGitGadget
Quoted 10 lines · Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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.

#40 lore raw

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

Harald

#41 lore raw
Quoted 2 lines · Harald Nordgren

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.

#42 lore raw

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

Merry Christmas!

#43 lore raw

PATCH v7 1/1 status: additional comparison with goal branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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

---
status: show default branch comparison when tracking non-default branch

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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 <[email protected]>
      
       ## 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 <[email protected]>
      
       ## 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(+)
remote.c +92 -0
index 59b3715120..7e13c027b5 100644
@@ -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;
 }
t/t6040-tracking-info.sh +340 -0

350 lines not on this page · raw message

base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda

Signature · 1 line
gitgitgadget
#44 lore raw

PATCH v8 1/1 status: show comparison with configured goal branch

From: Harald Nordgren <[email protected]>

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

---
status: show comparison with configured goal branch

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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 <[email protected]>

## 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.

 remote.c                 |  92 +++++++++++
 t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 432 insertions(+)
remote.c +92 -0
index 59b3715120..7e13c027b5 100644
@@ -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;
 }
t/t6040-tracking-info.sh +340 -0

350 lines not on this page · raw message

base-commit: c4a0c8845e2426375ad257b6c221a3a7d92ecfda

Signature · 1 line
gitgitgadget
#45 lore raw

Code review?

Hi!

Could I get some code review on this? Maybe it's ready to be merged?

Harald

#46 lore raw

Re: Code review?

Quoted 1 line · Harald Nordgren

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.

#47 lore raw

Code review?

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

#48 lore raw

Re: Code review?

Quoted 10 lines · Harald Nordgren

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?

Quoted 9 lines

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.

#50 lore raw

Re: Code review?

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

#51 lore raw

Re: Code review?

In reply to Junio C Hamano

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

#52 lore raw

Re: [PATCH v8] status: show comparison with configured goal branch

In reply to Harald Nordgren via GitGitGadget

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 311

Quoted 4 lines · Harald Nordgren via GitGitGadget
t/t6040-tracking-info.sh +4 -0
+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?

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 314

Quoted 3 lines
t/t6040-tracking-info.sh +3 -0
+		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
	)

Review

Quoted 1 line
(quoted diff) +1 -0
+	cat >expect <<-\EOF &&

Looking up what "<<-\EOF" means, it makes little sense to have these lines ...

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 323

Quoted 8 lines
t/t6040-tracking-info.sh +8 -0
+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.

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 332

Quoted 10 lines
t/t6040-tracking-info.sh +8 -0
+	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.

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 340

Quoted 8 lines
t/t6040-tracking-info.sh +8 -0
+	) >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".

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 348

Quoted 8 lines
t/t6040-tracking-info.sh +8 -0
+	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.

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 352

Quoted 4 lines
t/t6040-tracking-info.sh +4 -0
+	) >actual &&
+	test_grep "ahead of .origin/main. by 2 commits" actual &&
+	test_grep ! "Ahead of" actual
+'

I'll stop here.

#54 lore raw

v9 cover status: show comparison with configured goal branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]

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
Signature · 1 line
gitgitgadget
#55 lore raw

PATCH v9 1/2 status: show comparison with configured goal branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 |  92 +++++++++++
 t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 432 insertions(+)
remote.c +92 -0
index 59b3715120..7e13c027b5 100644
@@ -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;
 }
t/t6040-tracking-info.sh +340 -0

350 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#56 lore raw

PATCH v9 2/2 improve tests

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Simplify tests based on feedback.

---
 t/t6040-tracking-info.sh | 249 +++++++++++++++++++--------------------
 1 file changed, 124 insertions(+), 125 deletions(-)
t/t6040-tracking-info.sh +124 -125
index fe34ddf0ab..a875b4c73b 100755
@@ -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
 '
 
Signature · 1 line
gitgitgadget
#57 lore raw

v10 cover status: show additional comparison with push branch when different from tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected]

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
Signature · 1 line
gitgitgadget
#58 lore raw

PATCH v10 1/3 status: show comparison with configured goal branch

From: Harald Nordgren <[email protected]>

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

---
 remote.c                 |  92 +++++++++++
 t/t6040-tracking-info.sh | 340 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 432 insertions(+)
remote.c +92 -0
index 59b3715120..7e13c027b5 100644
@@ -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;
 }
t/t6040-tracking-info.sh +340 -0

350 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#59 lore raw

PATCH v10 2/3 improve tests

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Simplify tests based on feedback.

---
 t/t6040-tracking-info.sh | 249 +++++++++++++++++++--------------------
 1 file changed, 124 insertions(+), 125 deletions(-)
t/t6040-tracking-info.sh +124 -125
index fe34ddf0ab..a875b4c73b 100755
@@ -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
 '
 
Signature · 1 line
gitgitgadget
#60 lore raw

PATCH v10 3/3 use pushRemote and tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Use them for comparisons instead of config variable.

---
 remote.c                 |  71 +++++-----
 t/t6040-tracking-info.sh | 296 ++++++++-------------------------------
 2 files changed, 96 insertions(+), 271 deletions(-)
remote.c +34 -37
index 7e13c027b5..2317725f7d 100644
@@ -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);
t/t6040-tracking-info.sh +62 -234

409 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#61 lore raw

Re: [PATCH v10 0/3] status: show additional comparison with push branch when different from tracking branch

In reply to Harald Nordgren via GitGitGadget
Quoted 8 lines · Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected]

Harald Nordgren (3):
status: show comparison with configured goal branch improve tests
use pushRemote and tracking branch

Again this seems to do a "step 1 goes in a direction, step 2 fixes its mistake, step 3 changes course" drunken-man's walk.

The same advice to restructure them into a logical incremental progression that moves the codebase in one consistent direction to eventually reach the goal at the end applies.

I see you are now using pushremote_for_branch() that is already used by branch_get_push(). If that gives us "the other thing" that we would want to compare, instead of adding yet another configuration variable users need to be aware of, that is really good.

#62 lore raw

Another look?

Quoted 6 lines

Again this seems to do a "step 1 goes in a direction, step 2 fixes its mistake, step 3 changes course" drunken-man's walk.

The same advice to restructure them into a logical incremental progression that moves the codebase in one consistent direction to eventually reach the goal at the end applies.

Isn't programming always bit of drunken-man's walk?

I'm very hesitant to restructure my history before I am confident I will not need any of the old work later -- I would hate to lose history if I make a mistake.

One option is to keep my code backed up on a separate branch locally, but this gets problematic as I add more work (endless cherry-picking and squashing) between local branches before submitting new patches. So now you know some of my reasoning. I'm not saying I'm right, but it's a bit fear-based.

With that said, my idea has always been to squash everything into a single commit before merging this. The whole diff is not that big. I can split it into code in one commit and tests in another.

As a side-note: In my day job we only allow "squash and merge" on our GitHub. This gives devs the flexibility to treat their branches as a WIP area before merging, but still gives a pristine git history after merge. This feels to me like a good trade-offs. But again, happy to take instructions on how to do better.

Quoted 4 lines

I see you are now using pushremote_for_branch() that is already used by branch_get_push(). If that gives us "the other thing" that we would want to compare, instead of adding yet another configuration variable users need to be aware of, that is really good.

Thanks for the encouragement! I put a lot of work into the tests as well, I hope they tell the story of what this code achieves now.

Harald

#63 lore raw

Re: Another look?

Quoted 8 lines · Harald Nordgren
Quoted 6 lines

Again this seems to do a "step 1 goes in a direction, step 2 fixes its mistake, step 3 changes course" drunken-man's walk.

The same advice to restructure them into a logical incremental progression that moves the codebase in one consistent direction to eventually reach the goal at the end applies.

Isn't programming always bit of drunken-man's walk?

We don’t have to present the chain of code changes as they “really happened”. An alternative is to present what will eventually be the final version as if you had both a borderline perfect plan, foresight, and execution. And that’s the convention in this project. Because that’s the natural progression of a patch series; each iteration you get help to arrive at what looks like the perfect iteration. (Until someone finds a off-by-one error two years later?)

What *really happened* is always fiction in any case.

Quoted 9 lines

I'm very hesitant to restructure my history before I am confident I will not need any of the old work later -- I would hate to lose history if I make a mistake.

One option is to keep my code backed up on a separate branch locally, but this gets problematic as I add more work (endless cherry-picking and squashing) between local branches before submitting new patches. So now you know some of my reasoning. I'm not saying I'm right, but it's a bit fear-based.

To make a snapshot of each version seems inevitable in my book since you are encouraged to include a range-diff (and optionally also an interdiff) between each iteration.

Now you of course have the backup of each version because you can download the patches that you yourself posted. But that’s more work then just snapshotting each version, for me at least.

Quoted 9 lines

With that said, my idea has always been to squash everything into a single commit before merging this. The whole diff is not that big. I can split it into code in one commit and tests in another.

As a side-note: In my day job we only allow "squash and merge" on our GitHub. This gives devs the flexibility to treat their branches as a WIP area before merging, but still gives a pristine git history after merge. This feels to me like a good trade-offs. But again, happy to take instructions on how to do better.

If that’s a good trade off, what are the variables involved in the trade off? So far it seems like:

1. Flexibility to iterate like you want
2. A final history without any back-and-forth noise (pristine/sober walk)

But a third variable here is

3. A series of logically separated commits

And you don’t get that with that approach. And a good final history is very important in many people’s eyes.

People call this mandatory squash strategy some word similar to *clean* presumably because there is no back-and-forth noise. That’s the memetic contagion. But there are other adjectives as well:

• Bloated: When the mandated squash strategy forces different concerns (code formatting, whitespace formatting, refactor, bug fix, ...) to be truncated into one commit
• Lossy: When so many commits get squashed that you cannot, with any amount of analysis, piece together what lines in the commit message correspond to some part of the diff (especially likely to happen if
(1) the squash commit message is a bullet list and (2) the commit messages are just things like “WIP” and “fix”)

Someone might argue that the squash merges will not be large because the pull requests are not large and the tasks are not large. Or they should not be. But that demands more of both the project/task management and the pull request management:

1. Better project management foresight and planning. Notice that we have traded the rewriting commits strategy of “perfect plan, foresight, and execution” for demanding more foresight from project management. Just because we have mandated no more than one commit per pull request or patch series.

And “perfect plan, foresight, and execution” is not even a requirement. Just a contrast to the one-commit requirement. A project could allow contributors to both present “perfect plan, foresight, and execution” series as well as messy series, with the latter being squashed at the integrator’s/reviewer’s/mantainer’s discretion.
2. Divide changes into more pull requests or patch series. At least with vanilla GitHub that causes overhead as you have to manually link all the pull requests. You might also have to manually link the pull requests using URLs if the target branch of the PR does not do it for you.

On the other hand you might have little or no overhead with some “stacked PR” tool.

The one-commit rule works just as well if not better[1] than the alternatives in theory. The problem is that the theory demands much more from project management and pull request handling.

† 1: Part of the motivation is often avoiding merge commits. And if
merge commits always cause point deductions then a perfectly executed squash merge strategy will always win over some strategy involving true merges.

Although I would personally choose a rebase-with-trailer strategy over squash merges here; rebase the series/PR and add a trailer to each commit which links to the series/PR.

Quoted 2 lines
Quoted 2 lines

[snip]

#64 lore raw

Another look?

Thanks for an interesting discussion, always nice to talk about Git strategy!

Did you have some time to also look at the code? Would be nice to move this along if possible.

Harald

#65 lore raw

Re: Another look?

Quoted 3 lines · Harald Nordgren

[snip]
Did you have some time to also look at the code? Would be nice to move this along if possible.

I’m not the right person to review this. ;)

#66 lore raw

Re: Another look?

In reply to Harald Nordgren
Quoted 8 lines · Harald Nordgren
Quoted 6 lines

Again this seems to do a "step 1 goes in a direction, step 2 fixes its mistake, step 3 changes course" drunken-man's walk.

The same advice to restructure them into a logical incremental progression that moves the codebase in one consistent direction to eventually reach the goal at the end applies.

Isn't programming always bit of drunken-man's walk?

Yes, but the point is that other people do not have to see you taking roundabout route (or for that matter, they do not have to see you coding in your bathrobe like Linus, even if it may be true you ;-).

When other developers later need to figure out what you really wanted to achieve so that they do not break your intention while they update your code to fix bugs in it and/or adding new features on top of it, it would be far easier for them to work on a clean history.

#67 lore raw

Re: Another look?

Quoted 7 lines · Junio C Hamano
Quoted 1 line · Harald Nordgren

Isn't programming always bit of drunken-man's walk?

Yes, but the point is that other people do not have to see you taking roundabout route (or for that matter, they do not have to see you coding in your bathrobe like Linus, even if it may be true you ;-).

Sun Microsystems, Inc. used a rebase workflow from 1992 to its end (RIP). Thousands of developers worked with stricly linear history (merges were not allowed!). Large team projects that had to have their own alternate repositories and do the Teamware equivalent of git rebase --onto periodically. There was no branching, only forking. Forks were kept for backporting and for history archival purposes.

It worked like a charm.

Merge workflows are -IMO- toxic and obnoxious. I do NOT want to see any programmer's "drunken-man's walk", unless it's because I'm interviewing them. There is no value to preserving that.

Quoted 5 lines

When other developers later need to figure out what you really wanted to achieve so that they do not break your intention while they update your code to fix bugs in it and/or adding new features on top of it, it would be far easier for them to work on a clean history.

Hear hear!

Strictly linear history makes it much easier to understand what's going on. The only merge turds I would allow are fast-forward ones where the purpose of the merge commit is to document push boundaries in the commit history, but even this I don't like.

Nico

#68 lore raw

Re: Another look?

Quoted 3 lines · Nico Williams

Sun Microsystems, Inc. used a rebase workflow from 1992 to its end
...
It worked like a charm.

That's good for you. But I do not necessarily think merge workflows are bad, and my advice was certainly *not* about avoiding merges.

A linear logical progression of commits that is about a single theme is much nicer than drunken-man's walk that is also a linear sequence of commits. The distinction between them has nothing to do with merges.

And a history that bundles together a collection of linear logical progressions with merges of these topics one by one is much easier than rebasing these unrelated topics into a strictly linear single strand of pearls, by making it much clear where each of these topics concludes.

#69 lore raw

Re: Another look?

In reply to Harald Nordgren
Quoted 8 lines · Harald Nordgren
Quoted 7 lines

Again this seems to do a "step 1 goes in a direction, step 2 fixes its mistake, step 3 changes course" drunken-man's walk.

The same advice to restructure them into a logical incremental progression that moves the codebase in one consistent direction to eventually reach the goal at the end applies.

Quoted 7 lines

Isn't programming always bit of drunken-man's walk?

I'm very hesitant to restructure my history before I am confident I will not need any of the old work later -- I would hate to lose history if I make a mistake.

One option is to keep my code backed up on a separate branch locally, but

Git will already do this, more or less! See "git reflog". No need to worry :)

I don't fear-driven development to lead to optimal results ;)

Quoted 5 lines

As a side-note: In my day job we only allow "squash and merge" on our GitHub. This gives devs the flexibility to treat their branches as a WIP area before merging, but still gives a pristine git history after merge. This feels to me like a good trade-offs. But again, happy to take instructions on how to do better.

I think others have covered this, but you can both "branch is WIP" and "clean history" by iterating within a PR. The GitHub UI does not make this particularly nice [1], but my recipe is essentially

1. Make changes
2. Post range-diff [2] and force-push

[1]: https://benknoble.github.io/blog/2025/03/17/more-range-diff/
[2]: https://benknoble.github.io/blog/2024/10/04/copy-range-diff/

Signature · 1 line
D. Ben Knoble
#70 lore raw

PATCH v11 1/1 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push remote's tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When a push remote is configured (via branch.<name>.pushRemote or remote.pushDefault), git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push remote's tracking branch, if it differs from the upstream tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format:
- "Ahead of 'origin/feature' by N commits" when purely ahead
- "Behind 'origin/feature' by N commits" when purely behind
- "Diverged from 'origin/feature' by N commits" when diverged

Example output when tracking upstream/main with pushRemote set to origin:
On branch feature
Your branch is ahead of 'upstream/main' by 2 commits.
(use "git pull" if you want to integrate the remote branch with yours)

Ahead of 'origin/feature' by 5 commits.

The comparison is only shown when a push remote is configured and the push remote's tracking branch differs from the upstream tracking branch.

---
status: show comparison with push remote tracking branch

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected] cc: Ben Knoble [email protected] cc:
"Kristoffer Haugsbakk" [email protected]

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

Range-diff vs v10:

1: 43a75944fb ! 1: 53bb1cb6bb status: show comparison with configured goal branch

     @@ Metadata
      Author: Harald Nordgren <[email protected]>
      
       ## Commit message ##
     -    status: show comparison with configured goal branch
     +    status: show comparison with push remote tracking branch
      
          "git status" on a branch that follows a remote branch compares
          commits on the current branch and the remote-tracking branch it
          builds upon, to show "ahead", "behind", or "diverged" status.
      
          When working on a feature branch that tracks a remote feature branch,
     -    but you also want to track progress relative to a goal branch (e.g.,
     -    upstream/main), you can configure status.goalBranch to show an
     -    additional comparison.
     +    but you also want to track progress relative to the push remote's
     +    tracking branch (which may differ from the upstream branch), git status
     +    now shows an additional comparison.
      
     -    Add support for status.goalBranch configuration option that specifies
     -    a remote/branch to compare against. When set, git status shows both
     -    the comparison with the tracking branch (as before) and an additional
     -    comparison with the configured goal branch, if it differs from the
     -    tracking branch. The goal branch comparison appears on a separate
     -    line after the tracking branch status, using the same format:
     -    - "Ahead of 'upstream/main' by N commits" when purely ahead
     -    - "Behind 'upstream/main' by N commits" when purely behind
     -    - "Diverged from 'upstream/main' by N commits" when diverged
     +    When a push remote is configured (via branch.<name>.pushRemote or
     +    remote.pushDefault), git status shows both the comparison with the
     +    upstream tracking branch (as before) and an additional comparison with
     +    the push remote's tracking branch, if it differs from the upstream
     +    tracking branch. The push branch comparison appears on a separate
     +    line after the upstream branch status, using the same format:
     +    - "Ahead of 'origin/feature' by N commits" when purely ahead
     +    - "Behind 'origin/feature' by N commits" when purely behind
     +    - "Diverged from 'origin/feature' by N commits" when diverged
      
     -    Example output when tracking a feature branch with status.goalBranch
     -    set to upstream/main:
     +    Example output when tracking upstream/main with pushRemote set to origin:
              On branch feature
     -        Your branch is ahead of 'origin/feature' by 2 commits.
     -          (use "git push" to publish your local commits)
     +        Your branch is ahead of 'upstream/main' by 2 commits.
     +          (use "git pull" if you want to integrate the remote branch with yours)
      
     -        Ahead of 'upstream/main' by 5 commits.
     +        Ahead of 'origin/feature' by 5 commits.
      
     -    The comparison is only shown when status.goalBranch is configured
     -    and the goal branch differs from the tracking branch.
     +    The comparison is only shown when a push remote is configured and the
     +    push remote's tracking branch differs from the upstream tracking branch.
      
          Signed-off-by: Harald Nordgren <[email protected]>
      
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
       }
       
     -+static char *get_goal_branch_ref(char **full_ref_out)
     ++static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
      +{
     -+	const char *config_value;
     ++	const char *push_remote;
      +	const char *resolved;
      +	int flag;
      +	struct strbuf ref_buf = STRBUF_INIT;
     -+	char *slash_pos;
      +	char *ret = NULL;
      +
     -+	if (repo_config_get_value(the_repository, "status.goalBranch", &config_value))
     ++	if (!branch)
      +		return NULL;
      +
     -+	if (!config_value || !*config_value)
     ++	push_remote = pushremote_for_branch(branch, NULL);
     ++	if (!push_remote)
      +		return NULL;
      +
     -+	slash_pos = strchr(config_value, '/');
     -+	if (!slash_pos || slash_pos == config_value || !slash_pos[1]) {
     -+		warning(_("invalid value for status.goalBranch: '%s' (expected format: remote/branch)"),
     -+			config_value);
     -+		return NULL;
     -+	}
     -+
     -+	strbuf_addf(&ref_buf, "refs/remotes/%.*s/%s",
     -+		    (int)(slash_pos - config_value), config_value,
     -+		    slash_pos + 1);
     ++	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
      +
      +	resolved = refs_resolve_ref_unsafe(
      +		get_main_ref_store(the_repository),
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +	return ret;
      +}
      +
     -+static void format_goal_branch_comparison(struct strbuf *sb,
     ++static void format_push_branch_comparison(struct strbuf *sb,
      +					     const char *branch_refname,
     -+					     const char *goal_full,
     -+					     const char *goal_short,
     ++					     const char *push_full,
     ++					     const char *push_short,
      +					     enum ahead_behind_flags abf)
      +{
     -+	int goal_ahead = 0, goal_behind = 0;
     ++	int push_ahead = 0, push_behind = 0;
     ++	int stat_result;
      +
     -+	if (stat_branch_pair(branch_refname, goal_full,
     -+			     &goal_ahead, &goal_behind, abf) <= 0)
     ++	stat_result = stat_branch_pair(branch_refname, push_full,
     ++				       &push_ahead, &push_behind, abf);
     ++	if (stat_result < 0)
      +		return;
      +
      +	strbuf_addstr(sb, "\n");
      +
     -+	if (goal_ahead > 0 && goal_behind == 0) {
     ++	if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
     ++		strbuf_addf(sb,
     ++			_("Your branch is up to date with '%s'.\n"),
     ++			push_short);
     ++	} else if (push_ahead > 0 && push_behind == 0) {
      +		strbuf_addf(sb,
      +			Q_("Ahead of '%s' by %d commit.\n",
      +			   "Ahead of '%s' by %d commits.\n",
     -+			   goal_ahead),
     -+			goal_short, goal_ahead);
     -+	} else if (goal_behind > 0 && goal_ahead == 0) {
     ++			   push_ahead),
     ++			push_short, push_ahead);
     ++	} else if (push_behind > 0 && push_ahead == 0) {
      +		strbuf_addf(sb,
      +			Q_("Behind '%s' by %d commit.\n",
      +			   "Behind '%s' by %d commits.\n",
     -+			   goal_behind),
     -+			goal_short, goal_behind);
     -+	} else if (goal_ahead > 0 && goal_behind > 0) {
     ++			   push_behind),
     ++			push_short, push_behind);
     ++	} else if (push_ahead > 0 && push_behind > 0) {
      +		strbuf_addf(sb,
      +			Q_("Diverged from '%s' by %d commit.\n",
      +			   "Diverged from '%s' by %d commits.\n",
     -+			   goal_ahead + goal_behind),
     -+			goal_short, goal_ahead + goal_behind);
     ++			   push_ahead + push_behind),
     ++			push_short, push_ahead + push_behind);
      +	}
      +}
      +
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	}
      +
      +	if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
     -+		char *goal_full = NULL;
     -+		char *goal_short = get_goal_branch_ref(&goal_full);
     ++		char *push_full = NULL;
     ++		char *push_short = get_remote_push_branch(branch, &push_full);
      +
     -+		if (goal_short && strcmp(base, goal_short))
     -+			format_goal_branch_comparison(sb, branch->refname, goal_full,
     -+						     goal_short, abf);
     ++		if (push_short && strcmp(base, push_short))
     ++			format_push_branch_comparison(sb, branch->refname, push_full,
     ++						     push_short, abf);
      +
     -+		free(goal_short);
     -+		free(goal_full);
     ++		free(push_short);
     ++		free(push_full);
      +	}
      +
       	free(base);
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       }
      
       ## t/t6040-tracking-info.sh ##
     -@@ t/t6040-tracking-info.sh: test_expect_success setup '
     - 	git clone . test &&
     - 	(
     - 		cd test &&
     -+		git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main &&
     - 		git checkout -b b1 origin &&
     - 		git reset --hard HEAD^ &&
     - 		advance d &&
      @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
       	test_cmp expect actual
       '
       
     -+test_expect_success 'setup for ahead of non-main tracking branch' '
     -+	(
     -+		cd test &&
     -+		git checkout -b feature origin/main &&
     -+		advance feature1 &&
     -+		git push origin feature &&
     -+		git checkout -b work --track origin/feature &&
     -+		advance work1 &&
     -+		advance work2
     -+	)
     -+'
     -+
     -+test_expect_success 'status shows ahead of both tracked branch and origin/main' '
     ++test_expect_success 'status tracking origin/main shows only main' '
      +	(
      +		cd test &&
     -+		git checkout work >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work
     -+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
     -+  (use "git push" to publish your local commits)
     -+
     -+Ahead of '\''origin/main'\'' by 3 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++		git checkout b4 &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch b4
     ++	Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'checkout shows ahead of both tracked branch and origin/main' '
     ++test_expect_success 'status shows ahead of both origin/main and feature branch' '
      +	(
      +		cd test &&
     -+		git checkout main >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git checkout work 2>&1
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+Switched to branch '\''work'\''
     -+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
     -+  (use "git push" to publish your local commits)
     -+
     -+Ahead of '\''origin/main'\'' by 3 commits.
     -+EOF
     ++		git checkout -b feature2 origin/main &&
     ++		git push origin HEAD &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature2
     ++	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'status tracking origin/main shows only main' '
     ++test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
      +	(
      +		cd test &&
     -+		git checkout b4 >/dev/null &&
     -+		git status --long -b
     -+	) >actual &&
     -+	test_grep "ahead of .origin/main. by 2 commits" actual &&
     -+	test_grep ! "Ahead of" actual
     ++		git checkout feature2 >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++	EOF
     ++	test_cmp expect actual
      +'
      +
      +test_expect_success 'setup for ahead of tracked but diverged from main' '
      +	(
      +		cd test &&
     ++		git checkout -b feature4 origin/main &&
     ++		advance work1 &&
      +		git checkout origin/main &&
     -+		git checkout -b oldfeature &&
     -+		advance oldfeature1 &&
     -+		git push origin oldfeature &&
     -+		git checkout origin/main &&
     -+		advance main_newer &&
     ++		advance work2 &&
      +		git push origin HEAD:main &&
     -+		git checkout -b work2 --track origin/oldfeature &&
     -+		advance work2_commit
     -+	)
     -+'
     -+
     -+test_expect_success 'status shows ahead of tracked and diverged from origin/main' '
     -+	(
     -+		cd test &&
     -+		git checkout work2 >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work2
     -+Your branch is ahead of '\''origin/oldfeature'\'' by 1 commit.
     -+  (use "git push" to publish your local commits)
     -+
     -+Diverged from '\''origin/main'\'' by 3 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'setup for diverged from tracked but behind main' '
     -+	(
     -+		cd test &&
     -+		git fetch origin &&
     -+		git checkout origin/main &&
     -+		git checkout -b work2b &&
     -+		git branch --set-upstream-to=origin/oldfeature &&
     -+		git checkout origin/main &&
     -+		advance main_extra &&
     -+		git push origin HEAD:main
     -+	)
     -+'
     -+
     -+test_expect_success 'status shows diverged from tracked and behind origin/main' '
     -+	(
     -+		cd test &&
     -+		git checkout work2b >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work2b
     -+Your branch and '\''origin/oldfeature'\'' have diverged,
     -+and have 1 and 1 different commits each, respectively.
     -+  (use "git pull" if you want to integrate the remote branch with yours)
     -+
     -+Behind '\''origin/main'\'' by 1 commit.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'setup for behind tracked but ahead of main' '
     -+	(
     -+		cd test &&
     -+		git fetch origin &&
     -+		git checkout origin/main &&
     -+		git checkout -b feature3 &&
     -+		advance feature3_1 &&
     -+		advance feature3_2 &&
     -+		advance feature3_3 &&
     -+		git push origin feature3 &&
     -+		git checkout -b work3 --track origin/feature3 &&
     -+		git reset --hard HEAD~2
     ++		git checkout feature4 &&
     ++		advance work3
      +	)
      +'
      +
     -+test_expect_success 'status shows behind tracked and ahead of origin/main' '
     ++test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
      +	(
      +		cd test &&
     -+		git checkout work3 >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work3
     -+Your branch is behind '\''origin/feature3'\'' by 2 commits, and can be fast-forwarded.
     -+  (use "git pull" to update your local branch)
     -+
     -+Ahead of '\''origin/main'\'' by 1 commit.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++		git checkout feature4 &&
     ++		git branch --set-upstream-to origin/main &&
     ++		git push origin HEAD &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature4
     ++	Your branch and ${SQ}origin/main${SQ} have diverged,
     ++	and have 3 and 1 different commits each, respectively.
     ++	  (use "git pull" if you want to integrate the remote branch with yours)
     ++
     ++	Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'setup upstream remote preference' '
     ++test_expect_success 'setup upstream remote' '
      +	(
      +		cd test &&
      +		git remote add upstream ../. &&
      +		git fetch upstream &&
     -+		git symbolic-ref refs/remotes/upstream/HEAD refs/remotes/upstream/main
     ++		git config remote.pushDefault origin
      +	)
      +'
      +
     -+test_expect_success 'status prefers upstream remote over origin for comparison' '
     ++test_expect_success 'status with upstream remote and push.default set to origin' '
      +	(
      +		cd test &&
     -+		git checkout work >/dev/null &&
     -+		git config status.goalBranch upstream/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work
     -+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
     -+  (use "git push" to publish your local commits)
     -+
     -+Diverged from '\''upstream/main'\'' by 5 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++		git checkout -b feature5 upstream/main &&
     ++		git push origin &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature5
     ++	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'setup for up to date with tracked but ahead of default' '
     -+	(
     -+		cd test &&
     -+		git checkout origin/feature &&
     -+		git checkout -b synced_feature --track origin/feature &&
     -+		git checkout origin/main &&
     -+		advance main_ahead &&
     -+		git push origin HEAD:main
     -+	)
     -+'
     -+
     -+test_expect_success 'status shows up to date with tracked but diverged from default' '
     ++test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
      +	(
      +		cd test &&
     -+		git checkout synced_feature >/dev/null &&
     -+		git config status.goalBranch upstream/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+Diverged from '\''upstream/main'\'' by 3 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++		git checkout -b feature6 upstream/main &&
     ++		advance work &&
     ++		git push origin &&
     ++		git reset --hard upstream/main &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature6
     ++	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'setup for up to date with tracked but ahead of origin/main' '
     ++test_expect_success 'status with upstream remote and push branch up to date' '
      +	(
      +		cd test &&
     -+		git remote remove upstream &&
     -+		git checkout origin/feature &&
     -+		git checkout -b synced_feature2 --track origin/feature &&
     -+		git checkout origin/main &&
     -+		advance main_ahead2 &&
     -+		git push origin HEAD:main
     -+	)
     -+'
     -+
     -+test_expect_success 'status shows up to date with tracked but diverged from origin/main' '
     -+	(
     -+		cd test &&
     -+		git checkout synced_feature2 >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature2
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+Diverged from '\''origin/main'\'' by 5 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++		git checkout -b feature7 upstream/main &&
     ++		git push origin &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature7
     ++	Your branch is up to date with ${SQ}upstream/main${SQ}.
     ++
     ++	Your branch is up to date with ${SQ}origin/feature7${SQ}.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success 'setup for up to date with tracked but purely ahead of origin/main' '
     ++test_expect_success 'checkout shows push branch up to date' '
      +	(
      +		cd test &&
     -+		git checkout origin/feature &&
     -+		git checkout -b synced_feature3 --track origin/feature
     -+	)
     -+'
     ++		git checkout feature7 >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	Your branch is up to date with ${SQ}upstream/main${SQ}.
      +
     -+test_expect_success 'status shows up to date with tracked but shows default branch comparison' '
     -+	(
     -+		cd test &&
     -+		git checkout synced_feature3 >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature3
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+Diverged from '\''origin/main'\'' by 5 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'status with status.goalBranch unset shows no default comparison' '
     -+	(
     -+		cd test &&
     -+		git checkout synced_feature3 >/dev/null &&
     -+		git config --unset status.goalBranch 2>/dev/null || true &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature3
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'status with status.goalBranch set uses configured branch' '
     -+	(
     -+		cd test &&
     -+		git checkout synced_feature3 >/dev/null &&
     -+		git config status.goalBranch origin/main &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature3
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+Diverged from '\''origin/main'\'' by 5 commits.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'status with status.goalBranch set to different remote/branch' '
     -+	(
     -+		cd test &&
     -+		git checkout work >/dev/null &&
     -+		git config status.goalBranch origin/feature &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch work
     -+Your branch is ahead of '\''origin/feature'\'' by 2 commits.
     -+  (use "git push" to publish your local commits)
     -+
     -+nothing to commit, working tree clean
     -+EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success 'status with status.goalBranch set to non-existent branch' '
     -+	(
     -+		cd test &&
     -+		git checkout synced_feature3 >/dev/null &&
     -+		git config status.goalBranch origin/nonexistent &&
     -+		git status --long -b
     -+	) >actual &&
     -+	cat >expect <<-\EOF &&
     -+On branch synced_feature3
     -+Your branch is up to date with '\''origin/feature'\''.
     -+
     -+nothing to commit, working tree clean
     -+EOF
     ++	Your branch is up to date with ${SQ}origin/feature7${SQ}.
     ++	EOF
      +	test_cmp expect actual
      +'
      +
 2:  e6d24b8b6a < -:  ---------- improve tests
 3:  13c2a03b0a < -:  ---------- use pushRemote and tracking branch
 remote.c                 |  89 +++++++++++++++++++++
 t/t6040-tracking-info.sh | 167 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 256 insertions(+)
remote.c +89 -0
index 59b3715120..2317725f7d 100644
@@ -2237,6 +2237,81 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	const char *push_remote;
+	const char *resolved;
+	int flag;
+	struct strbuf ref_buf = STRBUF_INIT;
+	char *ret = NULL;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		ref_buf.buf,
+		RESOLVE_REF_READING,
+		NULL, &flag);
+
+	if (resolved) {
+		if (full_ref_out)
+			*full_ref_out = xstrdup(resolved);
+		ret = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), resolved, 0);
+	}
+
+	strbuf_release(&ref_buf);
+	return ret;
+}
+
+static void format_push_branch_comparison(struct strbuf *sb,
+					     const char *branch_refname,
+					     const char *push_full,
+					     const char *push_short,
+					     enum ahead_behind_flags abf)
+{
+	int push_ahead = 0, push_behind = 0;
+	int stat_result;
+
+	stat_result = stat_branch_pair(branch_refname, push_full,
+				       &push_ahead, &push_behind, abf);
+	if (stat_result < 0)
+		return;
+
+	strbuf_addstr(sb, "\n");
+
+	if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			push_short);
+	} else if (push_ahead > 0 && push_behind == 0) {
+		strbuf_addf(sb,
+			Q_("Ahead of '%s' by %d commit.\n",
+			   "Ahead of '%s' by %d commits.\n",
+			   push_ahead),
+			push_short, push_ahead);
+	} else if (push_behind > 0 && push_ahead == 0) {
+		strbuf_addf(sb,
+			Q_("Behind '%s' by %d commit.\n",
+			   "Behind '%s' by %d commits.\n",
+			   push_behind),
+			push_short, push_behind);
+	} else if (push_ahead > 0 && push_behind > 0) {
+		strbuf_addf(sb,
+			Q_("Diverged from '%s' by %d commit.\n",
+			   "Diverged from '%s' by %d commits.\n",
+			   push_ahead + push_behind),
+			push_short, push_ahead + push_behind);
+	}
+}
+
 /*
  * Return true when there is anything to report, otherwise false.
  */
@@ -2258,6 +2333,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2311,6 +2387,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+
+	if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
+		char *push_full = NULL;
+		char *push_short = get_remote_push_branch(branch, &push_full);
+
+		if (push_short && strcmp(base, push_short))
+			format_push_branch_comparison(sb, branch->refname, push_full,
+						     push_short, abf);
+
+		free(push_short);
+		free(push_full);
+	}
+
 	free(base);
 	return 1;
 }
t/t6040-tracking-info.sh +167 -0

171 lines not on this page · raw message

base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe

Signature · 1 line
gitgitgadget
#71 lore raw

Re: [PATCH v11] status: show comparison with push remote tracking branch

Hi Harald

Quoted 10 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push remote's tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

This is great and it is really good that it is now using the default push destination rather than a custom config key.

Quoted 5 lines

When a push remote is configured (via branch.<name>.pushRemote or remote.pushDefault) git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push remote's tracking branch, if it differs from the upstream tracking branch.

Looking at the tests, it seems that the extra information is shown whenever the upstream branch differs from the default push destination even if they are on the same remote. I think that is sensible but this paragraph should be updated to reflect the fact that one does not need to set either of the "pushDefault" config settings to benefit from this.

Quoted 5 lines

The push branch comparison appears on a separate line after the upstream branch status, using the same format:
- "Ahead of 'origin/feature' by N commits" when purely ahead
- "Behind 'origin/feature' by N commits" when purely behind
- "Diverged from 'origin/feature' by N commits" when diverged

I'm wondering why we don't reuse the existing messages - I don't see how using a different wording for the push destination compared to the upstream branch benefits the user. We should adjust the hints that are shown so that we only recommend pulling from the upstream branch and only recommend pushing to the default push destination but the comparisons should be the same. Also I don't find the message "Diverged from 'origin/feature' by N commits' very helpful, I'd find it more useful if it gave the ahead/behind count like we do for the upstream branch.

Quoted 4 lines

Example output when tracking upstream/main with pushRemote set to origin:
On branch feature
Your branch is ahead of 'upstream/main' by 2 commits.
(use "git pull" if you want to integrate the remote branch with yours)

Is this a typo? - why are we recommending "git pull" when our branch is ahead of the upstream branch?

Quoted 1 line

Ahead of 'origin/feature' by 5 commits.

It would be nice to have a hint suggesting the user runs "git push" here.

Quoted 2 lines

The comparison is only shown when a push remote is configured and the push remote's tracking branch differs from the upstream tracking branch.

Looking at the tests, only the second half of that sentence appears to be true.

Quoted 14 lines
remote.c +6 -0
index 59b3715120..2317725f7d 100644
@@ -2237,6 +2237,81 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
  	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
  }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	const char *push_remote;
+	const char *resolved;
+	int flag;
+	struct strbuf ref_buf = STRBUF_INIT;

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2255

Quoted 10 lines
remote.c +10 -0
+	char *ret = NULL;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);

Shouldn't we be taking account of the push and fetch refspecs here? There is no guarantee that $branch maps to refs/remotes/$remote/$branch. To take a silly example, if we have

remote.$remote.fetch =
refs/heads/$branch:refs/remotes/$remote/abc-$branch
remote.$remote.pull = refs/heads/$branch:refs/heads/xyz-$branch

Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the message above as "$branch" will be pushed to "xyz-$branch" on the remote which is fetched to "$remote/abc-xyz-$branch"

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2261

Quoted 6 lines
remote.c +6 -0
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		ref_buf.buf,
+		RESOLVE_REF_READING,
+		NULL, &flag);

As we don't use flag we can pass NULL - see the documentation for this function in refs.h

Quoted 5 lines
remote.c +5 -0
+static void format_push_branch_comparison(struct strbuf *sb,
+					     const char *branch_refname,
+					     const char *push_full,
+					     const char *push_short,
+					     enum ahead_behind_flags abf)

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2288

Quoted 10 lines
remote.c +10 -0
+{
+	int push_ahead = 0, push_behind = 0;
+	int stat_result;
+
+	stat_result = stat_branch_pair(branch_refname, push_full,
+				       &push_ahead, &push_behind, abf);
+	if (stat_result < 0)
+		return;
+
+	strbuf_addstr(sb, "\n");

As I said above it would be nice if we could use the existing messages here. Can we have a separate preparatory patch that refactors fromat_tracking_info() so that we can use the same messages for the upstream branch and the default push destination?

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2391

Quoted 6 lines
remote.c +2 -0
@@ -2311,6 +2387,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  			strbuf_addstr(sb,
  				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
  	}
+
+	if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {

Why do we handle AHEAD_BEHIND_QUICK differently to the upstream branch? Surely it would be useful to tell the user whether the branch is up to date with the default push destination or not?

Quoted 7 lines
t/t6040-tracking-info.sh +2 -0
index 0b719bbae6..f27ae719ad 100755
[...]
+test_expect_success 'status shows ahead of both origin/main and feature branch' '
+	(

t/t6040-tracking-info.sh test_expect_success '--set-upstream-to @{-1}' ' · line 322

Quoted 10 lines
t/t6040-tracking-info.sh +10 -0
+		cd test &&
+		git checkout -b feature2 origin/main &&
+		git push origin HEAD &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature2
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)

Why are we still suggesting pushing to the upstream branch when we know the user is pushing to a different remote branch?

t/t6040-tracking-info.sh

Quoted 1 line
t/t6040-tracking-info.sh +1 -0
+	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.

Why aren't we suggesting to use "git push" here?

Thanks for working on this. With a few tweaks it will be a really useful improvement to "git status"

Phillip

Quoted 139 lines
t/t6040-tracking-info.sh +136 -0
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
+	(
+		cd test &&
+		git checkout feature2 >../actual
+	) &&
+	cat >expect <<-EOF &&
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+	(
+		cd test &&
+		git checkout -b feature4 origin/main &&
+		advance work1 &&
+		git checkout origin/main &&
+		advance work2 &&
+		git push origin HEAD:main &&
+		git checkout feature4 &&
+		advance work3
+	)
+'
+
+test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
+	(
+		cd test &&
+		git checkout feature4 &&
+		git branch --set-upstream-to origin/main &&
+		git push origin HEAD &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature4
+	Your branch and ${SQ}origin/main${SQ} have diverged,
+	and have 3 and 1 different commits each, respectively.
+	  (use "git pull" if you want to integrate the remote branch with yours)
+
+	Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote' '
+	(
+		cd test &&
+		git remote add upstream ../. &&
+		git fetch upstream &&
+		git config remote.pushDefault origin
+	)
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin' '
+	(
+		cd test &&
+		git checkout -b feature5 upstream/main &&
+		git push origin &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature5
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
+	(
+		cd test &&
+		git checkout -b feature6 upstream/main &&
+		advance work &&
+		git push origin &&
+		git reset --hard upstream/main &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature6
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push branch up to date' '
+	(
+		cd test &&
+		git checkout -b feature7 upstream/main &&
+		git push origin &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature7
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature7${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout shows push branch up to date' '
+	(
+		cd test &&
+		git checkout feature7 >../actual
+	) &&
+	cat >expect <<-EOF &&
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature7${SQ}.
+	EOF
+	test_cmp expect actual
+'
+
  test_done

base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe

#72 lore raw

Another look?

Quoted 5 lines

Looking at the tests, it seems that the extra information is shown whenever the upstream branch differs from the default push destination even if they are on the same remote. I think that is sensible but this paragraph should be updated to reflect the fact that one does not need to set either of the "pushDefault" config settings to benefit from this.

Good catch, will be included in the next diff!

Quoted 8 lines

I'm wondering why we don't reuse the existing messages - I don't see how using a different wording for the push destination compared to the upstream branch benefits the user. We should adjust the hints that are shown so that we only recommend pulling from the upstream branch and only recommend pushing to the default push destination but the comparisons should be the same. Also I don't find the message "Diverged from 'origin/feature' by N commits' very helpful, I'd find it more useful if it gave the ahead/behind count like we do for the upstream branch.

Thanks for saying this, I actually did the majority of this work already but was hesitant to include it in the diff because it increases the surface area. But I will include it now!

Quoted 11 lines

Shouldn't we be taking account of the push and fetch refspecs here? There is no guarantee that $branch maps to refs/remotes/$remote/$branch. To take a silly example, if we have

remote.$remote.fetch =
refs/heads/$branch:refs/remotes/$remote/abc-$branch remote.$remote.pull = refs/heads/$branch:refs/heads/xyz-$branch

Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the message above as "$branch" will be pushed to "xyz-$branch" on the remote which is fetched to "$remote/abc-xyz-$branch"

I don't understand this one, can you maybe explain how to code will need to change?

Quoted 2 lines

As we don't use flag we can pass NULL - see the documentation for this function in refs.h

Thanks, will be included in the next diff!

Quoted 1 line

Why are we still suggesting pushing to the upstream branch when we know

the user is pushing to a different remote branch?

Thanks, will be included in the next diff!

Harald

#73 lore raw

Re: Another look?

Quoted 9 lines · Harald Nordgren
Quoted 9 lines

Shouldn't we be taking account of the push and fetch refspecs here? There is no guarantee that $branch maps to refs/remotes/$remote/$branch. To take a silly example, if we have

remote.$remote.fetch =
refs/heads/$branch:refs/remotes/$remote/abc-$branch remote.$remote.pull = refs/heads/$branch:refs/heads/xyz-$branch

Quoted 6 lines
Quoted 3 lines

Then we should be using "refs/remotes/$remote/abc-xyz-$branch" in the message above as "$branch" will be pushed to "xyz-$branch" on the remote which is fetched to "$remote/abc-xyz-$branch"

I don't understand this one, can you maybe explain how to code will need to change?

You want to display the remote tracking ref that tracks ref on the remote that we push to. When git pushes $branch to $remote it checks if any of the push refspecs remote.$remote.push match $branch. If there is a match then the branch name is mapped according to the refspec and that it the ref that is updated in the remote repository. It then takes that ref and checks the fetch refspecs remote.$remote.fetch and if it finds a match it maps the ref we've pushed to a remote tracking ref in the local repository.

It looks like you can find where we would push a branch to with remote_ref_for_branch() and you can map that to a remote tracking ref with tracking_for_push_dest(). We could use branch_get_push() but does more than just map the refs as it checks push.default to see whether "git push" would actually push the branch.

Thanks

Phillip

#74 lore raw

Another look?

Quoted 9 lines

You want to display the remote tracking ref that tracks ref on the remote that we push to. When git pushes $branch to $remote it checks if any of the push refspecs remote.$remote.push match $branch. If there is a match then the branch name is mapped according to the refspec and that it the ref that is updated in the remote repository. It then takes that ref and checks the fetch refspecs remote.$remote.fetch and if it finds a match it maps the ref we've pushed to a remote tracking ref in the local repository.

Quoted 5 lines

It looks like you can find where we would push a branch to with remote_ref_for_branch() and you can map that to a remote tracking ref with tracking_for_push_dest(). We could use branch_get_push() but does more than just map the refs as it checks push.default to see whether "git push" would actually push the branch.

Thanks! I have updated the code and added a test for remapped refspec.

With that said, I don't 100% understand what I'm doing with the refspecs, so maybe you can give it another review?

Harald

#75 lore raw

v12 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]

Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 164 +++++++++++++++++++++++++++++---------
 t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 295 insertions(+), 37 deletions(-)

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

Range-diff vs v11:

 -:  ---------- > 1:  a2c160c53e refactor: format_branch_comparison in preparation
 1:  53bb1cb6bb ! 2:  a586038d1f status: show comparison with push remote tracking branch
     @@ Commit message
          builds upon, to show "ahead", "behind", or "diverged" status.
      
          When working on a feature branch that tracks a remote feature branch,
     -    but you also want to track progress relative to the push remote's
     +    but you also want to track progress relative to the push destination
          tracking branch (which may differ from the upstream branch), git status
          now shows an additional comparison.
      
     -    When a push remote is configured (via branch.<name>.pushRemote or
     -    remote.pushDefault), git status shows both the comparison with the
     -    upstream tracking branch (as before) and an additional comparison with
     -    the push remote's tracking branch, if it differs from the upstream
     -    tracking branch. The push branch comparison appears on a separate
     -    line after the upstream branch status, using the same format:
     -    - "Ahead of 'origin/feature' by N commits" when purely ahead
     -    - "Behind 'origin/feature' by N commits" when purely behind
     -    - "Diverged from 'origin/feature' by N commits" when diverged
     +    When the upstream tracking branch differs from the push destination
     +    tracking branch, git status shows both the comparison with the upstream
     +    tracking branch (as before) and an additional comparison with the push
     +    destination tracking branch. The push branch comparison appears on a
     +    separate line after the upstream branch status, using the same format.
      
     -    Example output when tracking upstream/main with pushRemote set to origin:
     +    Example output when tracking origin/main but push destination is
     +    origin/feature:
              On branch feature
     -        Your branch is ahead of 'upstream/main' by 2 commits.
     +        Your branch and 'origin/main' have diverged,
     +        and have 3 and 1 different commits each, respectively.
                (use "git pull" if you want to integrate the remote branch with yours)
      
     -        Ahead of 'origin/feature' by 5 commits.
     +        Your branch is ahead of 'origin/feature' by 1 commit.
     +          (use "git push" to publish your local commits)
      
     -    The comparison is only shown when a push remote is configured and the
     -    push remote's tracking branch differs from the upstream tracking branch.
     +    The comparison is only shown when the push destination tracking branch
     +    differs from the upstream tracking branch, even if they are on the same
     +    remote.
      
          Signed-off-by: Harald Nordgren <[email protected]>
      
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +{
      +	const char *push_remote;
      +	const char *resolved;
     -+	int flag;
      +	struct strbuf ref_buf = STRBUF_INIT;
      +	char *ret = NULL;
      +
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +		get_main_ref_store(the_repository),
      +		ref_buf.buf,
      +		RESOLVE_REF_READING,
     -+		NULL, &flag);
     ++		NULL, NULL);
      +
      +	if (resolved) {
      +		if (full_ref_out)
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +	return ret;
      +}
      +
     -+static void format_push_branch_comparison(struct strbuf *sb,
     -+					     const char *branch_refname,
     -+					     const char *push_full,
     -+					     const char *push_short,
     -+					     enum ahead_behind_flags abf)
     -+{
     -+	int push_ahead = 0, push_behind = 0;
     -+	int stat_result;
     -+
     -+	stat_result = stat_branch_pair(branch_refname, push_full,
     -+				       &push_ahead, &push_behind, abf);
     -+	if (stat_result < 0)
     -+		return;
     -+
     -+	strbuf_addstr(sb, "\n");
     -+
     -+	if (stat_result == 0 || (push_ahead == 0 && push_behind == 0)) {
     -+		strbuf_addf(sb,
     -+			_("Your branch is up to date with '%s'.\n"),
     -+			push_short);
     -+	} else if (push_ahead > 0 && push_behind == 0) {
     -+		strbuf_addf(sb,
     -+			Q_("Ahead of '%s' by %d commit.\n",
     -+			   "Ahead of '%s' by %d commits.\n",
     -+			   push_ahead),
     -+			push_short, push_ahead);
     -+	} else if (push_behind > 0 && push_ahead == 0) {
     -+		strbuf_addf(sb,
     -+			Q_("Behind '%s' by %d commit.\n",
     -+			   "Behind '%s' by %d commits.\n",
     -+			   push_behind),
     -+			push_short, push_behind);
     -+	} else if (push_ahead > 0 && push_behind > 0) {
     -+		strbuf_addf(sb,
     -+			Q_("Diverged from '%s' by %d commit.\n",
     -+			   "Diverged from '%s' by %d commits.\n",
     -+			   push_ahead + push_behind),
     -+			push_short, push_ahead + push_behind);
     -+	}
     -+}
     -+
     - /*
     -  * Return true when there is anything to report, otherwise false.
     -  */
     + static void format_branch_comparison(struct strbuf *sb,
     + 				     int ahead, int behind,
     + 				     const char *branch_name,
      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
     + 	const char *full_base;
     + 	char *base;
     + 	int upstream_is_gone = 0;
     ++	int push_ours = 0, push_theirs = 0;
     ++	int push_stat_result = -1;
     ++	int will_show_push_comparison = 0;
       
     + 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
     + 	if (sti < 0) {
     +@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
       					    full_base, 0);
     + 
     ++	if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
     ++		char *push_full = NULL;
     ++		char *push_short = get_remote_push_branch(branch, &push_full);
      +
     - 	if (upstream_is_gone) {
     - 		strbuf_addf(sb,
     - 			_("Your branch is based on '%s', but the upstream is gone.\n"),
     -@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
     ++		if (push_short && strcmp(base, push_short)) {
     ++			push_stat_result = stat_branch_pair(branch->refname, push_full,
     ++							    &push_ours, &push_theirs, abf);
     ++			if (push_stat_result >= 0)
     ++				will_show_push_comparison = 1;
     ++		}
     ++
     ++		free(push_short);
     ++		free(push_full);
     ++	}
     ++
     + 	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
     + 	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
     +-		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
     ++		if (!theirs && !will_show_push_comparison &&
     ++		    advice_enabled(ADVICE_STATUS_HINTS)) {
       			strbuf_addstr(sb,
     - 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
     + 				_("  (use \"git push\" to publish your local commits)\n"));
     + 		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
     +@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
     + 		}
       	}
     -+
     -+	if (!upstream_is_gone && sti >= 0 && abf != AHEAD_BEHIND_QUICK) {
     + 
     ++	if (will_show_push_comparison) {
      +		char *push_full = NULL;
      +		char *push_short = get_remote_push_branch(branch, &push_full);
      +
     -+		if (push_short && strcmp(base, push_short))
     -+			format_push_branch_comparison(sb, branch->refname, push_full,
     -+						     push_short, abf);
     ++		if (push_short && strcmp(base, push_short)) {
     ++			strbuf_addstr(sb, "\n");
     ++			format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
     ++						push_ours || push_theirs);
     ++			if (push_ours > 0 && push_theirs == 0 &&
     ++			    advice_enabled(ADVICE_STATUS_HINTS)) {
     ++				strbuf_addstr(sb,
     ++					_("  (use \"git push\" to publish your local commits)\n"));
     ++			}
     ++		}
      +
      +		free(push_short);
      +		free(push_full);
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	cat >expect <<-EOF &&
      +	On branch feature2
      +	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
     -+	  (use "git push" to publish your local commits)
      +
     -+	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++	Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
      +
      +	nothing to commit, working tree clean
      +	EOF
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	) &&
      +	cat >expect <<-EOF &&
      +	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
     -+	  (use "git push" to publish your local commits)
      +
     -+	Ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++	Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
      +	EOF
      +	test_cmp expect actual
      +'
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	and have 3 and 1 different commits each, respectively.
      +	  (use "git pull" if you want to integrate the remote branch with yours)
      +
     -+	Ahead of ${SQ}origin/feature4${SQ} by 1 commit.
     ++	Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
      +
      +	nothing to commit, working tree clean
      +	EOF
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	cat >expect <<-EOF &&
      +	On branch feature5
      +	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
     -+	  (use "git push" to publish your local commits)
      +
     -+	Ahead of ${SQ}origin/feature5${SQ} by 1 commit.
     ++	Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
      +
      +	nothing to commit, working tree clean
      +	EOF
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	cat >expect <<-EOF &&
      +	On branch feature6
      +	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
     -+	  (use "git push" to publish your local commits)
      +
     -+	Diverged from ${SQ}origin/feature6${SQ} by 2 commits.
     ++	Your branch and ${SQ}origin/feature6${SQ} have diverged,
     ++	and have 1 and 1 different commits each, respectively.
      +
      +	nothing to commit, working tree clean
      +	EOF
Signature · 1 line
gitgitgadget
#76 lore raw

PATCH v12 1/2 refactor: format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 59 insertions(+), 40 deletions(-)
remote.c +59 -40
index 59b3715120..58093f64b0 100644
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ahead, int behind,
+				     const char *branch_name,
+				     int upstream_is_gone,
+				     enum ahead_behind_flags abf,
+				     int sti)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
+			branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else if (!sti) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
-	} else if (!theirs) {
+	} else if (ahead == 0 && behind == 0) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
+	} else if (ahead > 0 && behind == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
-			   ours),
-			base, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git push\" to publish your local commits)\n"));
-	} else if (!ours) {
+			   ahead),
+			branch_name, ahead);
+	} else if (behind > 0 && ahead == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is behind '%s' by %d commit, "
 			       "and can be fast-forwarded.\n",
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
-			   theirs),
-			base, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" to update your local branch)\n"));
-	} else {
+			   behind),
+			branch_name, behind);
+	} else if (ahead > 0 && behind > 0) {
 		strbuf_addf(sb,
 			Q_("Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
-			   ours + theirs),
-			base, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+			   ahead + behind),
+			branch_name, ahead, behind);
+	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git push\" to publish your local commits)\n"));
+		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git pull\" to update your local branch)\n"));
+		} else if (ours && theirs && show_divergence_advice &&
+			   advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		}
 	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#77 lore raw

PATCH v12 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  73 ++++++++++++++++-
 t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 240 insertions(+), 1 deletion(-)
remote.c +72 -1
index 58093f64b0..1663bd9236 100644
@@ -2237,6 +2237,39 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	const char *push_remote;
+	const char *resolved;
+	struct strbuf ref_buf = STRBUF_INIT;
+	char *ret = NULL;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		ref_buf.buf,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (resolved) {
+		if (full_ref_out)
+			*full_ref_out = xstrdup(resolved);
+		ret = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), resolved, 0);
+	}
+
+	strbuf_release(&ref_buf);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int ahead, int behind,
 				     const char *branch_name,
@@ -2304,6 +2337,9 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	int push_ours = 0, push_theirs = 0;
+	int push_stat_result = -1;
+	int will_show_push_comparison = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2315,9 +2351,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
+		char *push_full = NULL;
+		char *push_short = get_remote_push_branch(branch, &push_full);
+
+		if (push_short && strcmp(base, push_short)) {
+			push_stat_result = stat_branch_pair(branch->refname, push_full,
+							    &push_ours, &push_theirs, abf);
+			if (push_stat_result >= 0)
+				will_show_push_comparison = 1;
+		}
+
+		free(push_short);
+		free(push_full);
+	}
+
 	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
 	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+		if (!theirs && !will_show_push_comparison &&
+		    advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,6 +2382,25 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		}
 	}
 
+	if (will_show_push_comparison) {
+		char *push_full = NULL;
+		char *push_short = get_remote_push_branch(branch, &push_full);
+
+		if (push_short && strcmp(base, push_short)) {
+			strbuf_addstr(sb, "\n");
+			format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
+						push_ours || push_theirs);
+			if (push_ours > 0 && push_theirs == 0 &&
+			    advice_enabled(ADVICE_STATUS_HINTS)) {
+				strbuf_addstr(sb,
+					_("  (use \"git push\" to publish your local commits)\n"));
+			}
+		}
+
+		free(push_short);
+		free(push_full);
+	}
+
 	free(base);
 	return 1;
 }
t/t6040-tracking-info.sh +168 -0

172 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#78 lore raw

v13 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]

Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 152 ++++++++++++++++++++++++++---------
 t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 283 insertions(+), 37 deletions(-)

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

Range-diff vs v12:

 1:  a2c160c53e = 1:  a2c160c53e refactor: format_branch_comparison in preparation
 2:  a586038d1f ! 2:  891239211e status: show comparison with push remote tracking branch
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	const char *full_base;
       	char *base;
       	int upstream_is_gone = 0;
     -+	int push_ours = 0, push_theirs = 0;
     -+	int push_stat_result = -1;
     -+	int will_show_push_comparison = 0;
     ++	int push_ours, push_theirs, push_sti;
     ++	char *full_push = NULL;
     ++	char *push = NULL;
     ++	int show_push_comparison = 0;
       
       	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
       	if (sti < 0) {
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
       					    full_base, 0);
       
     -+	if (!upstream_is_gone && abf != AHEAD_BEHIND_QUICK) {
     -+		char *push_full = NULL;
     -+		char *push_short = get_remote_push_branch(branch, &push_full);
     -+
     -+		if (push_short && strcmp(base, push_short)) {
     -+			push_stat_result = stat_branch_pair(branch->refname, push_full,
     -+							    &push_ours, &push_theirs, abf);
     -+			if (push_stat_result >= 0)
     -+				will_show_push_comparison = 1;
     -+		}
     -+
     -+		free(push_short);
     -+		free(push_full);
     ++	push = get_remote_push_branch(branch, &full_push);
     ++	if (push && strcmp(base, push)) {
     ++		push_sti = stat_branch_pair(branch->refname, full_push,
     ++					   &push_ours, &push_theirs, abf);
     ++		if (push_sti >= 0)
     ++			show_push_comparison = 1;
      +	}
      +
       	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
       	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
      -		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
     -+		if (!theirs && !will_show_push_comparison &&
     ++		if (!theirs && !show_push_comparison &&
      +		    advice_enabled(ADVICE_STATUS_HINTS)) {
       			strbuf_addstr(sb,
       				_("  (use \"git push\" to publish your local commits)\n"));
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       		}
       	}
       
     -+	if (will_show_push_comparison) {
     -+		char *push_full = NULL;
     -+		char *push_short = get_remote_push_branch(branch, &push_full);
     -+
     -+		if (push_short && strcmp(base, push_short)) {
     -+			strbuf_addstr(sb, "\n");
     -+			format_branch_comparison(sb, push_ours, push_theirs, push_short, 0, abf,
     -+						push_ours || push_theirs);
     -+			if (push_ours > 0 && push_theirs == 0 &&
     -+			    advice_enabled(ADVICE_STATUS_HINTS)) {
     ++	if (show_push_comparison) {
     ++		strbuf_addstr(sb, "\n");
     ++		format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
     ++		if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
     ++			if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
      +				strbuf_addstr(sb,
      +					_("  (use \"git push\" to publish your local commits)\n"));
      +			}
      +		}
     -+
     -+		free(push_short);
     -+		free(push_full);
      +	}
      +
       	free(base);
     ++	free(full_push);
     ++	free(push);
       	return 1;
       }
     + 
      
       ## t/t6040-tracking-info.sh ##
      @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
Signature · 1 line
gitgitgadget
#79 lore raw

PATCH v13 1/2 refactor: format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 59 insertions(+), 40 deletions(-)
remote.c +59 -40
index 59b3715120..58093f64b0 100644
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ahead, int behind,
+				     const char *branch_name,
+				     int upstream_is_gone,
+				     enum ahead_behind_flags abf,
+				     int sti)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
+			branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else if (!sti) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
-	} else if (!theirs) {
+	} else if (ahead == 0 && behind == 0) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
+	} else if (ahead > 0 && behind == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
-			   ours),
-			base, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git push\" to publish your local commits)\n"));
-	} else if (!ours) {
+			   ahead),
+			branch_name, ahead);
+	} else if (behind > 0 && ahead == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is behind '%s' by %d commit, "
 			       "and can be fast-forwarded.\n",
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
-			   theirs),
-			base, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" to update your local branch)\n"));
-	} else {
+			   behind),
+			branch_name, behind);
+	} else if (ahead > 0 && behind > 0) {
 		strbuf_addf(sb,
 			Q_("Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
-			   ours + theirs),
-			base, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+			   ahead + behind),
+			branch_name, ahead, behind);
+	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git push\" to publish your local commits)\n"));
+		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git pull\" to update your local branch)\n"));
+		} else if (ours && theirs && show_divergence_advice &&
+			   advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		}
 	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#80 lore raw

PATCH v13 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  61 +++++++++++++-
 t/t6040-tracking-info.sh | 168 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 228 insertions(+), 1 deletion(-)
remote.c +60 -1
index 58093f64b0..c90441fe42 100644
@@ -2237,6 +2237,39 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	const char *push_remote;
+	const char *resolved;
+	struct strbuf ref_buf = STRBUF_INIT;
+	char *ret = NULL;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		ref_buf.buf,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (resolved) {
+		if (full_ref_out)
+			*full_ref_out = xstrdup(resolved);
+		ret = refs_shorten_unambiguous_ref(
+			get_main_ref_store(the_repository), resolved, 0);
+	}
+
+	strbuf_release(&ref_buf);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int ahead, int behind,
 				     const char *branch_name,
@@ -2304,6 +2337,10 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	int show_push_comparison = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2315,9 +2352,18 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0)
+			show_push_comparison = 1;
+	}
+
 	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
 	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+		if (!theirs && !show_push_comparison &&
+		    advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,7 +2376,20 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		}
 	}
 
+	if (show_push_comparison) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
+		if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+			if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+				strbuf_addstr(sb,
+					_("  (use \"git push\" to publish your local commits)\n"));
+			}
+		}
+	}
+
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +168 -0

172 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#81 lore raw

v14 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]

Harald Nordgren (2):
refactor: format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 171 ++++++++++++++++++++++++-------
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 344 insertions(+), 37 deletions(-)

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

Range-diff vs v13:

 1:  a2c160c53e = 1:  a2c160c53e refactor: format_branch_comparison in preparation
 2:  891239211e ! 2:  b9b2f15498 status: show comparison with push remote tracking branch
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       
      +static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
      +{
     ++	struct remote *remote;
      +	const char *push_remote;
     ++	char *push_dst = NULL;
     ++	char *tracking_ref;
      +	const char *resolved;
     -+	struct strbuf ref_buf = STRBUF_INIT;
     -+	char *ret = NULL;
     ++	char *ret;
      +
      +	if (!branch)
      +		return NULL;
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +	if (!push_remote)
      +		return NULL;
      +
     -+	strbuf_addf(&ref_buf, "refs/remotes/%s/%s", push_remote, branch->name);
     ++	remote = remotes_remote_get(the_repository, push_remote);
     ++	if (!remote)
     ++		return NULL;
     ++
     ++	push_dst = remote_ref_for_branch(branch, 1);
     ++	if (!push_dst) {
     ++		if (remote->push.nr)
     ++			return NULL;
     ++		push_dst = xstrdup(branch->refname);
     ++	}
     ++
     ++	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
     ++	free(push_dst);
     ++
     ++	if (!tracking_ref)
     ++		return NULL;
      +
      +	resolved = refs_resolve_ref_unsafe(
      +		get_main_ref_store(the_repository),
     -+		ref_buf.buf,
     ++		tracking_ref,
      +		RESOLVE_REF_READING,
      +		NULL, NULL);
      +
     -+	if (resolved) {
     -+		if (full_ref_out)
     -+			*full_ref_out = xstrdup(resolved);
     -+		ret = refs_shorten_unambiguous_ref(
     -+			get_main_ref_store(the_repository), resolved, 0);
     ++	if (!resolved) {
     ++		free(tracking_ref);
     ++		return NULL;
      +	}
      +
     -+	strbuf_release(&ref_buf);
     ++	if (full_ref_out)
     ++		*full_ref_out = xstrdup(resolved);
     ++	ret = refs_shorten_unambiguous_ref(
     ++		get_main_ref_store(the_repository), resolved, 0);
     ++	free(tracking_ref);
      +	return ret;
      +}
      +
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	EOF
      +	test_cmp expect actual
      +'
     ++
     ++test_expect_success 'status shows remapped push refspec' '
     ++	(
     ++		cd test &&
     ++		git checkout -b feature8 origin/main &&
     ++		git config remote.origin.push refs/heads/feature8:refs/heads/remapped &&
     ++		git push &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature8
     ++	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
     ++
     ++	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
     ++test_expect_success 'status shows remapped push refspec with upstream remote' '
     ++	(
     ++		cd test &&
     ++		git checkout -b feature9 upstream/main &&
     ++		git config remote.origin.push refs/heads/feature9:refs/heads/remapped &&
     ++		git push origin &&
     ++		advance work &&
     ++		git status >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature9
     ++	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
     ++
     ++	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
     ++	  (use "git push" to publish your local commits)
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
      +
       test_done
Signature · 1 line
gitgitgadget
#82 lore raw

PATCH v14 1/2 refactor: format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 59 insertions(+), 40 deletions(-)
remote.c +59 -40
index 59b3715120..58093f64b0 100644
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ahead, int behind,
+				     const char *branch_name,
+				     int upstream_is_gone,
+				     enum ahead_behind_flags abf,
+				     int sti)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
+			branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else if (!sti) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
-	} else if (!theirs) {
+	} else if (ahead == 0 && behind == 0) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
+	} else if (ahead > 0 && behind == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
-			   ours),
-			base, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git push\" to publish your local commits)\n"));
-	} else if (!ours) {
+			   ahead),
+			branch_name, ahead);
+	} else if (behind > 0 && ahead == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is behind '%s' by %d commit, "
 			       "and can be fast-forwarded.\n",
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
-			   theirs),
-			base, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" to update your local branch)\n"));
-	} else {
+			   behind),
+			branch_name, behind);
+	} else if (ahead > 0 && behind > 0) {
 		strbuf_addf(sb,
 			Q_("Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
-			   ours + theirs),
-			base, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+			   ahead + behind),
+			branch_name, ahead, behind);
+	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git push\" to publish your local commits)\n"));
+		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git pull\" to update your local branch)\n"));
+		} else if (ours && theirs && show_divergence_advice &&
+			   advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		}
 	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#83 lore raw

Re: [PATCH v14 1/2] refactor: format_branch_comparison in preparation

Quoted 48 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 99 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 59 insertions(+), 40 deletions(-)
remote.c +6 -20
index 59b3715120..58093f64b0 100644
@@ -2237,66 +2237,50 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ahead, int behind,
+				     const char *branch_name,
+				     int upstream_is_gone,
+				     enum ahead_behind_flags abf,
+				     int sti)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2257

Quoted 10 lines
remote.c +2 -2
-			base);
+			branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else if (!sti) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);

So, we used to say this when stat-tracking-info gave 0, i.e., ours and theirs have not diverged. But now you decided to make the caller responsible for making the call to stat_tracking_info(), it seems to me that this if/else-if arm is somewhat redundant given that the caller can and will signal the same thing with both ahead and behind being zero.

IOW, it smells to me that leaving "sti" as a parameter to this function is an incomplete refactoring---I say "smell" because I haven't seen the other, new, caller that will be added in the future step of this patch series.

And if the new calling convention is to let the caller be responsible for calling stat_tracking_info() and figuring out the base branch name, it probably is also better to have the caller handle upstream_is_gone case. This is especially true if your plan is to reuse this "branch comparison" helper to compare a branch with another branch that is *NOT* its upstream (e.g., where the result is pushed to).

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2279

Quoted 9 lines
remote.c +1 -2
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
-	} else if (!theirs) {

As the code already handled !sti, which is equivalent to (!theirs && !ours), in the original, !theirs here meant (!theirs && ours), which is (ahead && !behind) in the new world order, which we see a few lines below.

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2265

Quoted 1 line
remote.c +1 -0
+	} else if (ahead == 0 && behind == 0) {

And this is what the above (!sti) should have checked for, after the helper function lost the sti parameter.

Do not compare with 0 for equality. Instead write it like so:

	} else if (!ahead && !behind) {

I won't repeat for other style violations of the same kind.

Quoted 77 lines
remote.c +10 -12
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
 
+	} else if (ahead > 0 && behind == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
-			   ours),
-			base, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git push\" to publish your local commits)\n"));
-	} else if (!ours) {
+			   ahead),
+			branch_name, ahead);
+	} else if (behind > 0 && ahead == 0) {
 		strbuf_addf(sb,
 			Q_("Your branch is behind '%s' by %d commit, "
 			       "and can be fast-forwarded.\n",
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
-			   theirs),
-			base, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" to update your local branch)\n"));
-	} else {
+			   behind),
+			branch_name, behind);
+	} else if (ahead > 0 && behind > 0) {
 		strbuf_addf(sb,
 			Q_("Your branch and '%s' have diverged,\n"
remote.c +35 -4
 			       "and have %d and %d different commit each, "
@@ -2304,13 +2288,48 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
-			   ours + theirs),
-			base, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+			   ahead + behind),
+			branch_name, ahead, behind);
+	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
+	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git push\" to publish your local commits)\n"));
+		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
+			strbuf_addstr(sb,
+				_("  (use \"git pull\" to update your local branch)\n"));

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2335

Quoted 10 lines
remote.c +4 -0
+		} else if (ours && theirs && show_divergence_advice &&
+			   advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		}
 	}
+
 	free(base);
 	return 1;
 }

Overall I think this is a reasonable direction to go, even though passing the "sti" thing is iffy, and the implementation in the function may have small rooms for improvements.

I didn't look at minute details to ensure that the output for all cases are identical to that of the original, though.

#84 lore raw

Another look?

Quoted 6 lines

And if the new calling convention is to let the caller be responsible for calling stat_tracking_info() and figuring out the base branch name, it probably is also better to have the caller handle upstream_is_gone case. This is especially true if your plan is to reuse this "branch comparison" helper to compare a branch with another branch that is *NOT* its upstream (e.g., where the result is pushed to).

Done, will be in the next patch!

Quoted 10 lines

IOW, it smells to me that leaving "sti" as a parameter to this function is an incomplete refactoring---I say "smell" because I haven't seen the other, new, caller that will be added in the future step of this patch series.

[...]

As the code already handled !sti, which is equivalent to (!theirs && !ours), in the original, !theirs here meant (!theirs && ours), which is (ahead && !behind) in the new world order, which we see a few lines below.

This sounds reasonable, however, if I replace '!sti' with 'ahead && !behind' then old tests are breaking, one why to avoid them breaking is to switch the order of these cases, to this, would that be acceptable?

``` } else if (abf == AHEAD_BEHIND_QUICK) { strbuf_addf(sb, _("Your
branch and '%s' refer to different commits.\n"), branch_name); if
(advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addf(sb, _(" (use \"%s\" for
details)\n"), "git status --ahead-behind"); } else if (!theirs && !ours) {
strbuf_addf(sb, _("Your branch is up to date with '%s'.\n"), branch_name);
```
Quoted 1 line

Do not compare with 0 for equality. Instead write it like so:

Good point! I will upate it!

Thanks for you continued attention to this, much appreciated!

Harald

#85 lore raw

Another look?

Sorry, here's what the code block will look like:

```
	} else if (abf == AHEAD_BEHIND_QUICK) {
		strbuf_addf(sb,
			    _("Your branch and '%s' refer to different commits.\n"),
			    branch_name);
		if (advice_enabled(ADVICE_STATUS_HINTS))
			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
				    "git status --ahead-behind");
	} else if (!theirs && !ours) {
		strbuf_addf(sb,
			_("Your branch is up to date with '%s'.\n"),
			branch_name);
```

Harald

#86 lore raw

Re: Another look?

Quoted 15 lines · Harald Nordgren

Sorry, here's what the code block will look like:

```
	} else if (abf == AHEAD_BEHIND_QUICK) {
		strbuf_addf(sb,
			    _("Your branch and '%s' refer to different commits.\n"),
			    branch_name);
		if (advice_enabled(ADVICE_STATUS_HINTS))
			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
				    "git status --ahead-behind");
	} else if (!theirs && !ours) {
		strbuf_addf(sb,
			_("Your branch is up to date with '%s'.\n"),
			branch_name);
```

I did not check what comes before or after this block, but the above looks like a reversed ordering. If you have "the branches are the same" check first, it would make more sense, as after ruling out that case, QUICK can short-cut comparison and asy "they are different", and presumably after these two else/if arms, you'd have cases for "theirs && !ours -> they are ahead of us", "!theirs && ours -> we are ahead of them", and "theirs && ours -> we diverged" to handle.

#87 lore raw

PATCH v14 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  80 ++++++++++++++-
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 289 insertions(+), 1 deletion(-)
remote.c +79 -1
index 58093f64b0..f5d690d377 100644
@@ -2237,6 +2237,58 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int ahead, int behind,
 				     const char *branch_name,
@@ -2304,6 +2356,10 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	int show_push_comparison = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2315,9 +2371,18 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0)
+			show_push_comparison = 1;
+	}
+
 	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
 	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
-		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+		if (!theirs && !show_push_comparison &&
+		    advice_enabled(ADVICE_STATUS_HINTS)) {
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
@@ -2330,7 +2395,20 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		}
 	}
 
+	if (show_push_comparison) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
+		if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
+			if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
+				strbuf_addstr(sb,
+					_("  (use \"git push\" to publish your local commits)\n"));
+			}
+		}
+	}
+
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +210 -0

214 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#88 lore raw

v15 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 168 ++++++++++++++++++++++++-------
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 342 insertions(+), 36 deletions(-)

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

Range-diff vs v14:

1: a2c160c53e ! 1: cf4e9779c5 refactor: format_branch_comparison in preparation

     @@ Metadata
      Author: Harald Nordgren <[email protected]>
      
       ## Commit message ##
     -    refactor: format_branch_comparison in preparation
     +    refactor format_branch_comparison in preparation
      
          Refactor format_branch_comparison function in preparation for showing
          comparison with push remote tracking branch.
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      -			 enum ahead_behind_flags abf,
      -			 int show_divergence_advice)
      +static void format_branch_comparison(struct strbuf *sb,
     -+				     int ahead, int behind,
     ++				     int ours, int theirs,
      +				     const char *branch_name,
     -+				     int upstream_is_gone,
      +				     enum ahead_behind_flags abf,
     -+				     int sti)
     ++				     int show_divergence_advice)
       {
      -	int ours, theirs, sti;
      -	const char *full_base;
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      -
      -	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
      -					    full_base, 0);
     - 	if (upstream_is_gone) {
     - 		strbuf_addf(sb,
     - 			_("Your branch is based on '%s', but the upstream is gone.\n"),
     +-	if (upstream_is_gone) {
     +-		strbuf_addf(sb,
     +-			_("Your branch is based on '%s', but the upstream is gone.\n"),
      -			base);
     -+			branch_name);
     - 		if (advice_enabled(ADVICE_STATUS_HINTS))
     - 			strbuf_addstr(sb,
     - 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
     - 	} else if (!sti) {
     - 		strbuf_addf(sb,
     - 			_("Your branch is up to date with '%s'.\n"),
     +-		if (advice_enabled(ADVICE_STATUS_HINTS))
     +-			strbuf_addstr(sb,
     +-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
     +-	} else if (!sti) {
     +-		strbuf_addf(sb,
     +-			_("Your branch is up to date with '%s'.\n"),
      -			base);
     -+			branch_name);
     - 	} else if (abf == AHEAD_BEHIND_QUICK) {
     +-	} else if (abf == AHEAD_BEHIND_QUICK) {
     ++	if (abf == AHEAD_BEHIND_QUICK) {
       		strbuf_addf(sb,
       			    _("Your branch and '%s' refer to different commits.\n"),
      -			    base);
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       		if (advice_enabled(ADVICE_STATUS_HINTS))
       			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
       				    "git status --ahead-behind");
     --	} else if (!theirs) {
     -+	} else if (ahead == 0 && behind == 0) {
     ++	} else if (!ours && !theirs) {
      +		strbuf_addf(sb,
      +			_("Your branch is up to date with '%s'.\n"),
      +			branch_name);
     -+	} else if (ahead > 0 && behind == 0) {
     + 	} else if (!theirs) {
       		strbuf_addf(sb,
       			Q_("Your branch is ahead of '%s' by %d commit.\n",
       			   "Your branch is ahead of '%s' by %d commits.\n",
     --			   ours),
     + 			   ours),
      -			base, ours);
     --		if (advice_enabled(ADVICE_STATUS_HINTS))
     --			strbuf_addstr(sb,
     --				_("  (use \"git push\" to publish your local commits)\n"));
     --	} else if (!ours) {
     -+			   ahead),
     -+			branch_name, ahead);
     -+	} else if (behind > 0 && ahead == 0) {
     - 		strbuf_addf(sb,
     - 			Q_("Your branch is behind '%s' by %d commit, "
     - 			       "and can be fast-forwarded.\n",
     ++			branch_name, ours);
     + 		if (advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git push\" to publish your local commits)\n"));
     +@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			   "Your branch is behind '%s' by %d commits, "
       			       "and can be fast-forwarded.\n",
     --			   theirs),
     + 			   theirs),
      -			base, theirs);
     --		if (advice_enabled(ADVICE_STATUS_HINTS))
     --			strbuf_addstr(sb,
     --				_("  (use \"git pull\" to update your local branch)\n"));
     --	} else {
     -+			   behind),
     -+			branch_name, behind);
     -+	} else if (ahead > 0 && behind > 0) {
     - 		strbuf_addf(sb,
     - 			Q_("Your branch and '%s' have diverged,\n"
     - 			       "and have %d and %d different commit each, "
     ++			branch_name, theirs);
     + 		if (advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git pull\" to update your local branch)\n"));
      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
     - 			   "Your branch and '%s' have diverged,\n"
       			       "and have %d and %d different commits each, "
       			       "respectively.\n",
     --			   ours + theirs),
     + 			   ours + theirs),
      -			base, ours, theirs);
     --		if (show_divergence_advice &&
     --		    advice_enabled(ADVICE_STATUS_HINTS))
     -+			   ahead + behind),
     -+			branch_name, ahead, behind);
     -+	}
     ++			branch_name, ours, theirs);
     + 		if (show_divergence_advice &&
     + 		    advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
     + 	}
      +}
      +
      +/*
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      +	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
      +					    full_base, 0);
      +
     -+	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
     -+	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
     -+		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
     -+			strbuf_addstr(sb,
     -+				_("  (use \"git push\" to publish your local commits)\n"));
     -+		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
     ++	if (upstream_is_gone) {
     ++		strbuf_addf(sb,
     ++			_("Your branch is based on '%s', but the upstream is gone.\n"),
     ++			base);
     ++		if (advice_enabled(ADVICE_STATUS_HINTS))
      +			strbuf_addstr(sb,
     -+				_("  (use \"git pull\" to update your local branch)\n"));
     -+		} else if (ours && theirs && show_divergence_advice &&
     -+			   advice_enabled(ADVICE_STATUS_HINTS)) {
     - 			strbuf_addstr(sb,
     - 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
     -+		}
     - 	}
     ++				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
     ++	} else {
     ++		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
     ++	}
      +
       	free(base);
       	return 1;
 2:  b9b2f15498 ! 2:  a435cf4ce4 status: show comparison with push remote tracking branch
     @@ Commit message
          Signed-off-by: Harald Nordgren <[email protected]>
      
       ## remote.c ##
     +@@
     + 
     + enum map_direction { FROM_SRC, FROM_DST };
     + 
     ++enum branch_type {
     ++	PUSH = 1 << 0,
     ++	PULL = 1 << 1
     ++};
     ++
     + struct counted_string {
     + 	size_t len;
     + 	const char *s;
      @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
       	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
       }
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +}
      +
       static void format_branch_comparison(struct strbuf *sb,
     - 				     int ahead, int behind,
     + 				     int ours, int theirs,
       				     const char *branch_name,
     + 				     enum ahead_behind_flags abf,
     ++				     enum branch_type bt,
     + 				     int show_divergence_advice)
     + {
     + 	if (abf == AHEAD_BEHIND_QUICK) {
     +@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
     + 			   "Your branch is ahead of '%s' by %d commits.\n",
     + 			   ours),
     + 			branch_name, ours);
     +-		if (advice_enabled(ADVICE_STATUS_HINTS))
     ++		if ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git push\" to publish your local commits)\n"));
     + 	} else if (!ours) {
     +@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
     + 			       "and can be fast-forwarded.\n",
     + 			   theirs),
     + 			branch_name, theirs);
     +-		if (advice_enabled(ADVICE_STATUS_HINTS))
     ++		if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git pull\" to update your local branch)\n"));
     + 	} else {
     +@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
     + 			       "respectively.\n",
     + 			   ours + theirs),
     + 			branch_name, ours, theirs);
     +-		if (show_divergence_advice &&
     ++		if ((bt & PULL) &&
     ++		    show_divergence_advice &&
     + 		    advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	const char *full_base;
       	char *base;
       	int upstream_is_gone = 0;
     ++	enum branch_type base_bt = PUSH | PULL;
      +	int push_ours, push_theirs, push_sti;
      +	char *full_push = NULL;
      +	char *push = NULL;
     -+	int show_push_comparison = 0;
     ++	enum branch_type push_bt = 0;
       
       	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
       	if (sti < 0) {
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      +	if (push && strcmp(base, push)) {
      +		push_sti = stat_branch_pair(branch->refname, full_push,
      +					   &push_ours, &push_theirs, abf);
     -+		if (push_sti >= 0)
     -+			show_push_comparison = 1;
     ++		if (push_sti >= 0) {
     ++			base_bt = PULL;
     ++			push_bt = PUSH;
     ++		}
      +	}
      +
     - 	format_branch_comparison(sb, ours, theirs, base, upstream_is_gone, abf, sti);
     - 	if (sti > 0 && abf != AHEAD_BEHIND_QUICK) {
     --		if (!theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
     -+		if (!theirs && !show_push_comparison &&
     -+		    advice_enabled(ADVICE_STATUS_HINTS)) {
     - 			strbuf_addstr(sb,
     - 				_("  (use \"git push\" to publish your local commits)\n"));
     - 		} else if (!ours && advice_enabled(ADVICE_STATUS_HINTS)) {
     + 	if (upstream_is_gone) {
     + 		strbuf_addf(sb,
     + 			_("Your branch is based on '%s', but the upstream is gone.\n"),
      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
     - 		}
     - 	}
     - 
     -+	if (show_push_comparison) {
     -+		strbuf_addstr(sb, "\n");
     -+		format_branch_comparison(sb, push_ours, push_theirs, push, 0, abf, push_sti);
     -+		if (push_sti > 0 && abf != AHEAD_BEHIND_QUICK) {
     -+			if (!push_theirs && advice_enabled(ADVICE_STATUS_HINTS)) {
     -+				strbuf_addstr(sb,
     -+					_("  (use \"git push\" to publish your local commits)\n"));
     -+			}
     -+		}
     + 			strbuf_addstr(sb,
     + 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
     + 	} else {
     +-		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
     ++		format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
     ++					 show_divergence_advice);
      +	}
      +
     ++	if (push_bt & PUSH) {
     ++		strbuf_addstr(sb, "\n");
     ++		format_branch_comparison(sb, push_ours, push_theirs, push, abf,
     ++					 push_bt, 0);
     + 	}
     + 
       	free(base);
      +	free(full_push);
      +	free(push);
Signature · 1 line
gitgitgadget
#89 lore raw

PATCH v15 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 85 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 49 insertions(+), 36 deletions(-)
remote.c +49 -36
index 59b3715120..b6a9e14376 100644
@@ -2237,51 +2237,29 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     int show_divergence_advice)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
-		strbuf_addf(sb,
-			_("Your branch is up to date with '%s'.\n"),
-			base);
-	} else if (abf == AHEAD_BEHIND_QUICK) {
+	if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
+	} else if (!ours && !theirs) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
 	} else if (!theirs) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2270,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2283,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#90 lore raw

PATCH v15 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  91 ++++++++++++++++-
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 297 insertions(+), 4 deletions(-)
remote.c +87 -4
index b6a9e14376..3a8706395a 100644
@@ -29,6 +29,11 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum branch_type {
+	PUSH = 1 << 0,
+	PULL = 1 << 1
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -2237,10 +2242,63 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int ours, int theirs,
 				     const char *branch_name,
 				     enum ahead_behind_flags abf,
+				     enum branch_type bt,
 				     int show_divergence_advice)
 {
 	if (abf == AHEAD_BEHIND_QUICK) {
@@ -2260,7 +2318,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
 			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2271,7 +2329,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2284,7 +2342,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
+		if ((bt & PULL) &&
+		    show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2302,6 +2361,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	enum branch_type base_bt = PUSH | PULL;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	enum branch_type push_bt = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2313,6 +2377,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_bt = PULL;
+			push_bt = PUSH;
+		}
+	}
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2321,10 +2395,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
+					 show_divergence_advice);
+	}
+
+	if (push_bt & PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_ours, push_theirs, push, abf,
+					 push_bt, 0);
 	}
 
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +210 -0

214 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#91 lore raw

v16 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 171 ++++++++++++++++++++++++-------
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 345 insertions(+), 36 deletions(-)

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

Range-diff vs v15:

 1:  cf4e9779c5 = 1:  cf4e9779c5 refactor format_branch_comparison in preparation
 2:  a435cf4ce4 ! 2:  06cb483f61 status: show comparison with push remote tracking branch
     @@ remote.c
       
       enum map_direction { FROM_SRC, FROM_DST };
       
     -+enum branch_type {
     -+	PUSH = 1 << 0,
     -+	PULL = 1 << 1
     ++enum branch_mode_flags {
     ++	BRANCH_MODE_PULL = (1 << 0),
     ++	BRANCH_MODE_PUSH = (1 << 1),
      +};
      +
       struct counted_string {
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +
      +	if (full_ref_out)
      +		*full_ref_out = xstrdup(resolved);
     ++
      +	ret = refs_shorten_unambiguous_ref(
      +		get_main_ref_store(the_repository), resolved, 0);
      +	free(tracking_ref);
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       				     int ours, int theirs,
       				     const char *branch_name,
       				     enum ahead_behind_flags abf,
     -+				     enum branch_type bt,
     ++				     enum branch_mode_flags advice_flags,
       				     int show_divergence_advice)
       {
       	if (abf == AHEAD_BEHIND_QUICK) {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   ours),
       			branch_name, ours);
      -		if (advice_enabled(ADVICE_STATUS_HINTS))
     -+		if ((bt & PUSH) && advice_enabled(ADVICE_STATUS_HINTS))
     ++		if ((advice_flags & BRANCH_MODE_PUSH) &&
     ++		    advice_enabled(ADVICE_STATUS_HINTS))
       			strbuf_addstr(sb,
       				_("  (use \"git push\" to publish your local commits)\n"));
       	} else if (!ours) {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   theirs),
       			branch_name, theirs);
      -		if (advice_enabled(ADVICE_STATUS_HINTS))
     -+		if ((bt & PULL) && advice_enabled(ADVICE_STATUS_HINTS))
     ++		if ((advice_flags & BRANCH_MODE_PULL) &&
     ++		    advice_enabled(ADVICE_STATUS_HINTS))
       			strbuf_addstr(sb,
       				_("  (use \"git pull\" to update your local branch)\n"));
       	} else {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   ours + theirs),
       			branch_name, ours, theirs);
      -		if (show_divergence_advice &&
     -+		if ((bt & PULL) &&
     ++		if ((advice_flags & BRANCH_MODE_PULL) &&
      +		    show_divergence_advice &&
       		    advice_enabled(ADVICE_STATUS_HINTS))
       			strbuf_addstr(sb,
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	const char *full_base;
       	char *base;
       	int upstream_is_gone = 0;
     -+	enum branch_type base_bt = PUSH | PULL;
     ++	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
      +	int push_ours, push_theirs, push_sti;
      +	char *full_push = NULL;
      +	char *push = NULL;
     -+	enum branch_type push_bt = 0;
     ++	enum branch_mode_flags push_branch_modes = 0;
       
       	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
       	if (sti < 0) {
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      +		push_sti = stat_branch_pair(branch->refname, full_push,
      +					   &push_ours, &push_theirs, abf);
      +		if (push_sti >= 0) {
     -+			base_bt = PULL;
     -+			push_bt = PUSH;
     ++			base_branch_modes = BRANCH_MODE_PULL;
     ++			push_branch_modes = BRANCH_MODE_PUSH;
      +		}
      +	}
      +
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
       	} else {
      -		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
     -+		format_branch_comparison(sb, ours, theirs, base, abf, base_bt,
     -+					 show_divergence_advice);
     ++		format_branch_comparison(sb, ours, theirs, base, abf,
     ++					 base_branch_modes, show_divergence_advice);
      +	}
      +
     -+	if (push_bt & PUSH) {
     ++	if (push_branch_modes & BRANCH_MODE_PUSH) {
      +		strbuf_addstr(sb, "\n");
      +		format_branch_comparison(sb, push_ours, push_theirs, push, abf,
     -+					 push_bt, 0);
     ++					 push_branch_modes, 0);
       	}
       
       	free(base);
Signature · 1 line
gitgitgadget
#92 lore raw

PATCH v16 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 85 ++++++++++++++++++++++++++++++++------------------------
 1 file changed, 49 insertions(+), 36 deletions(-)
remote.c +49 -36
index 59b3715120..b6a9e14376 100644
@@ -2237,51 +2237,29 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
+static void format_branch_comparison(struct strbuf *sb,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     int show_divergence_advice)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
-		strbuf_addf(sb,
-			_("Your branch is up to date with '%s'.\n"),
-			base);
-	} else if (abf == AHEAD_BEHIND_QUICK) {
+	if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
+	} else if (!ours && !theirs) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);
 	} else if (!theirs) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2270,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2283,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#93 lore raw

Re: [PATCH v16 1/2] refactor format_branch_comparison in preparation

Quoted 7 lines · Harald Nordgren via GitGitGadget
remote.c +1 -5
-	} else if (!sti) {
-		strbuf_addf(sb,
-			_("Your branch is up to date with '%s'.\n"),
-			base);
-	} else if (abf == AHEAD_BEHIND_QUICK) {
+	if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2256

Quoted 10 lines
remote.c +5 -1
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
+	} else if (!ours && !theirs) {
+		strbuf_addf(sb,
+			_("Your branch is up to date with '%s'.\n"),
+			branch_name);

We used to check if there is nothing to report (i.e., !sti is a signal from stat_tracking_info() that there are no differences between the branches) and reported that first, so when abf was checked, we knew that there are some differences. Now, your patch reverses the order so whether there is or is not a change, abf codepath will always report "you have differences!".

This smells iffy.

#94 lore raw

Another look?

Quoted 18 lines
Quoted 7 lines
remote.c +1 -5
-     } else if (!sti) {
-             strbuf_addf(sb,
-                     _("Your branch is up to date with '%s'.\n"),
-                     base);
-     } else if (abf == AHEAD_BEHIND_QUICK) {
+     if (abf == AHEAD_BEHIND_QUICK) {
              strbuf_addf(sb,

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2256

Quoted 10 lines
remote.c +5 -1
                          _("Your branch and '%s' refer to different commits.\n"),
-                         base);
+                         branch_name);
              if (advice_enabled(ADVICE_STATUS_HINTS))
                      strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
                                  "git status --ahead-behind");
+     } else if (!ours && !theirs) {
+             strbuf_addf(sb,
+                     _("Your branch is up to date with '%s'.\n"),
+                     branch_name);
Quoted 6 lines

We used to check if there is nothing to report (i.e., !sti is a signal from stat_tracking_info() that there are no differences between the branches) and reported that first, so when abf was checked, we knew that there are some differences. Now, your patch reverses the order so whether there is or is not a change, abf codepath will always report "you have differences!".

Agreed! This change was done to get rid of sti as a parameter. Maybe I misunderstood your previous comment around the sti.

I will bring back sti as a parameter to ’format_branch_comparison’ now, I don’t see any other way to solve this.

Harald

#95 lore raw

Another look?

I'm also adding better test coverage for '--no-ahead-behind', it's not good that that feature can silently break.

Harald

#96 lore raw

Re: Another look?

In reply to Harald Nordgren
Quoted 25 lines · Harald Nordgren
Quoted 18 lines
Quoted 7 lines
remote.c +1 -5
-     } else if (!sti) {
-             strbuf_addf(sb,
-                     _("Your branch is up to date with '%s'.\n"),
-                     base);
-     } else if (abf == AHEAD_BEHIND_QUICK) {
+     if (abf == AHEAD_BEHIND_QUICK) {
              strbuf_addf(sb,

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2256

Quoted 10 lines
remote.c +5 -1
                          _("Your branch and '%s' refer to different commits.\n"),
-                         base);
+                         branch_name);
              if (advice_enabled(ADVICE_STATUS_HINTS))
                      strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
                                  "git status --ahead-behind");
+     } else if (!ours && !theirs) {
+             strbuf_addf(sb,
+                     _("Your branch is up to date with '%s'.\n"),
+                     branch_name);
Quoted 6 lines

We used to check if there is nothing to report (i.e., !sti is a signal from stat_tracking_info() that there are no differences between the branches) and reported that first, so when abf was checked, we knew that there are some differences. Now, your patch reverses the order so whether there is or is not a change, abf codepath will always report "you have differences!".

Quoted 5 lines

Agreed! This change was done to get rid of sti as a parameter. Maybe I misunderstood your previous comment around the sti.

I will bring back sti as a parameter to ’format_branch_comparison’ now, I don’t see any other way to solve this.

Please don't. Unless my assumption, which is that in the old code "!sti" and "!ours && !theirs" is equivalent, is wrong, all you need to do around that part is to first check "if (!ours && !theirs)" and say "your branch is up to date with...", and then have the check "else if (abf == ABQ)" next. That way, when we check abf we know the branches are different.

#97 lore raw

Another look?

Quoted 6 lines

Please don't. Unless my assumption, which is that in the old code "!sti" and "!ours && !theirs" is equivalent, is wrong, all you need to do around that part is to first check "if (!ours && !theirs)" and say "your branch is up to date with...", and then have the check "else if (abf == ABQ)" next. That way, when we check abf we know the branches are different.

It seems to be an incorrect assumption. This code change breaks several tests including old ones:

```
diff --git a/remote.c b/remote.c
index 1f87b85b22..8db4fcd7b5 100644
--- a/remote.c
+++ b/remote.c
@@ -2303,7 +2303,7 @@ static void format_branch_comparison(struct strbuf *sb,
 				     enum branch_mode_flags advice_flags,
 				     int show_divergence_advice)
 {
-	if (!sti) {
+	if (!ours && !theirs) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
 			branch_name);
```

Harald

#98 lore raw

Re: Another look?

Quoted 30 lines · Harald Nordgren
Quoted 6 lines

Please don't. Unless my assumption, which is that in the old code "!sti" and "!ours && !theirs" is equivalent, is wrong, all you need to do around that part is to first check "if (!ours && !theirs)" and say "your branch is up to date with...", and then have the check "else if (abf == ABQ)" next. That way, when we check abf we know the branches are different.

It seems to be an incorrect assumption. This code change breaks several tests including old ones:

```

remote.c +1 -1
index 1f87b85b22..8db4fcd7b5 100644
@@ -2303,7 +2303,7 @@ static void format_branch_comparison(struct strbuf *sb,
 				     enum branch_mode_flags advice_flags,
 				     int show_divergence_advice)
 {
-	if (!sti) {
+	if (!ours && !theirs) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
 			branch_name);

```

Quoted 1 line

Harald

That is unexpected.

Looking at what stat_branch_pair() does, before returning 0, the function always clears *num_theirs and *num_ours, so there is something else going on.

If your caller is *not* initializing ours and theirs, and if it is not detecting an error return from stat_tracking_info, and the test code is trying to see when stat_branch_info() signals failure by returning -1, then I can understand why the above change makes a difference (i.e., your code above with or without sti -> ours/theirs change is broken), but then, that should be handled at the caller of stat_tracking_info() by noticing its return value being negative, I would have to say.

#99 lore raw

ABQ

Note that this change also makes tests fail on the 'master' branch, so not an issue introdued by my code:

```
diff --git a/remote.c b/remote.c
index 59b3715120..f84f2747b2 100644
--- a/remote.c
+++ b/remote.c
@@ -2265,7 +2265,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+	} else if (!ours && !theirs) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
 			base);
```

If you have the time, it might be interesting to run './t6040-tracking-info.sh' and to see what happens for the different cases.

It's failing for the ABQ cases, maybe because of this:

```
	if (abf == AHEAD_BEHIND_QUICK)
		return 1;
```

I would argue this is getting outside of the scope of push branch comparison, so maybe better to do this as a follow-up? What do you say?

Harald

#101 lore raw

PATCH v16 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  94 +++++++++++++++++-
 t/t6040-tracking-info.sh | 210 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 300 insertions(+), 4 deletions(-)
remote.c +90 -4
index b6a9e14376..74a394c2a6 100644
@@ -29,6 +29,11 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum branch_mode_flags {
+	BRANCH_MODE_PULL = (1 << 0),
+	BRANCH_MODE_PUSH = (1 << 1),
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -2237,10 +2242,64 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int ours, int theirs,
 				     const char *branch_name,
 				     enum ahead_behind_flags abf,
+				     enum branch_mode_flags advice_flags,
 				     int show_divergence_advice)
 {
 	if (abf == AHEAD_BEHIND_QUICK) {
@@ -2260,7 +2319,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
 			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PUSH) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2271,7 +2331,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PULL) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2284,7 +2345,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
+		if ((advice_flags & BRANCH_MODE_PULL) &&
+		    show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2302,6 +2364,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	enum branch_mode_flags push_branch_modes = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2313,6 +2380,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_branch_modes = BRANCH_MODE_PULL;
+			push_branch_modes = BRANCH_MODE_PUSH;
+		}
+	}
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2321,10 +2398,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, ours, theirs, base, abf,
+					 base_branch_modes, show_divergence_advice);
+	}
+
+	if (push_branch_modes & BRANCH_MODE_PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_ours, push_theirs, push, abf,
+					 push_branch_modes, 0);
 	}
 
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +210 -0

214 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#102 lore raw

v17 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 169 ++++++++++++++++++++-----
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 397 insertions(+), 34 deletions(-)

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

Range-diff vs v16:

1: cf4e9779c5 ! 1: b62a9feb4d refactor format_branch_comparison in preparation

     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      -int format_tracking_info(struct branch *branch, struct strbuf *sb,
      -			 enum ahead_behind_flags abf,
      -			 int show_divergence_advice)
     -+static void format_branch_comparison(struct strbuf *sb,
     -+				     int ours, int theirs,
     -+				     const char *branch_name,
     -+				     enum ahead_behind_flags abf,
     -+				     int show_divergence_advice)
     - {
     +-{
      -	int ours, theirs, sti;
      -	const char *full_base;
      -	char *base;
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      -			strbuf_addstr(sb,
      -				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
      -	} else if (!sti) {
     --		strbuf_addf(sb,
     --			_("Your branch is up to date with '%s'.\n"),
     ++static void format_branch_comparison(struct strbuf *sb,
     ++				     int sti,
     ++				     int ours, int theirs,
     ++				     const char *branch_name,
     ++				     enum ahead_behind_flags abf,
     ++				     int show_divergence_advice)
     ++{
     ++	if (!sti) {
     + 		strbuf_addf(sb,
     + 			_("Your branch is up to date with '%s'.\n"),
      -			base);
     --	} else if (abf == AHEAD_BEHIND_QUICK) {
     -+	if (abf == AHEAD_BEHIND_QUICK) {
     ++			branch_name);
     + 	} else if (abf == AHEAD_BEHIND_QUICK) {
       		strbuf_addf(sb,
       			    _("Your branch and '%s' refer to different commits.\n"),
      -			    base);
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       		if (advice_enabled(ADVICE_STATUS_HINTS))
       			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
       				    "git status --ahead-behind");
     -+	} else if (!ours && !theirs) {
     -+		strbuf_addf(sb,
     -+			_("Your branch is up to date with '%s'.\n"),
     -+			branch_name);
     - 	} else if (!theirs) {
     - 		strbuf_addf(sb,
     +@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			Q_("Your branch is ahead of '%s' by %d commit.\n",
       			   "Your branch is ahead of '%s' by %d commits.\n",
       			   ours),
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      +			strbuf_addstr(sb,
      +				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
      +	} else {
     -+		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
     ++		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
      +	}
      +
       	free(base);
 2:  06cb483f61 ! 2:  1348542edc status: show comparison with push remote tracking branch
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +}
      +
       static void format_branch_comparison(struct strbuf *sb,
     + 				     int sti,
       				     int ours, int theirs,
       				     const char *branch_name,
       				     enum ahead_behind_flags abf,
      +				     enum branch_mode_flags advice_flags,
       				     int show_divergence_advice)
       {
     - 	if (abf == AHEAD_BEHIND_QUICK) {
     + 	if (!sti) {
     +@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
     + 		strbuf_addf(sb,
     + 			    _("Your branch and '%s' refer to different commits.\n"),
     + 			    branch_name);
     +-		if (advice_enabled(ADVICE_STATUS_HINTS))
     ++		if ((advice_flags & BRANCH_MODE_PUSH) &&
     ++		    advice_enabled(ADVICE_STATUS_HINTS))
     + 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
     + 				    "git status --ahead-behind");
     + 	} else if (!theirs) {
      @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   "Your branch is ahead of '%s' by %d commits.\n",
       			   ours),
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			strbuf_addstr(sb,
       				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
       	} else {
     --		format_branch_comparison(sb, ours, theirs, base, abf, show_divergence_advice);
     -+		format_branch_comparison(sb, ours, theirs, base, abf,
     +-		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
     ++		format_branch_comparison(sb, sti, ours, theirs, base, abf,
      +					 base_branch_modes, show_divergence_advice);
      +	}
      +
      +	if (push_branch_modes & BRANCH_MODE_PUSH) {
      +		strbuf_addstr(sb, "\n");
     -+		format_branch_comparison(sb, push_ours, push_theirs, push, abf,
     ++		format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf,
      +					 push_branch_modes, 0);
       	}
       
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	test_cmp expect actual
      +'
      +
     ++test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' '
     ++	(
     ++		cd test &&
     ++		git checkout b4 &&
     ++		git status --no-ahead-behind >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch b4
     ++	Your branch and ${SQ}origin/main${SQ} refer to different commits.
     ++	  (use "git status --ahead-behind" for details)
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
      +test_expect_success 'status shows ahead of both origin/main and feature branch' '
      +	(
      +		cd test &&
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	test_cmp expect actual
      +'
      +
     ++test_expect_success 'status --no-ahead-behind shows diverged from origin/main and ahead of feature branch' '
     ++	(
     ++		cd test &&
     ++		git checkout feature4 &&
     ++		git status --no-ahead-behind >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature4
     ++	Your branch and ${SQ}origin/main${SQ} refer to different commits.
     ++
     ++	Your branch and ${SQ}origin/feature4${SQ} refer to different commits.
     ++	  (use "git status --ahead-behind" for details)
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
      +test_expect_success 'setup upstream remote' '
      +	(
      +		cd test &&
     @@ t/t6040-tracking-info.sh: test_expect_success '--set-upstream-to @{-1}' '
      +	test_cmp expect actual
      +'
      +
     ++test_expect_success 'status --no-ahead-behind with upstream remote and push branch up to date' '
     ++	(
     ++		cd test &&
     ++		git checkout feature7 &&
     ++		git push origin &&
     ++		git status --no-ahead-behind >../actual
     ++	) &&
     ++	cat >expect <<-EOF &&
     ++	On branch feature7
     ++	Your branch is up to date with ${SQ}upstream/main${SQ}.
     ++
     ++	Your branch is up to date with ${SQ}origin/feature7${SQ}.
     ++
     ++	nothing to commit, working tree clean
     ++	EOF
     ++	test_cmp expect actual
     ++'
     ++
      +test_expect_success 'checkout shows push branch up to date' '
      +	(
      +		cd test &&
Signature · 1 line
gitgitgadget
#103 lore raw

PATCH v17 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 34 deletions(-)
remote.c +48 -34
index 59b3715120..7163a8ec28 100644
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
-{
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+				     int sti,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     int show_divergence_advice)
+{
+	if (!sti) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#104 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Hi Harald

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2241

Quoted 4 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

remote.c +2 -0
+static void format_branch_comparison(struct strbuf *sb,
+				     int sti,

I wondered why we needed to pass sti as well as ours and theirs but it is because when we're using AHEAD_BEHIND_QUICK our and theirs are always zero and so we need to check sti to see if the branch is up to date. Perhaps we could make this a boolean called 'up_to_date' ?

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2245

Quoted 4 lines
remote.c +4 -0
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     int show_divergence_advice)

This could be 'bool' not 'int'

Everything else looks fine - it is a faithful conversion from the original and it makes sense to check if the upstream is gone in the caller.

Thanks

Phillip

Quoted 81 lines
remote.c +42 -5
+{
+	if (!sti) {
  		strbuf_addf(sb,
  			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
  	} else if (abf == AHEAD_BEHIND_QUICK) {
  		strbuf_addf(sb,
  			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
  		if (advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
  				    "git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  			Q_("Your branch is ahead of '%s' by %d commit.\n",
  			   "Your branch is ahead of '%s' by %d commits.\n",
  			   ours),
-			base, ours);
+			branch_name, ours);
  		if (advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addstr(sb,
  				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  			   "Your branch is behind '%s' by %d commits, "
  			       "and can be fast-forwarded.\n",
  			   theirs),
-			base, theirs);
+			branch_name, theirs);
  		if (advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addstr(sb,
  				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  			       "and have %d and %d different commits each, "
  			       "respectively.\n",
  			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
  		if (show_divergence_advice &&
  		    advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addstr(sb,
  				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
  	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
  	free(base);
  	return 1;
  }
#105 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 4 lines

I wondered why we needed to pass sti as well as ours and theirs but it is because when we're using AHEAD_BEHIND_QUICK our and theirs are always zero and so we need to check sti to see if the branch is up to date. Perhaps we could make this a boolean called 'up_to_date' ?

Yes, this is the reason. And I tried a lot of things to get around it. But yes, let's pass this boolean instead.

Quoted 4 lines

This could be 'bool' not 'int'

Everything else looks fine - it is a faithful conversion from the original and it makes sense to check if the upstream is gone in the caller.

Done, will be in the next patch.

I didn't update the signature of 'format_tracking_info' to have a bool there too, because it makes the surface area bigger of also updating the .h file there.

Harald

#106 lore raw

PATCH v17 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 |  97 ++++++++++++++-
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 354 insertions(+), 5 deletions(-)
remote.c +92 -5
index 7163a8ec28..1f87b85b22 100644
@@ -29,6 +29,11 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum branch_mode_flags {
+	BRANCH_MODE_PULL = (1 << 0),
+	BRANCH_MODE_PUSH = (1 << 1),
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -2237,11 +2242,65 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     int sti,
 				     int ours, int theirs,
 				     const char *branch_name,
 				     enum ahead_behind_flags abf,
+				     enum branch_mode_flags advice_flags,
 				     int show_divergence_advice)
 {
 	if (!sti) {
@@ -2252,7 +2311,8 @@ static void format_branch_comparison(struct strbuf *sb,
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
 			    branch_name);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PUSH) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
 	} else if (!theirs) {
@@ -2261,7 +2321,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
 			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PUSH) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2272,7 +2333,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PULL) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2285,7 +2347,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
+		if ((advice_flags & BRANCH_MODE_PULL) &&
+		    show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2303,6 +2366,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	enum branch_mode_flags push_branch_modes = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2314,6 +2382,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_branch_modes = BRANCH_MODE_PULL;
+			push_branch_modes = BRANCH_MODE_PUSH;
+		}
+	}
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2400,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, sti, ours, theirs, base, abf,
+					 base_branch_modes, show_divergence_advice);
+	}
+
+	if (push_branch_modes & BRANCH_MODE_PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf,
+					 push_branch_modes, 0);
 	}
 
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +262 -0

266 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#107 lore raw

Re: [PATCH v17 2/2] status: show comparison with push remote tracking branch

Hi Harald

Quoted 24 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Quoted 2 lines

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The advice looks good

Quoted 3 lines

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

Sounds sensible

remote.c line 35

Quoted 12 lines
remote.c +4 -0
index 7163a8ec28..1f87b85b22 100644
@@ -29,6 +29,11 @@
 
  enum map_direction { FROM_SRC, FROM_DST };
 
+enum branch_mode_flags {
+	BRANCH_MODE_PULL = (1 << 0),
+	BRANCH_MODE_PUSH = (1 << 1),
+};

Using an enum for a set of flags is a bit confusing.

Quoted 19 lines
remote.c +19 -0
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2273

Quoted 10 lines
remote.c +10 -0
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);

On reflection I wonder if we should be calling branch_get_push() instead of remote_ref_for_branch() and tracking_for_push_dest() as it respects 'push.default' and so the branch it returns is the one that "git push" without any arguments would push to.

Quoted 5 lines
remote.c +5 -0
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2289

Quoted 10 lines
remote.c +10 -0
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)

I think it would be simpler to just return the full refname and let the caller shorten it.

Quoted 18 lines
remote.c +9 -0
+		*full_ref_out = xstrdup(resolved);
+
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
  static void format_branch_comparison(struct strbuf *sb,
  				     int sti,
  				     int ours, int theirs,
  				     const char *branch_name,
  				     enum ahead_behind_flags abf,
+				     enum branch_mode_flags advice_flags,
  				     int show_divergence_advice)
  {
  	if (!sti) {

remote.c static void format_branch_comparison(struct strbuf *sb, · line 2314

Quoted 5 lines
remote.c +1 -1
@@ -2252,7 +2311,8 @@ static void format_branch_comparison(struct strbuf *sb,
  		strbuf_addf(sb,
  			    _("Your branch and '%s' refer to different commits.\n"),
  			    branch_name);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PUSH) &&

Why are we checking for BRANCH_MODE_PUSH here? Don't we want to show this advice regardless of the mode?

remote.c static void format_branch_comparison(struct strbuf *sb, · line 2325

Quoted 11 lines
remote.c +3 -1
+		    advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
  				    "git status --ahead-behind");
  	} else if (!theirs) {
@@ -2261,7 +2321,8 @@ static void format_branch_comparison(struct strbuf *sb,
  			   "Your branch is ahead of '%s' by %d commits.\n",
  			   ours),
  			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if ((advice_flags & BRANCH_MODE_PUSH) &&
+		    advice_enabled(ADVICE_STATUS_HINTS))

Having to test the flags each time is a bit cumbersome. We could define a couple of local variables to simplify this

	bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
				advice_enabled(ADVICE_STATUS_HINTS);
	bool want_pull_advice = advice_flags & BRANCH_MODE_PULL &&
				advice_enabled(ADVICE_STATUS_HINTS);

Then we can simplify the above to

if (want_push_advice)

remote.c static void format_branch_comparison(struct strbuf *sb, · line 2351

Quoted 11 lines
remote.c +2 -1
  			strbuf_addstr(sb,
  				_("  (use \"git push\" to publish your local commits)\n"));
  	} else if (!ours) {
[...]
@@ -2285,7 +2347,8 @@ static void format_branch_comparison(struct strbuf *sb,
  			       "respectively.\n",
  			   ours + theirs),
  			branch_name, ours, theirs);
-		if (show_divergence_advice &&
+		if ((advice_flags & BRANCH_MODE_PULL) &&
+		    show_divergence_advice &&

If we don't want to show this can't we set show_divergance_adivce to false when we call this function - why is it guarded by BRANCH_MODE_PULL as well?

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2369

Quoted 8 lines
remote.c +1 -0
  		    advice_enabled(ADVICE_STATUS_HINTS))
  			strbuf_addstr(sb,
  				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
@@ -2303,6 +2366,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  	const char *full_base;
  	char *base;
  	int upstream_is_gone = 0;
+	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;

Here we set an enum to a value that is not a member of the enum.

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2386

Quoted 13 lines
remote.c +6 -0
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	enum branch_mode_flags push_branch_modes = 0;
 
  	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
  	if (sti < 0) {
@@ -2314,6 +2382,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
  					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {

This is good - we only show the push branch separately if it differs from the upstream branch.

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2392

Quoted 6 lines
remote.c +6 -0
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_branch_modes = BRANCH_MODE_PULL;
+			push_branch_modes = BRANCH_MODE_PUSH;
+		}

This combined with checking "push_branch_modes & BRANCH_MODE_PUSH" below ensures we skip the push branch if push_sti < 0. That's good but it is a bit hard to follow.

Thanks

Phillip

Quoted 297 lines
remote.c +12 -1
+	}
+
  	if (upstream_is_gone) {
  		strbuf_addf(sb,
  			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2400,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
  			strbuf_addstr(sb,
  				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
  	} else {
-		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, sti, ours, theirs, base, abf,
+					 base_branch_modes, show_divergence_advice);
+	}
+
+	if (push_branch_modes & BRANCH_MODE_PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf,
+					 push_branch_modes, 0);
  	}
 
  	free(base);
+	free(full_push);
+	free(push);
  	return 1;
  }
 
t/t6040-tracking-info.sh +262 -0
index 0b719bbae6..cf5a926dcd 100755
@@ -292,4 +292,266 @@ test_expect_success '--set-upstream-to @{-1}' '
  	test_cmp expect actual
  '
 
+test_expect_success 'status tracking origin/main shows only main' '
+	(
+		cd test &&
+		git checkout b4 &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch b4
+	Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' '
+	(
+		cd test &&
+		git checkout b4 &&
+		git status --no-ahead-behind >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch b4
+	Your branch and ${SQ}origin/main${SQ} refer to different commits.
+	  (use "git status --ahead-behind" for details)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status shows ahead of both origin/main and feature branch' '
+	(
+		cd test &&
+		git checkout -b feature2 origin/main &&
+		git push origin HEAD &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature2
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout shows ahead of both origin/main and feature branch' '
+	(
+		cd test &&
+		git checkout feature2 >../actual
+	) &&
+	cat >expect <<-EOF &&
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup for ahead of tracked but diverged from main' '
+	(
+		cd test &&
+		git checkout -b feature4 origin/main &&
+		advance work1 &&
+		git checkout origin/main &&
+		advance work2 &&
+		git push origin HEAD:main &&
+		git checkout feature4 &&
+		advance work3
+	)
+'
+
+test_expect_success 'status shows diverged from origin/main and ahead of feature branch' '
+	(
+		cd test &&
+		git checkout feature4 &&
+		git branch --set-upstream-to origin/main &&
+		git push origin HEAD &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature4
+	Your branch and ${SQ}origin/main${SQ} have diverged,
+	and have 3 and 1 different commits each, respectively.
+	  (use "git pull" if you want to integrate the remote branch with yours)
+
+	Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind shows diverged from origin/main and ahead of feature branch' '
+	(
+		cd test &&
+		git checkout feature4 &&
+		git status --no-ahead-behind >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature4
+	Your branch and ${SQ}origin/main${SQ} refer to different commits.
+
+	Your branch and ${SQ}origin/feature4${SQ} refer to different commits.
+	  (use "git status --ahead-behind" for details)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'setup upstream remote' '
+	(
+		cd test &&
+		git remote add upstream ../. &&
+		git fetch upstream &&
+		git config remote.pushDefault origin
+	)
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin' '
+	(
+		cd test &&
+		git checkout -b feature5 upstream/main &&
+		git push origin &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature5
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push.default set to origin and diverged' '
+	(
+		cd test &&
+		git checkout -b feature6 upstream/main &&
+		advance work &&
+		git push origin &&
+		git reset --hard upstream/main &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature6
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch and ${SQ}origin/feature6${SQ} have diverged,
+	and have 1 and 1 different commits each, respectively.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status with upstream remote and push branch up to date' '
+	(
+		cd test &&
+		git checkout -b feature7 upstream/main &&
+		git push origin &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature7
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature7${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status --no-ahead-behind with upstream remote and push branch up to date' '
+	(
+		cd test &&
+		git checkout feature7 &&
+		git push origin &&
+		git status --no-ahead-behind >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature7
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature7${SQ}.
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'checkout shows push branch up to date' '
+	(
+		cd test &&
+		git checkout feature7 >../actual
+	) &&
+	cat >expect <<-EOF &&
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is up to date with ${SQ}origin/feature7${SQ}.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status shows remapped push refspec' '
+	(
+		cd test &&
+		git checkout -b feature8 origin/main &&
+		git config remote.origin.push refs/heads/feature8:refs/heads/remapped &&
+		git push &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature8
+	Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status shows remapped push refspec with upstream remote' '
+	(
+		cd test &&
+		git checkout -b feature9 upstream/main &&
+		git config remote.origin.push refs/heads/feature9:refs/heads/remapped &&
+		git push origin &&
+		advance work &&
+		git status >../actual
+	) &&
+	cat >expect <<-EOF &&
+	On branch feature9
+	Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit.
+
+	Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit.
+	  (use "git push" to publish your local commits)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
  test_done
#108 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 1 line

Using an enum for a set of flags is a bit confusing.

The point of the flag and the bitmasking is to selectively turn off the push and pull advice advice from the relevant branch when the push branch comparison is active.

In an earlier implementation the advice logic was moved to the caller instead 'format_branch_comparison', but it's more faithful to the original to have the advice logic inside 'format_branch_comparison'. Maybe I misunderstood your comments around this?

Would happily take a suggestion on a nicer way to handle it.

Quoted 4 lines

On reflection I wonder if we should be calling branch_get_push() instead of remote_ref_for_branch() and tracking_for_push_dest() as it respects 'push.default' and so the branch it returns is the one that "git push" without any arguments would push to.

I'm not getting that to work without tests breaking.

Quoted 2 lines

I think it would be simpler to just return the full refname and let the caller shorten it.

We could to that but what's the benefit? Shouldn't a helper function reduce the work for the caller, not the other way around? 🤗

Quoted 2 lines

Why are we checking for BRANCH_MODE_PUSH here? Don't we want to show this advice regardless of the mode?

Yeah, that's a good point. However, this will often lead to the advice being shown twice, see this test:

```
status --no-ahead-behind shows diverged from origin/main and ahead of feature branch
```
Quoted 11 lines

Having to test the flags each time is a bit cumbersome. We could define a couple of local variables to simplify this

	bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
				advice_enabled(ADVICE_STATUS_HINTS);
	bool want_pull_advice = advice_flags & BRANCH_MODE_PULL &&
				advice_enabled(ADVICE_STATUS_HINTS);

Then we can simplify the above to

if (want_push_advice)

Done, will be in the next batch!

Quoted 3 lines

If we don't want to show this can't we set show_divergance_adivce to false when we call this function - why is it guarded by BRANCH_MODE_PULL as well?

This will already have been shown the the pull branch (the upstream branch).

And in the cases when it's NOT shown for the pull branch but only the push branch has diverged, then pulling won't solve the problem anyway. (Unless you would try to pull from your push branch.)

Quoted 1 line

Here we set an enum to a value that is not a member of the enum.

I use bitmasking to enable both modes by default. But see my comments above maybe there is a nicer way to handle all of this.

Quoted 3 lines

This combined with checking "push_branch_modes & BRANCH_MODE_PUSH" below ensures we skip the push branch if push_sti < 0. That's good but it is a bit hard to follow.

Possibly this can be done in a nicer way too. But at least I try to encapsulate the complexity to only here, to allow the rest of the code to be more straightforward. A good trade-off I think.

Harald

#109 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 11 lines · Harald Nordgren



Quoted 2 lines

Using an enum for a set of flags is a bit confusing.

The point of the flag and the bitmasking is to selectively turn off the push and pull advice advice from the relevant branch when the push branch comparison is active.

Quoted 6 lines

In an earlier implementation the advice logic was moved to the caller instead 'format_branch_comparison', but it's more faithful to the original to have the advice logic inside 'format_branch_comparison'. Maybe I misunderstood your comments around this?

Would happily take a suggestion on a nicer way to handle it.

I think other uses in Git declare a bunch of integer constants for the bitfields, not an enum. Why? Because or-ing flags together creates a value not in the enum…

Quoted 4 lines
Quoted 1 line

Here we set an enum to a value that is not a member of the enum.

I use bitmasking to enable both modes by default. But see my comments above maybe there is a nicer way to handle all of this.

…which explains this comment.

IOW: set of flags makes sense, but there’s a more usual way to set that up?

#110 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 16 lines · Ben Knoble
Quoted 16 lines · Harald Nordgren



Quoted 2 lines

Using an enum for a set of flags is a bit confusing.

The point of the flag and the bitmasking is to selectively turn off the push and pull advice advice from the relevant branch when the push branch comparison is active.

In an earlier implementation the advice logic was moved to the caller instead 'format_branch_comparison', but it's more faithful to the original to have the advice logic inside 'format_branch_comparison'. Maybe I misunderstood your comments around this?

Quoted 5 lines
Quoted 1 line

Would happily take a suggestion on a nicer way to handle it.

I think other uses in Git declare a bunch of integer constants for the bitfields, not an enum. Why? Because or-ing flags together creates a value not in the enum…

We nowadays typically declare the flags as enum, but when accepting the bitfield we use unsigned.

Patrick

#111 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 6 lines
Quoted 3 lines

I think other uses in Git declare a bunch of integer constants for the bitfields, not an enum. Why? Because or-ing flags together creates a value not in the enum…

We nowadays typically declare the flags as enum, but when accepting the bitfield we use unsigned.

Good points! I have updated it to remove the name branch_mode_flags now, define the enums and pass them around as unsigned now. Will be in the next patch.

Harald

#112 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

In reply to Harald Nordgren
Quoted 8 lines · Harald Nordgren
Quoted 6 lines

Using an enum for a set of flags is a bit confusing.

On reflection I wonder if we should be calling branch_get_push() instead of remote_ref_for_branch() and tracking_for_push_dest() as it respects 'push.default' and so the branch it returns is the one that "git push" without any arguments would push to.

I'm not getting that to work without tests breaking.

It is hard to discuss this without knowing what actually breaks. Are you talking about the tests added in this series? If so that means we're expecting a different behavior to what "git push" actually does. As Ben has pointed out elsewhere in this thread, if you're pushing back to a different branch on the same remote as the upstream branch you need to set push.default=current.

The advantage would be that it simplifies the code and it means we respect push.default=upstream.

The current implementation returns NULL if there is a push refspec exists but it does not match the current branch - in that case we should return a copy of the branch name instead.

Quoted 5 lines
Quoted 2 lines

I think it would be simpler to just return the full refname and let the caller shorten it.

We could to that but what's the benefit? Shouldn't a helper function reduce the work for the caller, not the other way around? 🤗

The benefit is that you get a sane interface rather that returning two different versions of the same string in two different ways (one from the function's return value and the other from a function parameter). It also matches what we do for the upstream branch.

Quoted 9 lines
Quoted 2 lines

Why are we checking for BRANCH_MODE_PUSH here? Don't we want to show this advice regardless of the mode?

Yeah, that's a good point. However, this will often lead to the advice being shown twice, see this test:

```
status --no-ahead-behind shows diverged from origin/main and ahead of feature branch
```

I can't seem to see that test. If we're printing the advice once for the upstream branch and once for the default push remote I think that would be ok.

Quoted 10 lines
Quoted 3 lines

If we don't want to show this can't we set show_divergance_adivce to false when we call this function - why is it guarded by BRANCH_MODE_PULL as well?

This will already have been shown the the pull branch (the upstream branch).

And in the cases when it's NOT shown for the pull branch but only the push branch has diverged, then pulling won't solve the problem anyway. (Unless you would try to pull from your push branch.)

But we set show_divergance_advice to false for the push branch so there is no need to check the flag.

Thanks

Phillip

#113 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 6 lines

It is hard to discuss this without knowing what actually breaks. Are you talking about the tests added in this series? If so that means we're expecting a different behavior to what "git push" actually does. As Ben has pointed out elsewhere in this thread, if you're pushing back to a different branch on the same remote as the upstream branch you need to set push.default=current.

Yes, it's my new tests that are breaking. Maybe it's easiest if you check out the seen branch which now has this logic, play with the code and run the tests to see when it breaks.

I designed the feature around 'push.default=current' which I use.

If we would design the feature around 'push.default=upstream' then what is the point? 🤗 Why do we need to show status for both an upstream and a push branch if we are already pushing to our upstream branch?

Quoted 4 lines

The benefit is that you get a sane interface rather that returning two different versions of the same string in two different ways (one from the function's return value and the other from a function parameter). It also matches what we do for the upstream branch.

That's a good point about matching what we do for upstream branch, I'll take a look.

Quoted 3 lines

I can't seem to see that test. If we're printing the advice once for the upstream branch and once for the default push remote I think that would be ok.

This test is also part of my patch 🤗

I disagree about showing the same advice twice.

Quoted 2 lines

But we set show_divergance_advice to false for the push branch so there is no need to check the flag.

Good point! I'll update it!

Harald

#114 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 11 lines · Harald Nordgren
Quoted 6 lines

It is hard to discuss this without knowing what actually breaks. Are you talking about the tests added in this series? If so that means we're expecting a different behavior to what "git push" actually does. As Ben has pointed out elsewhere in this thread, if you're pushing back to a different branch on the same remote as the upstream branch you need to set push.default=current.

Yes, it's my new tests that are breaking. Maybe it's easiest if you check out the seen branch which now has this logic, play with the code and run the tests to see when it breaks.

Quoted 5 lines

I designed the feature around 'push.default=current' which I use.

If we would design the feature around 'push.default=upstream' then what is the point? 🤗 Why do we need to show status for both an upstream and a push branch if we are already pushing to our upstream branch?

I'm not suggesting we design the feature around 'push.default=upstream', I'm suggesting that we design it to respect 'push.default' so it gives sensible output (i.e. something that resembles what "git push" would do) whatever the setting.

Quoted 7 lines
Quoted 4 lines

The benefit is that you get a sane interface rather that returning two different versions of the same string in two different ways (one from the function's return value and the other from a function parameter). It also matches what we do for the upstream branch.

That's a good point about matching what we do for upstream branch, I'll take a look.

Thanks, I think that would be cleaner

Quoted 5 lines
Quoted 3 lines

I can't seem to see that test. If we're printing the advice once for the upstream branch and once for the default push remote I think that would be ok.

This test is also part of my patch 🤗

Oh, I was looking in junio's "seen" branch from the 8th and it wasn't there. I've updated and I can see it now. I can see tests for

- upstream differs from the local branch, no push branch shown
- upstream and push branches differ from the local branch
- upstream and push branches match the local branch

I can't see a test for the local branch differing from the upstream branch when the push branch matches the local branch. As far as I can see in that case we don't show the advice when we should do (and we currently do show it)

Quoted 1 line

I disagree about showing the same advice twice.

I think it is less bad than not showing it when the upstream branch does not match the local branch.

Quoted 4 lines
Quoted 2 lines

But we set show_divergance_advice to false for the push branch so there is no need to check the flag.

Good point! I'll update it!

That's great

Thanks

Phillip

Quoted 2 lines

Harald

#115 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 4 lines

I'm not suggesting we design the feature around 'push.default=upstream', I'm suggesting that we design it to respect 'push.default' so it gives sensible output (i.e. something that resembles what "git push" would do) whatever the setting.

I guess I'm still not getting it, maybe explain it like I'm more stupid 😅

'push.default' does not change the output of git status, which shows a comparison with the tracking branch. I wrote the code to only report push branch status when it differs from the upstream. If we use 'push.default=upstream' they will be the same, so no need to report it twice.

Quoted 1 line

Thanks, I think that would be cleaner

It's in the latest patch now.

Quoted 10 lines

I can see tests for

- upstream differs from the local branch, no push branch shown
- upstream and push branches differ from the local branch
- upstream and push branches match the local branch

I can't see a test for the local branch differing from the upstream branch when the push branch matches the local branch. As far as I can see in that case we don't show the advice when we should do (and we currently do show it)

I added a test for this now, which I think proves that it's working correctly:

```
test_expect_success 'status with upstream ahead and push branch up to date' '
```

Harald

#116 lore raw

v18 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 174 ++++++++++++++++++++------
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 401 insertions(+), 35 deletions(-)

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

Range-diff vs v17:

1: b62a9feb4d ! 1: 451d7a4986 refactor format_branch_comparison in preparation

     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      -				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
      -	} else if (!sti) {
      +static void format_branch_comparison(struct strbuf *sb,
     -+				     int sti,
     ++				     bool up_to_date,
      +				     int ours, int theirs,
      +				     const char *branch_name,
      +				     enum ahead_behind_flags abf,
     -+				     int show_divergence_advice)
     ++				     bool show_divergence_advice)
      +{
     -+	if (!sti) {
     ++	if (up_to_date) {
       		strbuf_addf(sb,
       			_("Your branch is up to date with '%s'.\n"),
      -			base);
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
      +			strbuf_addstr(sb,
      +				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
      +	} else {
     -+		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
     ++		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
      +	}
      +
       	free(base);
 2:  1348542edc ! 2:  02476eb8e6 status: show comparison with push remote tracking branch
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
      +}
      +
       static void format_branch_comparison(struct strbuf *sb,
     - 				     int sti,
     + 				     bool up_to_date,
       				     int ours, int theirs,
       				     const char *branch_name,
       				     enum ahead_behind_flags abf,
      +				     enum branch_mode_flags advice_flags,
     - 				     int show_divergence_advice)
     + 				     bool show_divergence_advice)
       {
     - 	if (!sti) {
     ++	bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
     ++		advice_enabled(ADVICE_STATUS_HINTS);
     ++	bool want_pull_advice = (advice_flags & BRANCH_MODE_PULL) &&
     ++		advice_enabled(ADVICE_STATUS_HINTS);
     ++	bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
     ++		show_divergence_advice &&
     ++		advice_enabled(ADVICE_STATUS_HINTS);
     ++
     + 	if (up_to_date) {
     + 		strbuf_addf(sb,
     + 			_("Your branch is up to date with '%s'.\n"),
      @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       		strbuf_addf(sb,
       			    _("Your branch and '%s' refer to different commits.\n"),
       			    branch_name);
      -		if (advice_enabled(ADVICE_STATUS_HINTS))
     -+		if ((advice_flags & BRANCH_MODE_PUSH) &&
     -+		    advice_enabled(ADVICE_STATUS_HINTS))
     ++		if (want_push_advice)
       			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
       				    "git status --ahead-behind");
       	} else if (!theirs) {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   ours),
       			branch_name, ours);
      -		if (advice_enabled(ADVICE_STATUS_HINTS))
     -+		if ((advice_flags & BRANCH_MODE_PUSH) &&
     -+		    advice_enabled(ADVICE_STATUS_HINTS))
     ++		if (want_push_advice)
       			strbuf_addstr(sb,
       				_("  (use \"git push\" to publish your local commits)\n"));
       	} else if (!ours) {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   theirs),
       			branch_name, theirs);
      -		if (advice_enabled(ADVICE_STATUS_HINTS))
     -+		if ((advice_flags & BRANCH_MODE_PULL) &&
     -+		    advice_enabled(ADVICE_STATUS_HINTS))
     ++		if (want_pull_advice)
       			strbuf_addstr(sb,
       				_("  (use \"git pull\" to update your local branch)\n"));
       	} else {
     @@ remote.c: static void format_branch_comparison(struct strbuf *sb,
       			   ours + theirs),
       			branch_name, ours, theirs);
      -		if (show_divergence_advice &&
     -+		if ((advice_flags & BRANCH_MODE_PULL) &&
     -+		    show_divergence_advice &&
     - 		    advice_enabled(ADVICE_STATUS_HINTS))
     +-		    advice_enabled(ADVICE_STATUS_HINTS))
     ++		if (want_divergence_advice)
       			strbuf_addstr(sb,
       				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
     + 	}
      @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	const char *full_base;
       	char *base;
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			strbuf_addstr(sb,
       				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
       	} else {
     --		format_branch_comparison(sb, sti, ours, theirs, base, abf, show_divergence_advice);
     -+		format_branch_comparison(sb, sti, ours, theirs, base, abf,
     +-		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
     ++		format_branch_comparison(sb, !sti, ours, theirs, base, abf,
      +					 base_branch_modes, show_divergence_advice);
      +	}
      +
      +	if (push_branch_modes & BRANCH_MODE_PUSH) {
      +		strbuf_addstr(sb, "\n");
     -+		format_branch_comparison(sb, push_sti, push_ours, push_theirs, push, abf,
     -+					 push_branch_modes, 0);
     ++		format_branch_comparison(sb, !push_sti, push_ours, push_theirs, push, abf,
     ++					 push_branch_modes, show_divergence_advice);
       	}
       
       	free(base);
Signature · 1 line
gitgitgadget
#117 lore raw

PATCH v18 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 34 deletions(-)
remote.c +48 -34
index 59b3715120..eba013b6b4 100644
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
-{
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+				     bool up_to_date,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     bool show_divergence_advice)
+{
+	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#118 lore raw

PATCH v18 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 | 102 ++++++++++++++-
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 358 insertions(+), 6 deletions(-)
remote.c +96 -6
index eba013b6b4..4c3f18c3c1 100644
@@ -29,6 +29,11 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum branch_mode_flags {
+	BRANCH_MODE_PULL = (1 << 0),
+	BRANCH_MODE_PUSH = (1 << 1),
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     bool up_to_date,
 				     int ours, int theirs,
 				     const char *branch_name,
 				     enum ahead_behind_flags abf,
+				     enum branch_mode_flags advice_flags,
 				     bool show_divergence_advice)
 {
+	bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+	bool want_pull_advice = (advice_flags & BRANCH_MODE_PULL) &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+	bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
+		show_divergence_advice &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+
 	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ static void format_branch_comparison(struct strbuf *sb,
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
 			    branch_name);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_push_advice)
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
 	} else if (!theirs) {
@@ -2261,7 +2328,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
 			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_push_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_pull_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_divergence_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
@@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	enum branch_mode_flags push_branch_modes = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2314,6 +2385,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_branch_modes = BRANCH_MODE_PULL;
+			push_branch_modes = BRANCH_MODE_PUSH;
+		}
+	}
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2403,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf,
+					 base_branch_modes, show_divergence_advice);
+	}
+
+	if (push_branch_modes & BRANCH_MODE_PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, !push_sti, push_ours, push_theirs, push, abf,
+					 push_branch_modes, show_divergence_advice);
 	}
 
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +262 -0

266 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#119 lore raw

v19 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected] cc: Patrick Steinhardt [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 174 ++++++++++++++++++++------
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 401 insertions(+), 35 deletions(-)

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

Range-diff vs v18:

 1:  451d7a4986 = 1:  451d7a4986 refactor format_branch_comparison in preparation
 2:  02476eb8e6 ! 2:  dc8ab23158 status: show comparison with push remote tracking branch
     @@ remote.c
       
       enum map_direction { FROM_SRC, FROM_DST };
       
     -+enum branch_mode_flags {
     ++enum {
      +	BRANCH_MODE_PULL = (1 << 0),
      +	BRANCH_MODE_PUSH = (1 << 1),
      +};
     @@ remote.c: int stat_tracking_info(struct branch *branch, int *num_ours, int *num_
       				     int ours, int theirs,
       				     const char *branch_name,
       				     enum ahead_behind_flags abf,
     -+				     enum branch_mode_flags advice_flags,
     ++				     unsigned flags,
       				     bool show_divergence_advice)
       {
     -+	bool want_push_advice = (advice_flags & BRANCH_MODE_PUSH) &&
     ++	bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
      +		advice_enabled(ADVICE_STATUS_HINTS);
     -+	bool want_pull_advice = (advice_flags & BRANCH_MODE_PULL) &&
     ++	bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
      +		advice_enabled(ADVICE_STATUS_HINTS);
     -+	bool want_divergence_advice = (advice_flags & BRANCH_MODE_PULL) &&
     ++	bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
      +		show_divergence_advice &&
      +		advice_enabled(ADVICE_STATUS_HINTS);
      +
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       	const char *full_base;
       	char *base;
       	int upstream_is_gone = 0;
     -+	enum branch_mode_flags base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
     ++	unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
      +	int push_ours, push_theirs, push_sti;
      +	char *full_push = NULL;
      +	char *push = NULL;
     -+	enum branch_mode_flags push_branch_modes = 0;
     ++	unsigned push_branch_modes = 0;
       
       	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
       	if (sti < 0) {
Signature · 1 line
gitgitgadget
#120 lore raw

PATCH v19 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 82 +++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 48 insertions(+), 34 deletions(-)
remote.c +48 -34
index 59b3715120..eba013b6b4 100644
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
-{
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+				     bool up_to_date,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     bool show_divergence_advice)
+{
+	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,7 +2271,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2305,12 +2284,47 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
 			   ours + theirs),
-			base, ours, theirs);
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget
#121 lore raw

Re: [PATCH v19 1/2] refactor format_branch_comparison in preparation

Quoted 10 lines · Harald Nordgren via GitGitGadget
remote.c +8 -0
+static void format_branch_comparison(struct strbuf *sb,
+				     bool up_to_date,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     bool show_divergence_advice)
+{
+	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),

remote.c int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, · line 2257

Quoted 10 lines
remote.c +2 -2
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");

OK. We _could_, just like we moved the "upstream_gone" case to the caller, move the "up to date" case to the caller as well, but this reads well now. And we do not need the (!ours && !theirs) case in this if/else if/... cascade. Very good.

Quoted 36 lines
remote.c +33 -0
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
#122 lore raw

PATCH v19 2/2 status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git status" on a branch that follows a remote branch compares commits on the current branch and the remote-tracking branch it builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch, but you also want to track progress relative to the push destination tracking branch (which may differ from the upstream branch), git status now shows an additional comparison.

When the upstream tracking branch differs from the push destination tracking branch, git status shows both the comparison with the upstream tracking branch (as before) and an additional comparison with the push destination tracking branch. The push branch comparison appears on a separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is origin/feature:
On branch feature
Your branch and 'origin/main' have diverged,
and have 3 and 1 different commits each, respectively.
(use "git pull" if you want to integrate the remote branch with yours)

Your branch is ahead of 'origin/feature' by 1 commit.
(use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch differs from the upstream tracking branch, even if they are on the same remote.

---
 remote.c                 | 102 ++++++++++++++-
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 358 insertions(+), 6 deletions(-)
remote.c +96 -6
index eba013b6b4..b8d2d1360a 100644
@@ -29,6 +29,11 @@
 
 enum map_direction { FROM_SRC, FROM_DST };
 
+enum {
+	BRANCH_MODE_PULL = (1 << 0),
+	BRANCH_MODE_PUSH = (1 << 1),
+};
+
 struct counted_string {
 	size_t len;
 	const char *s;
@@ -2237,13 +2242,75 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+static char *get_remote_push_branch(struct branch *branch, char **full_ref_out)
+{
+	struct remote *remote;
+	const char *push_remote;
+	char *push_dst = NULL;
+	char *tracking_ref;
+	const char *resolved;
+	char *ret;
+
+	if (!branch)
+		return NULL;
+
+	push_remote = pushremote_for_branch(branch, NULL);
+	if (!push_remote)
+		return NULL;
+
+	remote = remotes_remote_get(the_repository, push_remote);
+	if (!remote)
+		return NULL;
+
+	push_dst = remote_ref_for_branch(branch, 1);
+	if (!push_dst) {
+		if (remote->push.nr)
+			return NULL;
+		push_dst = xstrdup(branch->refname);
+	}
+
+	tracking_ref = (char *)tracking_for_push_dest(remote, push_dst, NULL);
+	free(push_dst);
+
+	if (!tracking_ref)
+		return NULL;
+
+	resolved = refs_resolve_ref_unsafe(
+		get_main_ref_store(the_repository),
+		tracking_ref,
+		RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (!resolved) {
+		free(tracking_ref);
+		return NULL;
+	}
+
+	if (full_ref_out)
+		*full_ref_out = xstrdup(resolved);
+
+	ret = refs_shorten_unambiguous_ref(
+		get_main_ref_store(the_repository), resolved, 0);
+	free(tracking_ref);
+	return ret;
+}
+
 static void format_branch_comparison(struct strbuf *sb,
 				     bool up_to_date,
 				     int ours, int theirs,
 				     const char *branch_name,
 				     enum ahead_behind_flags abf,
+				     unsigned flags,
 				     bool show_divergence_advice)
 {
+	bool want_push_advice = (flags & BRANCH_MODE_PUSH) &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+	bool want_pull_advice = (flags & BRANCH_MODE_PULL) &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+	bool want_divergence_advice = (flags & BRANCH_MODE_PULL) &&
+		show_divergence_advice &&
+		advice_enabled(ADVICE_STATUS_HINTS);
+
 	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
@@ -2252,7 +2319,7 @@ static void format_branch_comparison(struct strbuf *sb,
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
 			    branch_name);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_push_advice)
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
 	} else if (!theirs) {
@@ -2261,7 +2328,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
 			branch_name, ours);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_push_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
 	} else if (!ours) {
@@ -2272,7 +2339,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_pull_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_divergence_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
@@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	unsigned push_branch_modes = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {
@@ -2314,6 +2385,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);
+		if (push_sti >= 0) {
+			base_branch_modes = BRANCH_MODE_PULL;
+			push_branch_modes = BRANCH_MODE_PUSH;
+		}
+	}
+
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2322,10 +2403,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf,
+					 base_branch_modes, show_divergence_advice);
+	}
+
+	if (push_branch_modes & BRANCH_MODE_PUSH) {
+		strbuf_addstr(sb, "\n");
+		format_branch_comparison(sb, !push_sti, push_ours, push_theirs, push, abf,
+					 push_branch_modes, show_divergence_advice);
 	}
 
 	free(base);
+	free(full_push);
+	free(push);
 	return 1;
 }
 
t/t6040-tracking-info.sh +262 -0

266 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#123 lore raw

Re: [PATCH v19 2/2] status: show comparison with push remote tracking branch

remote.c static void format_branch_comparison(struct strbuf *sb, · line 2358

Quoted 10 lines · Harald Nordgren via GitGitGadget
remote.c +1 -2
@@ -2285,8 +2352,7 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (show_divergence_advice &&
-		    advice_enabled(ADVICE_STATUS_HINTS))
+		if (want_divergence_advice)
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}

This is not a new issue introduced by this series, but it is curious there is "ours + theirs" there #leftoverbits.

It is part of ngetext() aka Q_() call, used this way:

} else {
strbuf_addf(sb,
Q_("Your branch and '%s' have diverged,\n"
"and have %d and %d different commit each, " "respectively.\n",
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, " "respectively.\n",
ours + theirs),
branch_name, ours, theirs);

But in this if/else if/... cascade, we have ruled out cases where either/both of ours and theirs is 0 already, so ours + theirs has to be at least two (because each has to be at least one). Q_() based on a value that is always plural would always use the latter form (i.e., "commits").

#124 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 4 lines

This is not a new issue introduced by this series, but it is curious there is "ours + theirs" there #leftoverbits.

It is part of ngetext() aka Q_() call, used this way:

Agreed, I thought about this one too. Will update it!

Harald

#125 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 6 lines · Harald Nordgren
Quoted 4 lines

This is not a new issue introduced by this series, but it is curious there is "ours + theirs" there #leftoverbits.

It is part of ngetext() aka Q_() call, used this way:

Agreed, I thought about this one too. Will update it!

Please do so as a separate patch, not mix it into a patch that does something else.

I do not think we mind even if it is left outside the current topic we are discussing, if it distracts the main theme of the topic.

#126 lore raw

Re: [PATCH v19 2/2] status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget
Quoted 13 lines · Harald Nordgren via GitGitGadget
remote.c +5 -0
@@ -2303,6 +2369,11 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	const char *full_base;
 	char *base;
 	int upstream_is_gone = 0;
+	unsigned base_branch_modes = BRANCH_MODE_PULL | BRANCH_MODE_PUSH;
+	int push_ours, push_theirs, push_sti;
+	char *full_push = NULL;
+	char *push = NULL;
+	unsigned push_branch_modes = 0;
 
 	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
 	if (sti < 0) {

remote.c int format_tracking_info(struct branch *branch, struct strbuf *sb, · line 2391

Quoted 7 lines
remote.c +4 -0
@@ -2314,6 +2385,16 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
 					    full_base, 0);
 
+	push = get_remote_push_branch(branch, &full_push);
+	if (push && strcmp(base, push)) {
+		push_sti = stat_branch_pair(branch->refname, full_push,
+					   &push_ours, &push_theirs, abf);

Why is this variable called "push_sti"? Calling the return value of stat_tracking_info() "sti" was klumsy but understandable. It would have been much easier to follow the code if the variable were named after what it _means_ in this particular caller's code flow, like "cmp_fetch" (comparison on the fetching side, by convention negative signals an error, and zero signals 'the same'). Perhaps rename "sti" and "push_sti" at the same time to make them more symmetric?

Other than this minor nit, this step looks nicely done (and the previous one is also good).

Thanks.

#127 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

It's following my naming scheme where each push version of a base variable tacks on _push at the end. Since the push logic introduces a second version of most variables, it's hard to make it fully logical when the base versions still retain their "unqualified" names.

I can rename the sti's to 'cmp_fetch' and 'cmp_fetch_push', but does it help?

Probably best would be to create a struct for the common variables between base and push cases. But that's a large refactoring, so maybe better done as a separate follow-up to all of this instead? Here's what is looks like:

```
diff --git a/remote.c b/remote.c
index eba013b6b4..581a62c266 100644
--- a/remote.c
+++ b/remote.c
@@ -2237,41 +2237,47 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
+struct format_branch {
+	int ours, theirs;
+	int cmp_fetch;
+	bool up_to_date;
+	const char *full;
+	char *name;
+};
+
 static void format_branch_comparison(struct strbuf *sb,
-				     bool up_to_date,
-				     int ours, int theirs,
-				     const char *branch_name,
+				     const struct format_branch *branch,
 				     enum ahead_behind_flags abf,
 				     bool show_divergence_advice)
 {
-	if (up_to_date) {
+	if (branch->up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			branch_name);
+			branch->name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    branch_name);
+			    branch->name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
-	} else if (!theirs) {
+	} else if (!branch->theirs) {
 		strbuf_addf(sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
-			   ours),
-			branch_name, ours);
+			   branch->ours),
+			branch->name, branch->ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
-	} else if (!ours) {
+	} else if (!branch->ours) {
 		strbuf_addf(sb,
 			Q_("Your branch is behind '%s' by %d commit, "
 			       "and can be fast-forwarded.\n",
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
-			   theirs),
-			branch_name, theirs);
+			   branch->theirs),
+			branch->name, branch->theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
@@ -2283,8 +2289,8 @@ static void format_branch_comparison(struct strbuf *sb,
 			   "Your branch and '%s' have diverged,\n"
 			       "and have %d and %d different commits each, "
 			       "respectively.\n",
-			   ours + theirs),
-			branch_name, ours, theirs);
+			   branch->ours + branch->theirs),
+			branch->name, branch->ours, branch->theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
@@ -2299,33 +2305,32 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			 enum ahead_behind_flags abf,
 			 int show_divergence_advice)
 {
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
 	int upstream_is_gone = 0;
+	struct format_branch base;
 
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
+	base.cmp_fetch = stat_tracking_info(branch, &base.ours, &base.theirs, &base.full, 0, abf);
+	base.up_to_date = !base.cmp_fetch;
+	if (base.cmp_fetch < 0) {
+		if (!base.full)
 			return 0;
 		upstream_is_gone = 1;
 	}
 
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
+	base.name = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    base.full, 0);
 
 	if (upstream_is_gone) {
 		strbuf_addf(sb,
 			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
+			base.name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
 	} else {
-		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+		format_branch_comparison(sb, &base, abf, show_divergence_advice);
 	}
 
-	free(base);
+	free(base.name);
 	return 1;
 }
```

Harald

#128 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 7 lines

Why is this variable called "push_sti"? Calling the return value of stat_tracking_info() "sti" was klumsy but understandable. It would have been much easier to follow the code if the variable were named after what it _means_ in this particular caller's code flow, like "cmp_fetch" (comparison on the fetching side, by convention negative signals an error, and zero signals 'the same'). Perhaps rename "sti" and "push_sti" at the same time to make them more symmetric?

I renamed to 'push_cmp_fetch' and 'push_cmp_fetch' now. I think on my first reading I misunderstood your comment.

Quoted 2 lines

Other than this minor nit, this step looks nicely done (and the previous one is also good).

Thanks a lot!

Harald

#129 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

In reply to Harald Nordgren
Quoted 2 lines · Harald Nordgren

I can rename the sti's to 'cmp_fetch' and 'cmp_fetch_push', but does it help?

Not at all. Unless the contrast were "something_fetch" vs "something_push", that is. And that something being cryptic "sti" (recall my comment on it, being the name of the function that returns the value, which is less understandable than using words that signals what the variable _means_), would not make much sense for the "push" direction, as "sti" is not even an abbreviation for the function that gives the value. "cmp" (or "compare" for that matter) still has the ambiguity "compare with what and what for?", but at least it would be better than "sti".

#130 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 9 lines

Not at all. Unless the contrast were "something_fetch" vs "something_push", that is. And that something being cryptic "sti" (recall my comment on it, being the name of the function that returns the value, which is less understandable than using words that signals what the variable _means_), would not make much sense for the "push" direction, as "sti" is not even an abbreviation for the function that gives the value. "cmp" (or "compare" for that matter) still has the ambiguity "compare with what and what for?", but at least it would be better than "sti".

Maybe simplest would be 'upstream_diff' and 'push_diff'? But this begs the question of why not all the old 'ours', 'theirs', etc variables are missing an 'upstream_' prefix.

I feel like this can turn into endless refactoring, latest version now has 'cmp_fetch' and 'push_cmp_fetch'.

I have on my TODO list to fix the pluralization of the "diverged" text after this patch series has been merged. I'm also working on refactoring this by introducing a struct to pass data to 'format_branch_comparison'. This has the very nice benefit on natural namespacing of variables when they become fields in struct variables like 'upstream_branch' and 'push_branch'.

In the meantime I think I will leave this patch series for now 🤗

Harald

#131 lore raw

Re: [PATCH v17 1/2] refactor format_branch_comparison in preparation

Quoted 1 line · Harald Nordgren

In the meantime I think I will leave this patch series for now 🤗

I think we are reaching the point of diminishing returns after polishing the series enough at the 22nd iteration.

Let's wait for a few days to see if what others find in the series and then mark the topic for 'next'. Hopefully any minor nits can be addressed later as a follow-up, just like the Q_(ours+theirs) thing.

Thanks.

#132 lore raw

v20 cover status: show comparison with push remote tracking branch

In reply to Harald Nordgren via GitGitGadget

cc: Chris Torek [email protected] cc: Yee Cheng Chin [email protected] cc: "brian m. carlson" [email protected]
cc: Ben Knoble [email protected] cc: "Kristoffer Haugsbakk" [email protected] cc: Phillip Wood [email protected]
cc: Nico Williams [email protected] cc: Patrick Steinhardt [email protected]

Harald Nordgren (2):
refactor format_branch_comparison in preparation
status: show comparison with push remote tracking branch

 remote.c                 | 183 ++++++++++++++++++++-------
 t/t6040-tracking-info.sh | 262 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 403 insertions(+), 42 deletions(-)

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

Range-diff vs v19:

1: 451d7a4986 ! 1: bb3e00863b refactor format_branch_comparison in preparation
@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
if (advice_enabled(ADVICE_STATUS_HINTS))
strbuf_addstr(sb,
_(" (use \"git pull\" to update your local branch)\n"));
-@@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
- "and have %d and %d different commits each, "
- "respectively.\n",
- ours + theirs),
+ } else {
+ strbuf_addf(sb,
+- Q_("Your branch and '%s' have diverged,\n"
+- "and have %d and %d different commit each, "
+- "respectively.\n",
+- "Your branch and '%s' have diverged,\n"
+- "and have %d and %d different commits each, "
+- "respectively.\n",
+- ours + theirs),
- base, ours, theirs);
++ "Your branch and '%s' have diverged,\n"
++ "and have %d and %d different commits each, respectively.\n",
+ branch_name, ours, theirs);
if (show_divergence_advice &&
advice_enabled(ADVICE_STATUS_HINTS))
2: dc8ab23158 ! 2: 050197eac3 status: show comparison with push remote tracking branch
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
_(" (use \"git pull\" to update your local branch)\n"));
} else {
@@ remote.c: static void format_branch_comparison(struct strbuf *sb,
- "respectively.\n",
- ours + theirs),
+ "Your branch and '%s' have diverged,\n"
+ "and have %d and %d different commits each, respectively.\n",
branch_name, ours, theirs);
- if (show_divergence_advice &&
- advice_enabled(ADVICE_STATUS_HINTS))

Signature · 1 line
gitgitgadget
#133 lore raw

PATCH v20 1/2 refactor format_branch_comparison in preparation

From: Harald Nordgren <[email protected]>

Refactor format_branch_comparison function in preparation for showing comparison with push remote tracking branch.

---
 remote.c | 91 +++++++++++++++++++++++++++++++-------------------------
 1 file changed, 50 insertions(+), 41 deletions(-)
remote.c +50 -41
index 59b3715120..b053d4e443 100644
@@ -2237,42 +2237,21 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs,
 	return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf);
 }
 
-/*
- * Return true when there is anything to report, otherwise false.
- */
-int format_tracking_info(struct branch *branch, struct strbuf *sb,
-			 enum ahead_behind_flags abf,
-			 int show_divergence_advice)
-{
-	int ours, theirs, sti;
-	const char *full_base;
-	char *base;
-	int upstream_is_gone = 0;
-
-	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
-	if (sti < 0) {
-		if (!full_base)
-			return 0;
-		upstream_is_gone = 1;
-	}
-
-	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
-					    full_base, 0);
-	if (upstream_is_gone) {
-		strbuf_addf(sb,
-			_("Your branch is based on '%s', but the upstream is gone.\n"),
-			base);
-		if (advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
-	} else if (!sti) {
+static void format_branch_comparison(struct strbuf *sb,
+				     bool up_to_date,
+				     int ours, int theirs,
+				     const char *branch_name,
+				     enum ahead_behind_flags abf,
+				     bool show_divergence_advice)
+{
+	if (up_to_date) {
 		strbuf_addf(sb,
 			_("Your branch is up to date with '%s'.\n"),
-			base);
+			branch_name);
 	} else if (abf == AHEAD_BEHIND_QUICK) {
 		strbuf_addf(sb,
 			    _("Your branch and '%s' refer to different commits.\n"),
-			    base);
+			    branch_name);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addf(sb, _("  (use \"%s\" for details)\n"),
 				    "git status --ahead-behind");
@@ -2281,7 +2260,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			Q_("Your branch is ahead of '%s' by %d commit.\n",
 			   "Your branch is ahead of '%s' by %d commits.\n",
 			   ours),
-			base, ours);
+			branch_name, ours);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git push\" to publish your local commits)\n"));
@@ -2292,25 +2271,55 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 			   "Your branch is behind '%s' by %d commits, "
 			       "and can be fast-forwarded.\n",
 			   theirs),
-			base, theirs);
+			branch_name, theirs);
 		if (advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" to update your local branch)\n"));
 	} else {
 		strbuf_addf(sb,
-			Q_("Your branch and '%s' have diverged,\n"
-			       "and have %d and %d different commit each, "
-			       "respectively.\n",
-			   "Your branch and '%s' have diverged,\n"
-			       "and have %d and %d different commits each, "
-			       "respectively.\n",
-			   ours + theirs),
-			base, ours, theirs);
+			"Your branch and '%s' have diverged,\n"
+			       "and have %d and %d different commits each, respectively.\n",
+			branch_name, ours, theirs);
 		if (show_divergence_advice &&
 		    advice_enabled(ADVICE_STATUS_HINTS))
 			strbuf_addstr(sb,
 				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
 	}
+}
+
+/*
+ * Return true when there is anything to report, otherwise false.
+ */
+int format_tracking_info(struct branch *branch, struct strbuf *sb,
+			 enum ahead_behind_flags abf,
+			 int show_divergence_advice)
+{
+	int ours, theirs, sti;
+	const char *full_base;
+	char *base;
+	int upstream_is_gone = 0;
+
+	sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf);
+	if (sti < 0) {
+		if (!full_base)
+			return 0;
+		upstream_is_gone = 1;
+	}
+
+	base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+					    full_base, 0);
+
+	if (upstream_is_gone) {
+		strbuf_addf(sb,
+			_("Your branch is based on '%s', but the upstream is gone.\n"),
+			base);
+		if (advice_enabled(ADVICE_STATUS_HINTS))
+			strbuf_addstr(sb,
+				_("  (use \"git branch --unset-upstream\" to fixup)\n"));
+	} else {
+		format_branch_comparison(sb, !sti, ours, theirs, base, abf, show_divergence_advice);
+	}
+
 	free(base);
 	return 1;
 }
Signature · 1 line
gitgitgadget