Git development
 help / color / mirror / Atom feed
From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
	"D. Ben Knoble" <ben.knoble@gmail.com>,
	Phillip Wood <phillip.wood123@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v3 0/2] reflog: add --operations and --historical-branch
Date: Tue, 27 Oct 2026 08:38:45 +0000	[thread overview]
Message-ID: <pull.2448.v3.git.git.1793090327.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2448.git.git.1792070038.gitgitgadget@gmail.com>

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/.

The reflog of HEAD records the work done on every branch, so "git reflog"
mixes them and does not say which entry was made on which branch. This adds
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
1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
1a2b3c4 HEAD@{3}: (main) commit: fix typo


It follows branch switches, branch renames and rebases, including "git
rebase ", rebases of a detached HEAD and rebases of a branch that was
already up to date. When HEAD was detached, the entry shows "detached at ".

Changes in v3:

 * Dropped --operations.
 * Replaced --historical-branch with a %gb placeholder for --format, e.g.
   git log -g --format='%h %gd: (%gb) %gs'.
 * %gb names the right branch after git rebase <upstream> <branch>, a rebase
   of a detached HEAD and an up-to-date rebase.
 * Named the returning to and checkout reflog strings in reflog.h too.

Changes in v2:

 * Move the "historical branch" rightward in the display for cleaner
   alignment.
 * Improve commit messages.

Harald Nordgren (2):
  reflog: name the strings that mark switches, renames and rebase steps
  pretty: add %gb to name the branch of each reflog entry

 Documentation/pretty-formats.adoc |   3 +
 builtin/branch.c                  |   3 +-
 builtin/checkout.c                |   3 +-
 builtin/rebase.c                  |  19 ++--
 object-name.c                     |   3 +-
 pretty.c                          |   4 +
 reflog-walk.c                     | 156 ++++++++++++++++++++++++++++--
 reflog-walk.h                     |   2 +
 reflog.h                          |   8 ++
 sequencer.c                       |  13 +--
 t/t1414-reflog-walk.sh            | 122 +++++++++++++++++++++++
 wt-status.c                       |   3 +-
 12 files changed, 314 insertions(+), 25 deletions(-)


base-commit: 7e34d8174dcda3435d1404c9a30bef46afe6c13e
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2448%2FHaraldNordgren%2Freflog-operations-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2448/HaraldNordgren/reflog-operations-v3
Pull-Request: https://github.com/git/git/pull/2448

Range-diff vs v2:

 1:  745929bd80 ! 1:  4d779e8da3 reflog: name the strings that mark switches, renames and rebase steps
     @@ Metadata
       ## Commit message ##
          reflog: name the strings that mark switches, renames and rebase steps
      
     -    The next commit adds "git reflog --operations", which finds branch
     -    switches, branch renames and the start and end of a rebase by the
     -    messages they leave in the reflog. The commands that write these
     -    messages spell out the strings by hand, and "@{-<n>}" and "git status"
     -    each keep their own copy of the switch prefix to read it back. Reading
     -    them in one more place would add yet another copy that has to stay in
     -    sync.
     +    The next commit adds a "%gb" placeholder that shows which branch HEAD
     +    was on for each reflog entry. It finds the branch from the messages
     +    that branch switches, branch renames and rebases leave in the reflog.
     +    The commands that write these messages spell out the strings by hand,
     +    and "@{-<n>}" and "git status" each keep their own copy of the switch
     +    prefix to read it back. Reading them in one more place would add yet
     +    another copy that has to stay in sync.
      
          Name the strings once in reflog.h and use the names wherever the
          messages are written or read: the "checkout: moving from " prefix
          written by "git checkout", the "Branch: renamed " prefix written by
     -    "git branch -m", and the "start", "finish" and "abort" steps written
     -    by "git rebase".
     +    "git branch -m", and the "start", "finish" and "abort" steps, the
     +    "returning to " before the branch a rebase goes back to and the
     +    "checkout " before what it checks out, all written by "git rebase".
      
          No change in behavior.
      
     @@ builtin/rebase.c: static int move_to_original_branch(struct rebase_options *opts
       		    opts->head_name, oid_to_hex(&opts->onto->object.oid));
      -	strbuf_addf(&head_reflog, "%s (finish): returning to %s",
      -		    opts->reflog_action, opts->head_name);
     -+	strbuf_addf(&head_reflog, "%s (%s): returning to %s",
     ++	strbuf_addf(&head_reflog, "%s (%s): " REFLOG_RETURNING_TO "%s",
      +		    opts->reflog_action, REFLOG_STEP_FINISH, opts->head_name);
       	ropts.branch = opts->head_name;
       	ropts.flags = RESET_WORKING_TREE_REFS_ONLY |
       		      RESET_WORKING_TREE_UPDATE_HEAD;
     +@@ builtin/rebase.c: static int checkout_up_to_date(struct rebase_options *options)
     + 	struct reset_working_tree_options ropts = { 0 };
     + 	int ret = 0;
     + 
     +-	strbuf_addf(&buf, "%s: checkout %s",
     ++	strbuf_addf(&buf, "%s: " REFLOG_CHECKOUT "%s",
     + 		    options->reflog_action, options->switch_to);
     + 	ropts.oid = &options->orig_head->object.oid;
     + 	ropts.branch = options->head_name;
      @@ builtin/rebase.c: int cmd_rebase(int argc,
       		if (read_basic_state(&options))
       			exit(1);
       
      -		strbuf_addf(&head_msg, "%s (abort): returning to %s",
      -			    options.reflog_action,
     -+		strbuf_addf(&head_msg, "%s (%s): returning to %s",
     ++		strbuf_addf(&head_msg, "%s (%s): " REFLOG_RETURNING_TO "%s",
      +			    options.reflog_action, REFLOG_STEP_ABORT,
       			    options.head_name ? options.head_name
       					      : oid_to_hex(&options.orig_head->object.oid));
     @@ builtin/rebase.c: int cmd_rebase(int argc,
       
      -	strbuf_addf(&msg, "%s (start): checkout %s",
      -		    options.reflog_action, options.onto_name);
     -+	strbuf_addf(&msg, "%s (%s): checkout %s", options.reflog_action,
     -+		    REFLOG_STEP_START, options.onto_name);
     ++	strbuf_addf(&msg, "%s (%s): " REFLOG_CHECKOUT "%s",
     ++		    options.reflog_action, REFLOG_STEP_START, options.onto_name);
       	ropts.oid = &options.onto->object.oid;
       	ropts.orig_head = &options.orig_head->object.oid;
       	ropts.flags = RESET_WORKING_TREE_DETACH |
     @@ reflog.h
      +#define REFLOG_STEP_START "start"
      +#define REFLOG_STEP_FINISH "finish"
      +#define REFLOG_STEP_ABORT "abort"
     ++#define REFLOG_RETURNING_TO "returning to "
     ++#define REFLOG_CHECKOUT "checkout "
      +
       #define REFLOG_EXPIRE_TOTAL   (1 << 0)
       #define REFLOG_EXPIRE_UNREACH (1 << 1)
     @@ sequencer.c: static int checkout_onto(struct repository *r, struct replay_opts *
      -		.head_msg = reflog_message(opts, "start", "checkout %s",
      -					   onto_name),
      +		.head_msg = reflog_message(opts, REFLOG_STEP_START,
     -+					   "checkout %s", onto_name),
     ++					   REFLOG_CHECKOUT "%s", onto_name),
       		.default_reflog_action = sequencer_reflog_action(opts)
       	};
       	if (reset_working_tree(r, &ropts)) {
     @@ sequencer.c: cleanup_head_ref:
      -			msg = reflog_message(opts, "finish", "returning to %s",
      -				head_ref.buf);
      +			msg = reflog_message(opts, REFLOG_STEP_FINISH,
     -+				"returning to %s", head_ref.buf);
     ++				REFLOG_RETURNING_TO "%s", head_ref.buf);
       			if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg)) {
       				res = error(_("could not update HEAD to %s"),
       					head_ref.buf);
 2:  1c47ba225e < -:  ---------- reflog: add --operations to show one entry per command
 3:  aa5a98b80d ! 2:  dee548f9b1 reflog: add --historical-branch to name the branch of each entry
     @@ Metadata
      Author: Harald Nordgren <haraldnordgren@gmail.com>
      
       ## Commit message ##
     -    reflog: add --historical-branch to name the branch of each entry
     +    pretty: add %gb to name the branch of each reflog entry
      
     -    "git reflog" does not say which branch HEAD was on when an entry was
     -    recorded. After switching between a few branches, telling which
     -    commits and rebases belong to which branch means reading back to the
     -    last "checkout: moving from" entry for each of them. Commit
     -    decorations do not help either, since they only name branches that
     -    still point at that commit, and a rebased or amended commit has none.
     +    The reflog of HEAD records the work done on every branch, so the
     +    output of "git reflog" mixes them and does not say which entry was
     +    made on which branch. Working that out by hand means looking back for
     +    the branch switch before each entry, and a rebase can move HEAD to
     +    another branch without writing one, as "git rebase <upstream>
     +    <branch>" does.
      
     -    Teach "git reflog" and "git log -g" a "--historical-branch" option
     -    that shows the branch each entry was made on before its message, in
     -    place of the refs that point at the commit now. The hashes and
     -    selectors stay lined up in their columns, and the name is colored like
     -    a branch decoration:
     +    Add a "%gb" placeholder that names the branch HEAD was on when each
     +    entry was recorded:
      
     -      $ git reflog --historical-branch
     -      4e5f6a7 HEAD@{0}: (topic) commit: add tests
     -      1a2b3c4 HEAD@{1}: (topic) checkout: moving from main to topic
     -      8c9d0e1 HEAD@{2}: (main) commit: fix typo
     +        $ 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
      
     -    Every entry gets a name, including ones whose commits are no longer on
     -    any branch. Entries from before a branch was renamed show its old name,
     -    and entries made on a detached HEAD show "detached at <commit>".
     -    Combined with "--operations", which leaves out the branch switches
     -    themselves, it lists what was done to each branch.
     +    The branch is read from the entries that branch switches, branch
     +    renames and rebases leave behind. The entries of a rebase are named
     +    after the branch being rebased. When HEAD was detached, the entry
     +    shows "detached at <commit>" with the commit HEAD was at when it
     +    moved on. 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.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     - ## Documentation/git-reflog.adoc ##
     -@@ Documentation/git-reflog.adoc: actions, and in addition the `HEAD` reflog records branch switching.
     - --pretty=oneline`; see linkgit:git-log[1] for more information.
     - With `--operations`, branch switches and other entries that change no
     - branch are left out, and a rebase is shown as a single entry instead
     --of one entry for each commit it replayed.
     -+of one entry for each commit it replayed. With `--historical-branch`,
     -+each entry shows the branch it was made on before its message.
     - 
     - The "list" subcommand lists all refs which have a corresponding reflog.
     - 
     + ## Documentation/pretty-formats.adoc ##
     +@@ Documentation/pretty-formats.adoc: 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
      
     - ## Documentation/rev-list-options.adoc ##
     -@@ Documentation/rev-list-options.adoc: Under `--pretty=reference`, this information will not be shown at all.
     - 	rebase started. It is an error to use this option unless
     - 	`--walk-reflogs` is in use.
     - 
     -+ifndef::git-rev-list[]
     -+`--historical-branch`::
     -+	Show the branch `HEAD` was on when each reflog entry was
     -+	recorded, such as `(topic)`, before the reflog message, in
     -+	place of the refs that point at the commit now. When `HEAD` was
     -+	detached, the entry shows `(detached at <commit>)`. For the
     -+	reflog of any other ref, the name of that ref is shown. It is
     -+	an error to use this option unless `--walk-reflogs` is in use.
     -+endif::git-rev-list[]
     -+
     - `--merge`::
     - 	Show commits touching conflicted paths in the range `HEAD...<other>`,
     - 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
     -
     - ## log-tree.c ##
     -@@ log-tree.c: void format_decorations(struct strbuf *sb,
     - 	}
     - }
     - 
     -+static void format_historical_branch(struct strbuf *sb,
     -+				     struct rev_info *opt)
     -+{
     -+	const char *branch = get_reflog_branch(opt->reflog_info);
     -+	enum git_colorbool use_color = opt->diffopt.use_color;
     -+	const char *color_commit, *color_reset;
     -+
     -+	if (!branch)
     -+		return;
     -+
     -+	color_commit = diff_get_color(use_color, DIFF_COMMIT);
     -+	color_reset = decorate_get_color(use_color, DECORATION_NONE);
     -+	strbuf_addf(sb, "%s(%s%s%s%s%s)%s ",
     -+		    color_commit, color_reset,
     -+		    decorate_get_color(use_color, DECORATION_REF_LOCAL),
     -+		    branch, color_reset, color_commit, color_reset);
     -+}
     -+
     - void show_decorations(struct rev_info *opt, struct commit *commit)
     - {
     - 	struct strbuf sb = STRBUF_INIT;
     -@@ log-tree.c: void show_decorations(struct rev_info *opt, struct commit *commit)
     - 		if (slot && *slot)
     - 			fprintf(opt->diffopt.file, "\t%s", *slot);
     - 	}
     --	if (!opt->show_decorations)
     -+	if (!opt->show_decorations || opt->historical_branch)
     - 		return;
     - 	format_decorations(&sb, commit, opt->diffopt.use_color, NULL);
     - 	fputs(sb.buf, opt->diffopt.file);
     -@@ log-tree.c: void show_log(struct rev_info *opt)
     - 			graph_show_oneline(opt->graph);
     - 		}
     - 		if (opt->reflog_info) {
     -+			struct strbuf branch = STRBUF_INIT;
     -+
     - 			/*
     - 			 * setup_revisions() ensures that opt->reflog_info
     - 			 * and opt->graph cannot both be set,
     - 			 * so we don't need to worry about printing the
     - 			 * graph info here.
     - 			 */
     -+			if (opt->historical_branch)
     -+				format_historical_branch(&branch, opt);
     - 			show_reflog_message(opt->reflog_info,
     - 					    opt->commit_format == CMIT_FMT_ONELINE,
     - 					    opt->date_mode,
     --					    opt->date_mode_explicit);
     -+					    opt->date_mode_explicit,
     -+					    branch.buf);
     -+			strbuf_release(&branch);
     - 			if (opt->commit_format == CMIT_FMT_ONELINE)
     - 				return;
     - 		}
     + ## pretty.c ##
     +@@ pretty.c: 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':
      
       ## reflog-walk.c ##
      @@
     + #include "git-compat-util.h"
     + #include "commit.h"
       #include "refs.h"
     - #include "reflog.h"
     ++#include "reflog.h"
       #include "diff.h"
      +#include "hex.h"
      +#include "object-name.h"
     @@ reflog-walk.c: struct complete_reflogs {
       		int tz;
       		char *message;
      +		char *branch;
     - 		bool user_operation;
       	} *items;
       	int nr, alloc;
     ++	bool branches_marked;
     + };
     + 
     + static int read_one_reflog(const char *refname UNUSED,
      @@ reflog-walk.c: static int read_one_reflog(const char *refname UNUSED,
       	item->timestamp = timestamp;
       	item->tz = tz;
       	item->message = xstrdup(message);
      +	item->branch = NULL;
     - 	item->user_operation = true;
       	array->nr++;
       	return 0;
     + }
      @@ reflog-walk.c: static void free_complete_reflog(struct complete_reflogs *array)
       	for (i = 0; i < array->nr; i++) {
       		free(array->items[i].email);
     @@ reflog-walk.c: static void free_complete_reflog(struct complete_reflogs *array)
       	}
       	free(array->items);
       	free(array->ref);
     -@@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
     - 	}
     +@@ reflog-walk.c: 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)
     ++static char *branch_label(const char *name, size_t len,
     ++			  const struct object_id *oid)
      +{
     -+	struct object_id oid;
     -+	const char *short_name;
     ++	if (name) {
     ++		struct object_id hex_oid;
     ++		char *branch;
      +
     -+	if (len == the_hash_algo->hexsz && !get_oid_hex(name, &oid))
     -+		return xstrfmt("detached at %s",
     -+			       repo_find_unique_abbrev(the_repository, &oid,
     -+						       DEFAULT_ABBREV));
     -+	if (skip_prefix(name, "refs/heads/", &short_name))
     -+		len -= short_name - name;
     -+	else
     -+		short_name = name;
     -+	return xmemdupz(short_name, len);
     ++		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 {
     @@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
      +{
      +	const char *to;
      +
     -+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) &&
     -+	    !skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from))
     ++	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;
     -+	to = strstr(move->from, " to ");
     -+	if (!to)
     -+		return false;
     -+	move->from_len = to - move->from;
     -+	move->to = to + strlen(" to ");
     -+	move->to_len = strcspn(move->to, "\n");
     ++	}
     ++	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 (strcmp(array->ref, "HEAD"))
     -+		return;
     ++	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];
     @@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
      +			continue;
      +		}
      +
     -+		if (!branch)
     -+			for (int j = i + 1; j < array->nr; j++)
     -+				array->items[j].branch =
     -+					branch_label(move.to, move.to_len);
     -+		item->branch = branch_label(move.to, move.to_len);
     -+		free(branch);
     -+		branch = branch_label(move.from, move.from_len);
     ++		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) {
     -+		struct ref_store *refs = get_main_ref_store(the_repository);
     -+		const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0,
     -+							   NULL, NULL);
     -+
     -+		if (head && skip_prefix(head, "refs/heads/", &head))
     -+			for (int i = 0; i < array->nr; i++)
     -+				array->items[i].branch = xstrdup(head);
     -+	}
     ++	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 =
     -@@ reflog-walk.c: void init_reflog_walk(struct reflog_walk_info **info)
     - 	(*info)->complete_reflogs.strdup_strings = 1;
     - }
     - 
     --void reflog_walk_mark(struct reflog_walk_info *info, bool operations)
     -+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
     -+		      bool historical_branch)
     - {
     - 	struct string_list_item *item;
     - 
     - 	for_each_string_list_item(item, &info->complete_reflogs) {
     - 		if (operations)
     - 			mark_operations(item->util);
     -+		if (historical_branch)
     -+			mark_branches(item->util);
     - 	}
     - }
     - 
      @@ reflog-walk.c: int add_reflog_for_walk(struct reflog_walk_info *info,
       	return 0;
       }
     @@ reflog-walk.c: void get_reflog_message(struct strbuf *sb,
       	strbuf_add(sb, info->message, len);
       }
       
     -+const char *get_reflog_branch(struct reflog_walk_info *reflog_info)
     ++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 NULL;
     ++		return;
      +
     -+	if (strcmp(commit_reflog->reflogs->ref, "HEAD"))
     -+		return short_ref(commit_reflog->reflogs);
     -+	return commit_reflog->reflogs->items[commit_reflog->recno+1].branch;
     ++	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;
     -@@ reflog-walk.c: timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info)
     - }
     - 
     - void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
     --			 struct date_mode dmode, int force_date)
     -+			 struct date_mode dmode, int force_date,
     -+			 const char *branch)
     - {
     - 	if (reflog_info && reflog_info->last_commit_reflog) {
     - 		struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
     -@@ reflog-walk.c: void show_reflog_message(struct reflog_walk_info *reflog_info, int oneline,
     - 		info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
     - 		get_reflog_selector(&selector, reflog_info, dmode, force_date, 0);
     - 		if (oneline) {
     --			printf("%s: %s", selector.buf, info->message);
     -+			printf("%s: %s%s", selector.buf, branch,
     -+			       info->message);
     - 		}
     - 		else {
     --			printf("Reflog: %s (%s)\nReflog message: %s",
     --			       selector.buf, info->email, info->message);
     -+			printf("Reflog: %s (%s)\nReflog message: %s%s",
     -+			       selector.buf, info->email, branch,
     -+			       info->message);
     - 		}
     - 
     - 		strbuf_release(&selector);
      
       ## reflog-walk.h ##
     -@@ reflog-walk.h: struct date_mode;
     - 
     - void init_reflog_walk(struct reflog_walk_info **info);
     - void reflog_walk_info_release(struct reflog_walk_info *info);
     --void reflog_walk_mark(struct reflog_walk_info *info, bool operations);
     -+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
     -+		      bool historical_branch);
     - int add_reflog_for_walk(struct reflog_walk_info *info,
     - 			struct commit *commit, const char *name);
     - void show_reflog_message(struct reflog_walk_info *info, int,
     --			 struct date_mode, int force_date);
     -+			 struct date_mode, int force_date,
     -+			 const char *branch);
     +@@ reflog-walk.h: 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);
     -+const char *get_reflog_branch(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,
      
     - ## revision.c ##
     -@@ revision.c: static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
     - 		revs->show_signature = 1;
     - 	} else if (!strcmp(arg, "--no-show-signature")) {
     - 		revs->show_signature = 0;
     -+	} else if (!strcmp(arg, "--historical-branch")) {
     -+		revs->historical_branch = 1;
     - 	} else if (!strcmp(arg, "--show-linear-break")) {
     - 		revs->break_bar = "                    ..........";
     - 		revs->track_linear = 1;
     -@@ revision.c: int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
     - 	if (!revs->reflog_info && revs->grep_filter.use_reflog_filter)
     - 		die(_("the option '%s' requires '%s'"), "--grep-reflog", "--walk-reflogs");
     - 
     -+	if (!revs->reflog_info && revs->historical_branch)
     -+		die(_("the option '%s' requires '%s'"), "--historical-branch", "--walk-reflogs");
     -+
     - 	if (!revs->reflog_info && revs->reflog_operations)
     - 		die(_("the option '%s' requires '%s'"), "--operations", "--walk-reflogs");
     - 
     - 	if (revs->reflog_info)
     --		reflog_walk_mark(revs->reflog_info, revs->reflog_operations);
     -+		reflog_walk_mark(revs->reflog_info, revs->reflog_operations,
     -+				 revs->historical_branch);
     - 
     - 	if (revs->line_level_traverse &&
     - 	    (revs->full_diff ||
     -
     - ## revision.h ##
     -@@ revision.h: struct rev_info {
     - 			show_notes_given:1,
     - 			show_notes_by_default:1,
     - 			show_signature:1,
     -+			historical_branch:1,
     - 			pretty_given:1,
     - 			abbrev_commit:1,
     - 			abbrev_commit_given:1,
     -
       ## t/t1414-reflog-walk.sh ##
     -@@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one entry' '
     - 	)
     +@@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
     + 	test_cmp expect actual
       '
       
     -+test_expect_success '--historical-branch names the branch of each entry' '
     ++test_expect_success '%gb names the branch HEAD was on' '
     ++	git init branches &&
      +	(
     -+		cd operations &&
     ++		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 &&
     -+		HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
     -+		HEAD@{1}: (topic) rebase (pick): C
     -+		HEAD@{2}: (topic) rebase (pick): B
     -+		HEAD@{3}: (topic) rebase (pick): A
     -+		HEAD@{4}: (topic) rebase (start): checkout main
     -+		HEAD@{5}: (topic) checkout: moving from main to topic
     -+		HEAD@{6}: (main) commit: upstream
     -+		HEAD@{7}: (main) checkout: moving from topic to main
     -+		HEAD@{8}: (topic) commit: C
     -+		HEAD@{9}: (topic) commit: B
     -+		HEAD@{10}: (topic) commit: A
     -+		HEAD@{11}: (topic) checkout: moving from main to topic
     -+		HEAD@{12}: (main) commit (initial): base
     ++		(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 reflog --historical-branch >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		git log -g --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch takes the place of decorations' '
     ++test_expect_success '%gb names the ref of other reflogs' '
      +	(
     -+		cd operations &&
     -+		hash=$(git rev-parse --short HEAD) &&
     -+		echo "<YELLOW>$hash<RESET> HEAD@{0}: <YELLOW>(<RESET><BOLD;GREEN>topic<RESET><YELLOW>)<RESET> rebase (finish): returning to refs/heads/topic" >expect &&
     -+		git reflog --historical-branch --decorate --color -1 >out &&
     -+		test_decode_color <out >actual &&
     ++		cd branches &&
     ++		echo main >expect &&
     ++		git log -g -1 --format=%gb main >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch names the ref of other reflogs' '
     ++test_expect_success '%gb follows "git rebase <upstream> <branch>"' '
      +	(
     -+		cd operations &&
     -+		echo "main@{0}: (main) commit: upstream" >expect &&
     -+		git reflog -1 main --historical-branch >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		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 '--operations shows a stopped rebase as one entry once it finishes' '
     - 	set_fake_editor &&
     - 	(
     -@@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase as one entry' '
     - 	)
     - '
     - 
     --test_expect_success '--operations requires --walk-reflogs' '
     -+test_expect_success '--historical-branch names the current branch when HEAD never switched' '
     ++test_expect_success '%gb names a detached HEAD by the commit it was left at' '
      +	(
     -+		cd downstream &&
     -+		echo "(topic)" >expect &&
     -+		git reflog --historical-branch >out &&
     -+		cut -d " " -f 3 out | sort -u >actual &&
     ++		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 '--historical-branch follows a renamed branch' '
     ++test_expect_success '%gb names the branch of a "git pull --rebase" in a fresh clone' '
     ++	git clone branches downstream &&
      +	(
     -+		cd operations &&
     -+		test_commit D &&
     -+		git branch -m topic renamed &&
     -+		test_commit E &&
     ++		cd branches &&
     ++		test_commit upstream-change
     ++	) &&
     ++	(
     ++		cd downstream &&
     ++		test_commit local-change &&
     ++		git pull --rebase &&
      +
     -+		cat >expect <<-\EOF &&
     -+		HEAD@{0}: (renamed) commit: E
     -+		HEAD@{1}: (renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
     -+		HEAD@{3}: (topic) commit: D
     -+		EOF
     -+		git reflog --historical-branch -3 >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		echo "(topic)" >expect &&
     ++		git log -g --format="(%gb)" >out &&
     ++		sort -u out >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch shows where HEAD was detached' '
     ++test_expect_success '%gb follows a renamed branch' '
      +	(
     -+		cd operations &&
     -+		git checkout --detach &&
     -+		test_commit F &&
     -+		git checkout renamed &&
     ++		cd branches &&
     ++		test_commit before-rename &&
     ++		git branch -m topic renamed &&
     ++		test_commit after-rename &&
      +
     -+		cat >expect <<-EOF &&
     -+		HEAD@{0}: (renamed) checkout: moving from $(git rev-parse F) to renamed
     -+		HEAD@{1}: (detached at $(git rev-parse --short F)) commit: F
     ++		cat >expect <<-\EOF &&
     ++		(renamed) commit: after-rename
     ++		(renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
     ++		(topic) commit: before-rename
      +		EOF
     -+		git reflog --historical-branch -2 >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		git log -g -3 --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--operations and --historical-branch require --walk-reflogs' '
     - 	test_must_fail git log --operations 2>err &&
     -+	test_grep "requires .--walk-reflogs." err &&
     -+	test_must_fail git log --historical-branch 2>err &&
     - 	test_grep "requires .--walk-reflogs." err
     + test_expect_success 'rev-list -g complains when there are no reflogs' '
     + 	test_must_fail git rev-list -g
       '
     - 

-- 
gitgitgadget

  parent reply	other threads:[~2026-10-27  8:38 UTC|newest]

Thread overview: 38+ 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-24 19:12     ` Harald Nordgren
2026-10-27  8:38 ` Harald Nordgren via GitGitGadget [this message]
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

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=pull.2448.v3.git.git.1793090327.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=haraldnordgren@gmail.com \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood123@gmail.com \
    /path/to/YOUR_REPLY

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

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