All of lore.kernel.org
 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: 42+ 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 ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.