Git development
 help / color / mirror / Atom feed
From: Phillip Wood <phillip.wood123@gmail.com>
To: Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>,
	git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
	"D. Ben Knoble" <ben.knoble@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: Re: [PATCH v3 2/2] pretty: add %gb to name the branch of each reflog entry
Date: Tue, 3 Nov 2026 16:16:54 +0000	[thread overview]
Message-ID: <49144eb0-9755-4843-930e-ae7b3b519080@gmail.com> (raw)
In-Reply-To: <dee548f9b1ca11feef2d74d0384c5c3c2e6c20d7.1793090327.git.gitgitgadget@gmail.com>

Hi Harald

On 27/10/2026 08:38, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
> 
> Add a "%gb" placeholder that names the branch HEAD was on when each
> entry was recorded:
> 
>      $ git log -g --format='%h %gd: (%gb) %gs'
>      9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
>      9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
>      1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
>      1a2b3c4 HEAD@{3}: (main) commit: fix typo
>      5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main

Looks good

> The branch is read from the entries that branch switches, branch
> renames and rebases leave behind.

"the entries created by ..."?
> The entries of a rebase are named
> after the branch being rebased.

This sounds very LLM, "labelled with the branch being rebased"?

> When HEAD was detached, the entry
> shows "detached at <commit>" with the commit HEAD was at when it
> moved on.

I wondered if that was a bit verbose, but having tried it, it is no 
longer than some of my branch names.

> When nothing older in the reflog says which branch a rebase
> started on, the rebase is taken to have started on the branch it
> rebased. For the reflog of any other ref, "%gb" is the name of that
> ref.

I'm seeing some strange behavior when GIT_REFLOG_ACTION is set, below is 
the output of "git reflog --format='%h %gd %gs (%gb)'" for my local 
HEAD. I've added comments above the lines where the branch name is 
incorrect. The "sync switch" entries come from running "git rebase" with 
GIT_REFLOG_ACTION set.

be8e0d55ec HEAD@{0} reset: moving to HEAD (sequencer-delete-REBASE_HEAD)
be8e0d55ec HEAD@{1} sync switch: checkout sequencer-delete-REBASE_HEAD 
(sequencer-delete-REBASE_HEAD)
# should be fix-nth-prior-checkout
64822b4a3b HEAD@{2} sync switch: checkout fix-nth-prior-checkout 
(sequencer-delete-REBASE_HEAD)
# should be detached at 64882b4a3b
64822b4a3b HEAD@{3} rebase: fast-forward (sequencer-delete-REBASE_HEAD)
# should be detached at 64822b4a3b
bf19b60d89 HEAD@{4} rebase (start): checkout @^ 
(sequencer-delete-REBASE_HEAD)
64822b4a3b HEAD@{5} rebase: fast-forward (detached at 64822b4a3b)
bf19b60d89 HEAD@{6} rebase (start): checkout fix-nth-prior-checkout^ 
(detached at 64822b4a3b)
be8e0d55ec HEAD@{7} rebase: checkout sequencer-delete-REBASE_HEAD 
(sequencer-delete-REBASE_HEAD)
be8e0d55ec HEAD@{8} rebase (start): checkout 
sequencer-delete-REBASE_HEAD (detached at be8e0d55ec)
64822b4a3b HEAD@{9} checkout: moving from 
64822b4a3ba71e5a823002cd5ebe0fa5fb4b3a0c to fix-nth-prior-checkout 
(fix-nth-prior-checkout)

Thanks

Phillip

> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
>   Documentation/pretty-formats.adoc |   3 +
>   pretty.c                          |   4 +
>   reflog-walk.c                     | 156 ++++++++++++++++++++++++++++--
>   reflog-walk.h                     |   2 +
>   t/t1414-reflog-walk.sh            | 122 +++++++++++++++++++++++
>   5 files changed, 281 insertions(+), 6 deletions(-)
> 
> diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc
> index 2ae0eb11a9..a8d00397c4 100644
> --- a/Documentation/pretty-formats.adoc
> +++ b/Documentation/pretty-formats.adoc
> @@ -334,6 +334,9 @@ endif::git-rev-list[]
>   +%gE+:: reflog identity email (respecting .mailmap, see
>   	linkgit:git-shortlog[1] or linkgit:git-blame[1])
>   +%gs+:: reflog subject
> ++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
> +	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
> +	of any other ref, this is the shortened refname.
>   ++%(trailers++`[:<option>,...]`++)++::
>   display the trailers of the body as interpreted by
>   linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
> diff --git a/pretty.c b/pretty.c
> index d8a9f370f6..1b3f7e2dc3 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -1637,6 +1637,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
>   			if (c->pretty_ctx->reflog_info)
>   				get_reflog_message(sb, c->pretty_ctx->reflog_info);
>   			return 2;
> +		case 'b':	/* reflog branch */
> +			if (c->pretty_ctx->reflog_info)
> +				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
> +			return 2;
>   		case 'n':
>   		case 'N':
>   		case 'e':
> diff --git a/reflog-walk.c b/reflog-walk.c
> index 4dbeaa93a7..45b5dc09b5 100644
> --- a/reflog-walk.c
> +++ b/reflog-walk.c
> @@ -3,7 +3,10 @@
>   #include "git-compat-util.h"
>   #include "commit.h"
>   #include "refs.h"
> +#include "reflog.h"
>   #include "diff.h"
> +#include "hex.h"
> +#include "object-name.h"
>   #include "repository.h"
>   #include "revision.h"
>   #include "string-list.h"
> @@ -18,8 +21,10 @@ struct complete_reflogs {
>   		timestamp_t timestamp;
>   		int tz;
>   		char *message;
> +		char *branch;
>   	} *items;
>   	int nr, alloc;
> +	bool branches_marked;
>   };
>   
>   static int read_one_reflog(const char *refname UNUSED,
> @@ -38,6 +43,7 @@ static int read_one_reflog(const char *refname UNUSED,
>   	item->timestamp = timestamp;
>   	item->tz = tz;
>   	item->message = xstrdup(message);
> +	item->branch = NULL;
>   	array->nr++;
>   	return 0;
>   }
> @@ -52,6 +58,7 @@ static void free_complete_reflog(struct complete_reflogs *array)
>   	for (i = 0; i < array->nr; i++) {
>   		free(array->items[i].email);
>   		free(array->items[i].message);
> +		free(array->items[i].branch);
>   	}
>   	free(array->items);
>   	free(array->ref);
> @@ -65,6 +72,113 @@ static void complete_reflogs_clear(void *util, const char *str UNUSED)
>   	free_complete_reflog(array);
>   }
>   
> +static char *branch_label(const char *name, size_t len,
> +			  const struct object_id *oid)
> +{
> +	if (name) {
> +		struct object_id hex_oid;
> +		char *branch;
> +
> +		if (skip_prefix(name, "refs/heads/", &name))
> +			len -= strlen("refs/heads/");
> +		branch = xmemdupz(name, len);
> +		if (strcmp(branch, "HEAD") && get_oid_hex(branch, &hex_oid) &&
> +		    !check_refname_format(branch, REFNAME_ALLOW_ONELEVEL))
> +			return branch;
> +		free(branch);
> +	}
> +	return xstrfmt("detached at %s",
> +		       repo_find_unique_abbrev(the_repository, oid,
> +					       DEFAULT_ABBREV));
> +}
> +
> +static const char *skip_rebase_step(const char *message, const char *step)
> +{
> +	const char *p = strstr(message, " (");
> +	const char *colon = strstr(message, ": ");
> +
> +	if (!p || (colon && colon < p) ||
> +	    !skip_prefix(p + 2, step, &p) || !skip_prefix(p, "): ", &p))
> +		return NULL;
> +	return p;
> +}
> +
> +struct branch_move {
> +	const char *from, *to;
> +	size_t from_len, to_len;
> +};
> +
> +static bool parse_branch_move(const char *message, struct branch_move *move)
> +{
> +	const char *to;
> +
> +	move->from = move->to = NULL;
> +	move->from_len = move->to_len = 0;
> +	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) ||
> +	    skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from)) {
> +		to = strstr(move->from, " to ");
> +		if (!to)
> +			return false;
> +		move->from_len = to - move->from;
> +		move->to = to + strlen(" to ");
> +	} else if (((to = skip_rebase_step(message, REFLOG_STEP_FINISH)) ||
> +		    (to = skip_rebase_step(message, REFLOG_STEP_ABORT))) &&
> +		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
> +		move->from = move->to = to;
> +		move->from_len = strcspn(to, "\n");
> +	} else if (skip_prefix(message, "rebase: " REFLOG_CHECKOUT, &to)) {
> +		move->to = to;
> +	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
> +		return false;
> +	}
> +	if (move->to)
> +		move->to_len = strcspn(move->to, "\n");
> +	return true;
> +}
> +
> +static void mark_branches(struct complete_reflogs *array)
> +{
> +	struct ref_store *refs = get_main_ref_store(the_repository);
> +	struct object_id oid;
> +	const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0, &oid, NULL);
> +	char *branch = NULL;
> +	int unknown = array->nr;
> +
> +	if (head)
> +		branch = branch_label(head, strlen(head), &oid);
> +
> +	for (int i = array->nr - 1; i >= 0; i--) {
> +		struct reflog_info *item = &array->items[i];
> +		struct branch_move move;
> +
> +		if (!parse_branch_move(item->message, &move)) {
> +			item->branch = xstrdup_or_null(branch);
> +			continue;
> +		}
> +
> +		if (!branch) {
> +			branch = branch_label(move.to, move.to_len,
> +					      &array->items[unknown - 1].noid);
> +			for (int j = i + 1; j < unknown; j++)
> +				array->items[j].branch = xstrdup(branch);
> +		}
> +		item->branch = branch;
> +		if (move.from) {
> +			branch = branch_label(move.from, move.from_len,
> +					      &item->ooid);
> +		} else {
> +			branch = NULL;
> +			unknown = i;
> +		}
> +	}
> +
> +	if (!branch && unknown < array->nr)
> +		for (int i = 0; i < unknown; i++)
> +			array->items[i].branch =
> +				xstrdup(array->items[unknown].branch);
> +	free(branch);
> +}
> +
>   static struct complete_reflogs *read_complete_reflog(const char *ref)
>   {
>   	struct complete_reflogs *reflogs =
> @@ -234,6 +348,16 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
>   	return 0;
>   }
>   
> +static const char *short_ref(struct complete_reflogs *reflogs)
> +{
> +	struct ref_store *refs = get_main_ref_store(the_repository);
> +
> +	if (!reflogs->short_ref)
> +		reflogs->short_ref =
> +			refs_shorten_unambiguous_ref(refs, reflogs->ref, 0);
> +	return reflogs->short_ref;
> +}
> +
>   void get_reflog_selector(struct strbuf *sb,
>   			 struct reflog_walk_info *reflog_info,
>   			 struct date_mode dmode, int force_date,
> @@ -247,12 +371,7 @@ void get_reflog_selector(struct strbuf *sb,
>   		return;
>   
>   	if (shorten) {
> -		if (!commit_reflog->reflogs->short_ref)
> -			commit_reflog->reflogs->short_ref
> -				= refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
> -							       commit_reflog->reflogs->ref,
> -							       0);
> -		printed_ref = commit_reflog->reflogs->short_ref;
> +		printed_ref = short_ref(commit_reflog->reflogs);
>   	} else {
>   		printed_ref = commit_reflog->reflogs->ref;
>   	}
> @@ -287,6 +406,31 @@ void get_reflog_message(struct strbuf *sb,
>   	strbuf_add(sb, info->message, len);
>   }
>   
> +void get_reflog_branch(struct strbuf *sb,
> +		       struct reflog_walk_info *reflog_info)
> +{
> +	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
> +	struct complete_reflogs *reflogs;
> +	const char *branch;
> +
> +	if (!commit_reflog)
> +		return;
> +
> +	reflogs = commit_reflog->reflogs;
> +	if (strcmp(reflogs->ref, "HEAD")) {
> +		strbuf_addstr(sb, short_ref(reflogs));
> +		return;
> +	}
> +
> +	if (!reflogs->branches_marked) {
> +		mark_branches(reflogs);
> +		reflogs->branches_marked = true;
> +	}
> +	branch = reflogs->items[commit_reflog->recno+1].branch;
> +	if (branch)
> +		strbuf_addstr(sb, branch);
> +}
> +
>   const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
>   {
>   	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
> diff --git a/reflog-walk.h b/reflog-walk.h
> index 989583dc55..fc1f344ce6 100644
> --- a/reflog-walk.h
> +++ b/reflog-walk.h
> @@ -13,6 +13,8 @@ void show_reflog_message(struct reflog_walk_info *info, int,
>   			 struct date_mode, int force_date);
>   void get_reflog_message(struct strbuf *sb,
>   			struct reflog_walk_info *reflog_info);
> +void get_reflog_branch(struct strbuf *sb,
> +		       struct reflog_walk_info *reflog_info);
>   const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
>   timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info);
>   void get_reflog_selector(struct strbuf *sb,
> diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
> index be6c3f472c..0e821922ca 100755
> --- a/t/t1414-reflog-walk.sh
> +++ b/t/t1414-reflog-walk.sh
> @@ -131,6 +131,128 @@ test_expect_success 'walk prefers reflog to ref tip' '
>   	test_cmp expect actual
>   '
>   
> +test_expect_success '%gb names the branch HEAD was on' '
> +	git init branches &&
> +	(
> +		cd branches &&
> +		test_commit base &&
> +		git switch -c topic &&
> +		test_commit A &&
> +		test_commit B &&
> +		git switch main &&
> +		test_commit upstream &&
> +		git switch topic &&
> +		git rebase main &&
> +
> +		cat >expect <<-\EOF &&
> +		(topic) rebase (finish): returning to refs/heads/topic
> +		(topic) rebase (pick): B
> +		(topic) rebase (pick): A
> +		(topic) rebase (start): checkout main
> +		(topic) checkout: moving from main to topic
> +		(main) commit: upstream
> +		(main) checkout: moving from topic to main
> +		(topic) commit: B
> +		(topic) commit: A
> +		(topic) checkout: moving from main to topic
> +		(main) commit (initial): base
> +		EOF
> +		git log -g --format="(%gb) %gs" >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '%gb names the ref of other reflogs' '
> +	(
> +		cd branches &&
> +		echo main >expect &&
> +		git log -g -1 --format=%gb main >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '%gb follows "git rebase <upstream> <branch>"' '
> +	(
> +		cd branches &&
> +		git switch main &&
> +		test_commit main-change &&
> +		git rebase main topic &&
> +		git switch main &&
> +		git rebase main topic &&
> +
> +		cat >expect <<-\EOF &&
> +		(topic) rebase: checkout topic
> +		(main) checkout: moving from topic to main
> +		(topic) rebase (finish): returning to refs/heads/topic
> +		(topic) rebase (pick): B
> +		(topic) rebase (pick): A
> +		(topic) rebase (start): checkout main
> +		(main) commit: main-change
> +		(main) checkout: moving from topic to main
> +		EOF
> +		git log -g -8 --format="(%gb) %gs" >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '%gb names a detached HEAD by the commit it was left at' '
> +	(
> +		cd branches &&
> +		git switch --detach topic~1 &&
> +		test_commit detached &&
> +		git rebase topic &&
> +		rebased=$(git rev-parse HEAD) &&
> +		git switch topic &&
> +
> +		left_before=$(git rev-parse --short detached) &&
> +		left_after=$(git rev-parse --short $rebased) &&
> +		cat >expect <<-EOF &&
> +		(topic) checkout: moving from $rebased to topic
> +		(detached at $left_after) rebase (pick): detached
> +		(detached at $left_after) rebase (start): checkout topic
> +		(detached at $left_before) commit: detached
> +		(detached at $left_before) checkout: moving from topic to topic~1
> +		EOF
> +		git log -g -5 --format="(%gb) %gs" >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '%gb names the branch of a "git pull --rebase" in a fresh clone' '
> +	git clone branches downstream &&
> +	(
> +		cd branches &&
> +		test_commit upstream-change
> +	) &&
> +	(
> +		cd downstream &&
> +		test_commit local-change &&
> +		git pull --rebase &&
> +
> +		echo "(topic)" >expect &&
> +		git log -g --format="(%gb)" >out &&
> +		sort -u out >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success '%gb follows a renamed branch' '
> +	(
> +		cd branches &&
> +		test_commit before-rename &&
> +		git branch -m topic renamed &&
> +		test_commit after-rename &&
> +
> +		cat >expect <<-\EOF &&
> +		(renamed) commit: after-rename
> +		(renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
> +		(topic) commit: before-rename
> +		EOF
> +		git log -g -3 --format="(%gb) %gs" >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
>   test_expect_success 'rev-list -g complains when there are no reflogs' '
>   	test_must_fail git rev-list -g
>   '


  reply	other threads:[~2026-11-03 16:16 UTC|newest]

Thread overview: 46+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
2026-10-15 13:13 ` [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps Harald Nordgren via GitGitGadget
2026-10-15 14:35   ` Kristoffer Haugsbakk
2026-10-15 20:52     ` Harald Nordgren
2026-10-15 13:13 ` [PATCH 2/3] reflog: add --operations to show one entry per command Harald Nordgren via GitGitGadget
2026-10-15 13:13 ` [PATCH 3/3] reflog: add --historical-branch to name the branch of each entry Harald Nordgren via GitGitGadget
2026-10-15 14:36 ` [PATCH 0/3] reflog: add --operations and --historical-branch Kristoffer Haugsbakk
2026-10-15 15:24   ` Harald Nordgren
2026-10-15 15:35     ` Kristoffer Haugsbakk
2026-10-15 20:47       ` Harald Nordgren
2026-10-16 18:26     ` D. Ben Knoble
2026-10-23 15:31   ` Harald Nordgren
2026-10-16 18:36 ` D. Ben Knoble
2026-10-17 10:03   ` Harald Nordgren
2026-10-18 18:27 ` [PATCH v2 " Harald Nordgren via GitGitGadget
2026-10-18 18:27   ` [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps Harald Nordgren via GitGitGadget
2026-10-23 21:19     ` Junio C Hamano
2026-10-23 22:47       ` Harald Nordgren
2026-10-23 22:55         ` Junio C Hamano
2026-10-18 18:27   ` [PATCH v2 2/3] reflog: add --operations to show one entry per command Harald Nordgren via GitGitGadget
2026-10-18 18:27   ` [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry Harald Nordgren via GitGitGadget
2026-10-25 19:18     ` Kristoffer Haugsbakk
2026-10-25 20:22       ` Harald Nordgren
2026-10-26  8:39         ` Kristoffer Haugsbakk
2026-10-26  8:42           ` Harald Nordgren
2026-10-23 15:32   ` [PATCH v2 0/3] reflog: add --operations and --historical-branch Harald Nordgren
2026-10-24 13:51   ` Phillip Wood
2026-10-24 15:22     ` D. Ben Knoble
2026-10-24 16:40     ` Junio C Hamano
2026-10-24 18:46       ` Harald Nordgren
2026-10-25 18:52         ` Kristoffer Haugsbakk
2026-10-26 10:52       ` Phillip Wood
2026-10-26 11:00         ` Harald Nordgren
2026-10-26 15:07           ` Junio C Hamano
2026-10-27 13:40             ` Ben Knoble
2026-10-27 19:24               ` Junio C Hamano
2026-10-27 19:33               ` Harald Nordgren
2026-10-27 13:37         ` Ben Knoble
2026-10-24 19:12     ` Harald Nordgren
2026-10-27  8:38 ` [PATCH v3 0/2] " Harald Nordgren via GitGitGadget
2026-10-27  8:38   ` [PATCH v3 1/2] reflog: name the strings that mark switches, renames and rebase steps Harald Nordgren via GitGitGadget
2026-10-27  8:38   ` [PATCH v3 2/2] pretty: add %gb to name the branch of each reflog entry Harald Nordgren via GitGitGadget
2026-11-03 16:16     ` Phillip Wood [this message]
2026-11-04 21:15 ` [PATCH v4 0/2] reflog: add format %gb Harald Nordgren via GitGitGadget
2026-11-04 21:15   ` [PATCH v4 1/2] reflog: name the strings that mark switches, renames and rebase steps Harald Nordgren via GitGitGadget
2026-11-04 21:15   ` [PATCH v4 2/2] pretty: add %gb to name the branch of each reflog entry Harald Nordgren via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=49144eb0-9755-4843-930e-ae7b3b519080@gmail.com \
    --to=phillip.wood123@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=haraldnordgren@gmail.com \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood@dunelm.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox