All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] reflog: add --operations and --historical-branch
@ 2026-10-15 13:13 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
                   ` (5 more replies)
  0 siblings, 6 replies; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-15 13:13 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren

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

"git reflog" lists every step of a rebase and every branch switch, which
makes it hard to see what was done and on which branch. This adds two
options to "git reflog" and "git log -g":

 * --operations shows one entry per command, folding a rebase into the entry
   that finished or aborted it, and leaves out entries that change no
   branch.
 * --historical-branch decorates each entry with the branch HEAD was on when
   it was recorded.

Harald Nordgren (3):
  reflog: name the strings that mark switches, renames and rebase steps
  reflog: add --operations to show one entry per command
  reflog: add --historical-branch to name the branch of each entry

 Documentation/git-reflog.adoc       |   4 +
 Documentation/rev-list-options.adoc |  19 +++
 builtin/branch.c                    |   3 +-
 builtin/checkout.c                  |   3 +-
 builtin/rebase.c                    |  17 +--
 log-tree.c                          |  21 +++
 object-name.c                       |   3 +-
 reflog-walk.c                       | 172 ++++++++++++++++++++++--
 reflog-walk.h                       |   3 +
 reflog.h                            |   6 +
 revision.c                          |  14 ++
 revision.h                          |   2 +
 sequencer.c                         |  13 +-
 t/t1414-reflog-walk.sh              | 198 ++++++++++++++++++++++++++++
 wt-status.c                         |   3 +-
 15 files changed, 455 insertions(+), 26 deletions(-)


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

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

* [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps
  2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
@ 2026-10-15 13:13 ` Harald Nordgren via GitGitGadget
  2026-10-15 14:35   ` Kristoffer Haugsbakk
  2026-10-15 13:13 ` [PATCH 2/3] reflog: add --operations to show one entry per command Harald Nordgren via GitGitGadget
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-15 13:13 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

"git checkout" records a move of HEAD in the reflog as "checkout:
moving from <old> to <new>", "git branch -m" records a rename of the
current branch as "Branch: renamed <old> to <new>", and "git rebase"
marks where it starts and ends with steps like "rebase (start)" and
"rebase (finish)". "@{-<n>}" and "git status" read the switch message
back, each with its own copy of the prefix, and a later commit adds a
reader for all of them.

Name these strings in reflog.h and use them where the messages are
written and read, so they cannot drift apart.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/branch.c   |  3 ++-
 builtin/checkout.c |  3 ++-
 builtin/rebase.c   | 17 +++++++++--------
 object-name.c      |  3 ++-
 reflog.h           |  6 ++++++
 sequencer.c        | 13 +++++++------
 wt-status.c        |  3 ++-
 7 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index a613148fc7..30062ceb7c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -13,6 +13,7 @@
 #include "editor.h"
 #include "environment.h"
 #include "refs.h"
+#include "reflog.h"
 #include "commit.h"
 #include "gettext.h"
 #include "object-name.h"
@@ -669,7 +670,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 		strbuf_addf(&logmsg, "Branch: copied %s to %s",
 			    oldref.buf, newref.buf);
 	else
-		strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+		strbuf_addf(&logmsg, REFLOG_RENAME_PREFIX "%s to %s",
 			    oldref.buf, newref.buf);
 
 	if (!copy && !(oldref_usage & IS_ORPHAN) &&
diff --git a/builtin/checkout.c b/builtin/checkout.c
index c0f0d2c700..9f0f09e50f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,6 +25,7 @@
 #include "preload-index.h"
 #include "read-cache.h"
 #include "refs.h"
+#include "reflog.h"
 #include "remote.h"
 #include "repo-settings.h"
 #include "resolve-undo.h"
@@ -999,7 +1000,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 
 	reflog_msg = getenv("GIT_REFLOG_ACTION");
 	if (!reflog_msg)
-		strbuf_addf(&msg, "checkout: moving from %s to %s",
+		strbuf_addf(&msg, REFLOG_SWITCH_PREFIX "%s to %s",
 			old_desc ? old_desc : "(invalid)", new_branch_info->name);
 	else
 		strbuf_insertstr(&msg, 0, reflog_msg);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 535db60181..26ef589e89 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -17,6 +17,7 @@
 #include "strvec.h"
 #include "dir.h"
 #include "refs.h"
+#include "reflog.h"
 #include "config.h"
 #include "unpack-trees.h"
 #include "lockfile.h"
@@ -601,11 +602,11 @@ static int move_to_original_branch(struct rebase_options *opts)
 	if (!opts->onto)
 		BUG("move_to_original_branch without onto");
 
-	strbuf_addf(&branch_reflog, "%s (finish): %s onto %s",
-		    opts->reflog_action,
+	strbuf_addf(&branch_reflog, "%s (%s): %s onto %s",
+		    opts->reflog_action, REFLOG_STEP_FINISH,
 		    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",
+		    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;
@@ -1413,8 +1414,8 @@ 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",
+			    options.reflog_action, REFLOG_STEP_ABORT,
 			    options.head_name ? options.head_name
 					      : oid_to_hex(&options.orig_head->object.oid));
 		ropts.oid = &options.orig_head->object.oid;
@@ -1884,8 +1885,8 @@ int cmd_rebase(int argc,
 		printf(_("First, rewinding head to replay your work on top of "
 			 "it...\n"));
 
-	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);
 	ropts.oid = &options.onto->object.oid;
 	ropts.orig_head = &options.orig_head->object.oid;
 	ropts.flags = RESET_WORKING_TREE_DETACH |
diff --git a/object-name.c b/object-name.c
index 4eda8c8eac..bb349014ee 100644
--- a/object-name.c
+++ b/object-name.c
@@ -13,6 +13,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "reflog.h"
 #include "remote.h"
 #include "dir.h"
 #include "odb.h"
@@ -1251,7 +1252,7 @@ static int grab_nth_branch_switch(const char *refname UNUSED,
 	const char *match = NULL, *target = NULL;
 	size_t len;
 
-	if (skip_prefix(message, "checkout: moving from ", &match))
+	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &match))
 		target = strstr(match, " to ");
 
 	if (!match || !target)
diff --git a/reflog.h b/reflog.h
index 3bdd1ca8a5..32c3737db4 100644
--- a/reflog.h
+++ b/reflog.h
@@ -2,6 +2,12 @@
 #define REFLOG_H
 #include "refs.h"
 
+#define REFLOG_SWITCH_PREFIX "checkout: moving from "
+#define REFLOG_RENAME_PREFIX "Branch: renamed "
+#define REFLOG_STEP_START "start"
+#define REFLOG_STEP_FINISH "finish"
+#define REFLOG_STEP_ABORT "abort"
+
 #define REFLOG_EXPIRE_TOTAL   (1 << 0)
 #define REFLOG_EXPIRE_UNREACH (1 << 1)
 
diff --git a/sequencer.c b/sequencer.c
index efbfd1315b..c55df2d38f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -31,6 +31,7 @@
 #include "merge-ort.h"
 #include "merge-ort-wrappers.h"
 #include "refs.h"
+#include "reflog.h"
 #include "sparse-index.h"
 #include "strvec.h"
 #include "quote.h"
@@ -5011,8 +5012,8 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
 			 RESET_WORKING_TREE_UPDATE_HEAD |
 			 RESET_WORKING_TREE_UPDATE_ORIG_HEAD |
 			 RESET_WORKING_TREE_RUN_POST_CHECKOUT_HOOK,
-		.head_msg = reflog_message(opts, "start", "checkout %s",
-					   onto_name),
+		.head_msg = reflog_message(opts, REFLOG_STEP_START,
+					   "checkout %s", onto_name),
 		.default_reflog_action = sequencer_reflog_action(opts)
 	};
 	if (reset_working_tree(r, &ropts)) {
@@ -5310,16 +5311,16 @@ cleanup_head_ref:
 				res = error(_("could not read 'onto'"));
 				goto cleanup_head_ref;
 			}
-			msg = reflog_message(opts, "finish", "%s onto %s",
-				head_ref.buf, buf.buf);
+			msg = reflog_message(opts, REFLOG_STEP_FINISH,
+				"%s onto %s", head_ref.buf, buf.buf);
 			if (refs_update_ref(get_main_ref_store(the_repository), msg, head_ref.buf, &head, &orig,
 					    REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
 				res = error(_("could not update %s"),
 					head_ref.buf);
 				goto 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);
 			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);
diff --git a/wt-status.c b/wt-status.c
index 57772c7501..ffd54353a8 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -22,6 +22,7 @@
 #include "strvec.h"
 #include "remote.h"
 #include "refs.h"
+#include "reflog.h"
 #include "submodule.h"
 #include "column.h"
 #include "read-cache.h"
@@ -1790,7 +1791,7 @@ static int grab_1st_switch(const char *refname UNUSED,
 	struct grab_1st_switch_cbdata *cb = cb_data;
 	const char *target = NULL, *end;
 
-	if (!skip_prefix(message, "checkout: moving from ", &message))
+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &message))
 		return 0;
 	target = strstr(message, " to ");
 	if (!target)
-- 
gitgitgadget


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

* [PATCH 2/3] reflog: add --operations to show one entry per command
  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 13:13 ` 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
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-15 13:13 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

After a rebase, "git reflog" shows one entry for every commit the
rebase replayed, plus one for its start and one for its end. Together
with an entry for every branch switch, finding where a branch was
before the rebase means reading past all of them.

Teach "git reflog" and "git log -g" an "--operations" option that
shows each rebase as the single entry that finished or aborted it, and
leaves out entries that change no branch, like branch switches and the
"reset: moving to HEAD" that "git stash" and "git merge --abort" leave
behind:

  $ git reflog --operations
  4e5f6a7 HEAD@{0}: rebase (finish): returning to refs/heads/topic
  8c9d0e1 HEAD@{5}: commit: fix typo
  1a2b3c4 HEAD@{7}: commit: add tests

The HEAD@{<n>} numbers keep their usual values, so the previous entry
on the same branch can be used to undo the rebase:

  $ git reset --hard HEAD@{7}

Commands run while a rebase is stopped, such as an amend after an
"edit", are folded into that rebase, and a "git pull --rebase" is shown
as one entry the same way.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-reflog.adoc       |   3 +
 Documentation/rev-list-options.adoc |  11 +++
 reflog-walk.c                       |  61 +++++++++++++++-
 reflog-walk.h                       |   1 +
 revision.c                          |   8 +++
 revision.h                          |   1 +
 t/t1414-reflog-walk.sh              | 105 ++++++++++++++++++++++++++++
 7 files changed, 188 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
index 38af0c977a..672b9ea72e 100644
--- a/Documentation/git-reflog.adoc
+++ b/Documentation/git-reflog.adoc
@@ -41,6 +41,9 @@ command-line (or `HEAD`, by default). The reflog covers all recent
 actions, and in addition the `HEAD` reflog records branch switching.
 `git reflog show` is an alias for `git log -g --abbrev-commit
 --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.
 
 The "list" subcommand lists all refs which have a corresponding reflog.
 
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index bd9f345690..a40ba2641b 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -348,6 +348,17 @@ See also linkgit:git-reflog[1].
 +
 Under `--pretty=reference`, this information will not be shown at all.
 
+`--operations`::
+	Show one reflog entry for each command that changed a branch.
+	A rebase is shown as only the entry that finished or aborted it,
+	including any commands run while it was stopped, such as
+	`git commit --amend`. Switching branches is not shown, and
+	neither is a reset that left `HEAD` where it was, such as the one
+	`git stash` does.
+	The previous entry on the same branch is the state before the
+	rebase started. It is an error to use this option unless
+	`--walk-reflogs` is in use.
+
 `--merge`::
 	Show commits touching conflicted paths in the range `HEAD...<other>`,
 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
diff --git a/reflog-walk.c b/reflog-walk.c
index 4dbeaa93a7..82528f5b66 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -3,6 +3,7 @@
 #include "git-compat-util.h"
 #include "commit.h"
 #include "refs.h"
+#include "reflog.h"
 #include "diff.h"
 #include "repository.h"
 #include "revision.h"
@@ -18,6 +19,7 @@ struct complete_reflogs {
 		timestamp_t timestamp;
 		int tz;
 		char *message;
+		bool user_operation;
 	} *items;
 	int nr, alloc;
 };
@@ -38,6 +40,7 @@ static int read_one_reflog(const char *refname UNUSED,
 	item->timestamp = timestamp;
 	item->tz = tz;
 	item->message = xstrdup(message);
+	item->user_operation = true;
 	array->nr++;
 	return 0;
 }
@@ -65,6 +68,46 @@ static void complete_reflogs_clear(void *util, const char *str UNUSED)
 	free_complete_reflog(array);
 }
 
+static bool has_rebase_step(const char *message, const char *step)
+{
+	size_t end = strcspn(message, ":\n");
+	size_t len = strlen(step);
+
+	return end >= len + 3 && message[end - 1] == ')' &&
+	       !strncmp(message + end - len - 3, " (", 2) &&
+	       !strncmp(message + end - len - 1, step, len);
+}
+
+static void mark_operations(struct complete_reflogs *array)
+{
+	int start = -1;
+
+	for (int i = 0; i < array->nr; i++) {
+		struct reflog_info *item = &array->items[i];
+		const char *msg = item->message;
+
+		if (has_rebase_step(msg, REFLOG_STEP_FINISH) ||
+		    has_rebase_step(msg, REFLOG_STEP_ABORT)) {
+			if (start >= 0)
+				for (int j = start; j < i; j++)
+					array->items[j].user_operation = false;
+			start = -1;
+			item->user_operation = true;
+			continue;
+		}
+		if (has_rebase_step(msg, REFLOG_STEP_START))
+			start = i;
+
+		if (starts_with(msg, REFLOG_SWITCH_PREFIX))
+			item->user_operation = false;
+		else if (starts_with(msg, REFLOG_RENAME_PREFIX))
+			item->user_operation = true;
+		else
+			item->user_operation =
+				!oideq(&item->ooid, &item->noid);
+	}
+}
+
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
 	struct complete_reflogs *reflogs =
@@ -135,6 +178,16 @@ 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)
+{
+	struct string_list_item *item;
+
+	for_each_string_list_item(item, &info->complete_reflogs) {
+		if (operations)
+			mark_operations(item->util);
+	}
+}
+
 void reflog_walk_info_release(struct reflog_walk_info *info)
 {
 	size_t i;
@@ -342,8 +395,12 @@ static struct commit *next_reflog_commit(struct commit_reflog *log)
 {
 	for (; log->recno >= 0; log->recno--) {
 		struct reflog_info *entry = &log->reflogs->items[log->recno];
-		struct object *obj = parse_object(the_repository,
-						  &entry->noid);
+		struct object *obj;
+
+		if (!entry->user_operation)
+			continue;
+
+		obj = parse_object(the_repository, &entry->noid);
 
 		if (obj && obj->type == OBJ_COMMIT)
 			return (struct commit *)obj;
diff --git a/reflog-walk.h b/reflog-walk.h
index 989583dc55..1d86795f84 100644
--- a/reflog-walk.h
+++ b/reflog-walk.h
@@ -7,6 +7,7 @@ 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);
 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,
diff --git a/revision.c b/revision.c
index a83e499047..88785651fe 100644
--- a/revision.c
+++ b/revision.c
@@ -2458,6 +2458,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		commit_list_insert(c, &revs->ancestry_path_bottoms);
 	} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
 		init_reflog_walk(&revs->reflog_info);
+	} else if (!strcmp(arg, "--operations")) {
+		revs->reflog_operations = 1;
 	} else if (!strcmp(arg, "--default")) {
 		if (argc <= 1)
 			return error("bad --default argument");
@@ -3247,6 +3249,12 @@ 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->reflog_operations)
+		die(_("the option '%s' requires '%s'"), "--operations", "--walk-reflogs");
+
+	if (revs->reflog_info)
+		reflog_walk_mark(revs->reflog_info, revs->reflog_operations);
+
 	if (revs->line_level_traverse &&
 	    (revs->full_diff ||
 	     (revs->diffopt.output_format &
diff --git a/revision.h b/revision.h
index e5dabd18ce..c4f5e06698 100644
--- a/revision.h
+++ b/revision.h
@@ -221,6 +221,7 @@ struct rev_info {
 			exclude_first_parent_only:1,
 			line_level_traverse:1,
 			tree_blobs_in_commit_order:1,
+			reflog_operations:1,
 
 			/*
 			 * Blobs are shown without regard for their existence.
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index be6c3f472c..e15f6f2381 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 test_expect_success 'set up some reflog entries' '
 	test_commit one &&
@@ -131,6 +132,110 @@ test_expect_success 'walk prefers reflog to ref tip' '
 	test_cmp expect actual
 '
 
+test_expect_success '--operations shows a rebase as one entry' '
+	git init operations &&
+	(
+		cd operations &&
+		test_commit base &&
+		git switch -c topic &&
+		test_commit A &&
+		test_commit B &&
+		test_commit C &&
+		git switch main &&
+		test_commit upstream &&
+		git switch topic &&
+		git rebase main &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (finish): returning to refs/heads/topic
+		HEAD@{6} commit: upstream
+		HEAD@{8} commit: C
+		HEAD@{9} commit: B
+		HEAD@{10} commit: A
+		HEAD@{12} commit (initial): base
+		EOF
+		git log -g --format="%gd %gs" HEAD --operations >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows a stopped rebase as one entry once it finishes' '
+	set_fake_editor &&
+	(
+		cd operations &&
+		FAKE_LINES="edit 1 2" git rebase -i HEAD~2 &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase: fast-forward
+		HEAD@{1} rebase (start): checkout HEAD~2
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual &&
+
+		git commit --amend -m "B amended" &&
+		git rebase --continue &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (finish): returning to refs/heads/topic
+		HEAD@{5} rebase (finish): returning to refs/heads/topic
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows an aborted rebase as one entry' '
+	(
+		cd operations &&
+		FAKE_LINES="edit 1 2" git rebase -i HEAD~2 &&
+		git rebase --abort &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (abort): returning to refs/heads/topic
+		HEAD@{3} rebase (finish): returning to refs/heads/topic
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations leaves out the reset done by git stash' '
+	(
+		cd operations &&
+		echo change >>C.t &&
+		git stash &&
+
+		echo "HEAD@{1} rebase (abort): returning to refs/heads/topic" >expect &&
+		git log -g --operations --format="%gd %gs" -1 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows a pull --rebase as one entry' '
+	git clone operations downstream &&
+	(
+		cd operations &&
+		test_commit upstream-change
+	) &&
+	(
+		cd downstream &&
+		test_commit local-change &&
+		git pull --rebase &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} pull --rebase (finish): returning to refs/heads/topic
+		HEAD@{3} commit: local-change
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations requires --walk-reflogs' '
+	test_must_fail git log --operations 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


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

* [PATCH 3/3] reflog: add --historical-branch to name the branch of each entry
  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 13:13 ` [PATCH 2/3] reflog: add --operations to show one entry per command Harald Nordgren via GitGitGadget
@ 2026-10-15 13:13 ` Harald Nordgren via GitGitGadget
  2026-10-15 14:36 ` [PATCH 0/3] reflog: add --operations and --historical-branch Kristoffer Haugsbakk
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-15 13:13 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

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

Teach "git reflog" and "git log -g" a "--historical-branch" option
that decorates each entry with the branch it was made on, in place of
the refs that point at the commit now. The name is colored like a
branch decoration:

  $ git reflog --historical-branch
  4e5f6a7 (topic) HEAD@{0}: commit: add tests
  1a2b3c4 (topic) HEAD@{1}: checkout: moving from main to topic
  8c9d0e1 (main) HEAD@{2}: commit: fix typo

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.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-reflog.adoc       |   3 +-
 Documentation/rev-list-options.adoc |   8 ++
 log-tree.c                          |  21 ++++++
 reflog-walk.c                       | 113 ++++++++++++++++++++++++++--
 reflog-walk.h                       |   4 +-
 revision.c                          |   8 +-
 revision.h                          |   1 +
 t/t1414-reflog-walk.sh              |  95 ++++++++++++++++++++++-
 8 files changed, 242 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
index 672b9ea72e..cd988f438b 100644
--- a/Documentation/git-reflog.adoc
+++ b/Documentation/git-reflog.adoc
@@ -43,7 +43,8 @@ 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 is decorated with the branch it was made on.
 
 The "list" subcommand lists all refs which have a corresponding reflog.
 
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index a40ba2641b..d2a0a9b3bc 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -359,6 +359,14 @@ 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.
 
+`--historical-branch`::
+	Decorate each reflog entry with the branch `HEAD` was on when
+	the entry was recorded, such as `(topic)`, 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.
+
 `--merge`::
 	Show commits touching conflicted paths in the range `HEAD...<other>`,
 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
diff --git a/log-tree.c b/log-tree.c
index 83a3c4bf9b..d457f0451a 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -421,6 +421,23 @@ void format_decorations(struct strbuf *sb,
 	}
 }
 
+static void show_historical_branch(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);
+	fprintf(opt->diffopt.file, "%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;
@@ -431,6 +448,10 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
 		if (slot && *slot)
 			fprintf(opt->diffopt.file, "\t%s", *slot);
 	}
+	if (opt->historical_branch) {
+		show_historical_branch(opt);
+		return;
+	}
 	if (!opt->show_decorations)
 		return;
 	format_decorations(&sb, commit, opt->diffopt.use_color, NULL);
diff --git a/reflog-walk.c b/reflog-walk.c
index 82528f5b66..cc42fcd7d8 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -5,6 +5,8 @@
 #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"
@@ -19,6 +21,7 @@ struct complete_reflogs {
 		timestamp_t timestamp;
 		int tz;
 		char *message;
+		char *branch;
 		bool user_operation;
 	} *items;
 	int nr, alloc;
@@ -40,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;
 	item->user_operation = true;
 	array->nr++;
 	return 0;
@@ -55,6 +59,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);
@@ -108,6 +113,80 @@ static void mark_operations(struct complete_reflogs *array)
 	}
 }
 
+static char *branch_label(const char *name, size_t len)
+{
+	struct object_id oid;
+	const char *short_name;
+
+	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);
+}
+
+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;
+
+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) &&
+	    !skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from))
+		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");
+	return true;
+}
+
+static void mark_branches(struct complete_reflogs *array)
+{
+	char *branch = NULL;
+
+	if (strcmp(array->ref, "HEAD"))
+		return;
+
+	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)
+			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) {
+		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);
+	}
+	free(branch);
+}
+
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
 	struct complete_reflogs *reflogs =
@@ -178,13 +257,16 @@ 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);
 	}
 }
 
@@ -287,6 +369,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,
@@ -300,12 +392,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;
 	}
@@ -340,6 +427,18 @@ void get_reflog_message(struct strbuf *sb,
 	strbuf_add(sb, info->message, len);
 }
 
+const char *get_reflog_branch(struct reflog_walk_info *reflog_info)
+{
+	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+
+	if (!commit_reflog)
+		return NULL;
+
+	if (strcmp(commit_reflog->reflogs->ref, "HEAD"))
+		return short_ref(commit_reflog->reflogs);
+	return commit_reflog->reflogs->items[commit_reflog->recno+1].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 1d86795f84..223f4a0dd3 100644
--- a/reflog-walk.h
+++ b/reflog-walk.h
@@ -7,13 +7,15 @@ 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);
 void get_reflog_message(struct strbuf *sb,
 			struct reflog_walk_info *reflog_info);
+const char *get_reflog_branch(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/revision.c b/revision.c
index 88785651fe..0367ad9b5e 100644
--- a/revision.c
+++ b/revision.c
@@ -2622,6 +2622,8 @@ 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;
@@ -3249,11 +3251,15 @@ 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 ||
diff --git a/revision.h b/revision.h
index c4f5e06698..6a0fd120e5 100644
--- a/revision.h
+++ b/revision.h
@@ -269,6 +269,7 @@ 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,
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index e15f6f2381..eaf8187ba7 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -159,6 +159,52 @@ test_expect_success '--operations shows a rebase as one entry' '
 	)
 '
 
+test_expect_success '--historical-branch names the branch of each entry' '
+	(
+		cd operations &&
+
+		cat >expect <<-\EOF &&
+		(topic) HEAD@{0}: rebase (finish): returning to refs/heads/topic
+		(topic) HEAD@{1}: rebase (pick): C
+		(topic) HEAD@{2}: rebase (pick): B
+		(topic) HEAD@{3}: rebase (pick): A
+		(topic) HEAD@{4}: rebase (start): checkout main
+		(topic) HEAD@{5}: checkout: moving from main to topic
+		(main) HEAD@{6}: commit: upstream
+		(main) HEAD@{7}: checkout: moving from topic to main
+		(topic) HEAD@{8}: commit: C
+		(topic) HEAD@{9}: commit: B
+		(topic) HEAD@{10}: commit: A
+		(topic) HEAD@{11}: checkout: moving from main to topic
+		(main) HEAD@{12}: commit (initial): base
+		EOF
+		git reflog --historical-branch >out &&
+		cut -d " " -f 2- out >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch takes the place of decorations' '
+	(
+		cd operations &&
+		hash=$(git rev-parse --short HEAD) &&
+		echo "<YELLOW>$hash<RESET><YELLOW> (<RESET><BOLD;GREEN>topic<RESET><YELLOW>)<RESET>" >expect &&
+		git reflog --historical-branch --decorate --color -1 >out &&
+		test_decode_color <out | sed "s/ HEAD@.*//" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch names the ref of other reflogs' '
+	(
+		cd operations &&
+		echo "(main) main@{0}: commit: upstream" >expect &&
+		git reflog -1 main --historical-branch >out &&
+		cut -d " " -f 2- out >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success '--operations shows a stopped rebase as one entry once it finishes' '
 	set_fake_editor &&
 	(
@@ -231,8 +277,55 @@ 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' '
+	(
+		cd downstream &&
+		echo "(topic)" >expect &&
+		git reflog --historical-branch >out &&
+		cut -d " " -f 2 out | sort -u >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch follows a renamed branch' '
+	(
+		cd operations &&
+		test_commit D &&
+		git branch -m topic renamed &&
+		test_commit E &&
+
+		cat >expect <<-\EOF &&
+		(renamed) HEAD@{0}: commit: E
+		(renamed) HEAD@{1}: Branch: renamed refs/heads/topic to refs/heads/renamed
+		(topic) HEAD@{3}: commit: D
+		EOF
+		git reflog --historical-branch -3 >out &&
+		cut -d " " -f 2- out >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch shows where HEAD was detached' '
+	(
+		cd operations &&
+		git checkout --detach &&
+		test_commit F &&
+		git checkout renamed &&
+
+		cat >expect <<-EOF &&
+		(renamed) HEAD@{0}: checkout: moving from $(git rev-parse F) to renamed
+		(detached at $(git rev-parse --short F)) HEAD@{1}: commit: F
+		EOF
+		git reflog --historical-branch -2 >out &&
+		cut -d " " -f 2- out >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
 '
 
-- 
gitgitgadget

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

* Re: [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps
  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
  0 siblings, 1 reply; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-15 14:35 UTC (permalink / raw)
  To: git, GGG; +Cc: Harald Nordgren

On Thu, Oct 15, 2026, at 15:13, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> "git checkout" records a move of HEAD in the reflog as "checkout:
> moving from <old> to <new>", "git branch -m" records a rename of the
> current branch as "Branch: renamed <old> to <new>", and "git rebase"
> marks where it starts and ends with steps like "rebase (start)" and
> "rebase (finish)". "@{-<n>}" and "git status" read the switch message
> back, each with its own copy of the prefix,

Here it feels like we are thrown into an enumeration without knowing
what for. What is the problem to solve?

> and a later commit adds a
> reader for all of them.

This clause at the end of the six-line sentence seems to reveal why: we
are about to use these strings in more than one place, so we want to
introduce constants in order to not duplicate anything (no drifting
apart). But the why here should have been put at the start. And the
phrasing is also so passive that I had to do a double-take as to what
“commit” meant. A commit in the reflog under discussion? Or a commit
that *we*/*I* are about to make?

The context is that you are about to use these prefixes in more than one
place. And the principle you are following is no duplication. Then the
details follow. If the details of exactly what they say is worth stating
in the commit message.

>
> Name these strings in reflog.h and use them where the messages are
> written and read, so they cannot drift apart.
>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
>[snip]

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
                   ` (2 preceding siblings ...)
  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 ` Kristoffer Haugsbakk
  2026-10-15 15:24   ` Harald Nordgren
  2026-10-23 15:31   ` Harald Nordgren
  2026-10-16 18:36 ` D. Ben Knoble
  2026-10-18 18:27 ` [PATCH v2 " Harald Nordgren via GitGitGadget
  5 siblings, 2 replies; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-15 14:36 UTC (permalink / raw)
  To: git, GGG; +Cc: Harald Nordgren

On Thu, Oct 15, 2026, at 15:13, Harald Nordgren via GitGitGadget wrote:
> Create a better reflog. Follows the discussion in
> https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/.
>
> "git reflog" lists every step of a rebase and every branch switch, which
> makes it hard to see what was done and on which branch. This adds two
> options to "git reflog" and "git log -g":
>
>  * --operations shows one entry per command, folding a rebase into the entry
>    that finished or aborted it, and leaves out entries that change no
>    branch.
>  * --historical-branch decorates each entry with the branch HEAD was on when
>    it was recorded.

This sounds very useful. That you rebase many commits and then the reset
points (where each one started and ended) get obscured in the reflog is
classic for me. And I think many have had the same issue. This is why I
sometimes use `ORIG_HEAD` instead, or maybe make a backup ref.

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  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-16 18:26     ` D. Ben Knoble
  2026-10-23 15:31   ` Harald Nordgren
  1 sibling, 2 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-15 15:24 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, GGG

> This sounds very useful. That you rebase many commits and then the reset
> points (where each one started and ended) get obscured in the reflog is
> classic for me. And I think many have had the same issue. This is why I
> sometimes use `ORIG_HEAD` instead, or maybe make a backup ref.

Yes! I love the reflog. But I'm also endlessly confused by it.

Sometimes I'll just pick all the commits one by one and do 'git show'
on them to figure out which one to actually pick. Not a great
experience.


Harald

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  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
  1 sibling, 1 reply; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-15 15:35 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, GGGGGG

On Thu, Oct 15, 2026, at 17:24, Harald Nordgren wrote:
>> This sounds very useful. That you rebase many commits and then the reset
>> points (where each one started and ended) get obscured in the reflog is
>> classic for me. And I think many have had the same issue. This is why I
>> sometimes use `ORIG_HEAD` instead, or maybe make a backup ref.
>
> Yes! I love the reflog. But I'm also endlessly confused by it.
>
> Sometimes I'll just pick all the commits one by one and do 'git show'
> on them to figure out which one to actually pick. Not a great
> experience.

:)

I got a bit curious about this forced workflow now. I tried the first
terminal navigate-and-click Git tool I could think of, tig(1), and it
turns out that you can do this:

    $ tig reflog
    $ # navigate up/down j/k
    $ # See the commit message and diff with `d`

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  2026-10-15 15:35     ` Kristoffer Haugsbakk
@ 2026-10-15 20:47       ` Harald Nordgren
  0 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-15 20:47 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, GGGGGG

> > Sometimes I'll just pick all the commits one by one and do 'git show'
> > on them to figure out which one to actually pick. Not a great
> > experience.
>
> :)
>
> I got a bit curious about this forced workflow now. I tried the first
> terminal navigate-and-click Git tool I could think of, tig(1), and it
> turns out that you can do this:
>
>     $ tig reflog
>     $ # navigate up/down j/k
>     $ # See the commit message and diff with `d`

I don't use tig, lazygit, github desktop or vscode’s version control
extension, not because they’re not good (they probably are) but only
because I don't want to be removed from the fundamentals. I want to
feel the pain of using git :)

But that seems like an improvement, yes, thanks for pointing it out!
Good news is that git options are passed through to tig, so with this
new feature we could run:

    tig reflog --operations


Harald

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

* Re: [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps
  2026-10-15 14:35   ` Kristoffer Haugsbakk
@ 2026-10-15 20:52     ` Harald Nordgren
  0 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-15 20:52 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, GGG

> [snip]
>
> The context is that you are about to use these prefixes in more than one
> place. And the principle you are following is no duplication. Then the
> details follow. If the details of exactly what they say is worth stating
> in the commit message.

Thanks!


Harald

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  2026-10-15 15:24   ` Harald Nordgren
  2026-10-15 15:35     ` Kristoffer Haugsbakk
@ 2026-10-16 18:26     ` D. Ben Knoble
  1 sibling, 0 replies; 35+ messages in thread
From: D. Ben Knoble @ 2026-10-16 18:26 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: Kristoffer Haugsbakk, git, GGG

On Thu, Oct 15, 2026 at 12:04 PM Harald Nordgren
<haraldnordgren@gmail.com> wrote:
>
> > This sounds very useful. That you rebase many commits and then the reset
> > points (where each one started and ended) get obscured in the reflog is
> > classic for me. And I think many have had the same issue. This is why I
> > sometimes use `ORIG_HEAD` instead, or maybe make a backup ref.
>
> Yes! I love the reflog. But I'm also endlessly confused by it.
>
> Sometimes I'll just pick all the commits one by one and do 'git show'
> on them to figure out which one to actually pick. Not a great
> experience.

Perhaps `git log -g -p` in that case? :)

-- 
D. Ben Knoble

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
                   ` (3 preceding siblings ...)
  2026-10-15 14:36 ` [PATCH 0/3] reflog: add --operations and --historical-branch Kristoffer Haugsbakk
@ 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
  5 siblings, 1 reply; 35+ messages in thread
From: D. Ben Knoble @ 2026-10-16 18:36 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren

[Apologies for replying in the wrong chain; it appears I haven't
received 3/3 yet and it's easier for me to reply to a message I have
than one I can only see in lore.]

On Thu, Oct 15, 2026 at 9:20 AM Harald Nordgren via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>   reflog: add --historical-branch to name the branch of each entry

> Teach "git reflog" and "git log -g" a "--historical-branch" option
> that decorates each entry with the branch it was made on, in place of
> the refs that point at the commit now. The name is colored like a
> branch decoration:
>
>   $ git reflog --historical-branch
>   4e5f6a7 (topic) HEAD@{0}: commit: add tests
>   1a2b3c4 (topic) HEAD@{1}: checkout: moving from main to topic
>   8c9d0e1 (main) HEAD@{2}: commit: fix typo

Notice how the "HEAD@" column is now not aligned? I find it really convient to
have fixed-width data at the front of the line so that I can visually pick out
pieces in columns.

So I'd suggest an alternat end-of-line format:

    $ git reflog --historical-branch
    4e5f6a7 HEAD@{0}: commit: add tests (topic)
    1a2b3c4 HEAD@{1}: checkout: moving from main to topic (topic)
    8c9d0e1 HEAD@{2}: commit: fix typo (main)

That might make picking out the branch name a little harder, so I suppose you
could try right before the messages?

    $ 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

or (since the "operation" prefixes" are useful)

    $ git reflog --historical-branch
    4e5f6a7 HEAD@{0}: commit: (topic) add tests
    1a2b3c4 HEAD@{1}: checkout: (topic) moving from main to topic
    8c9d0e1 HEAD@{2}: commit: (main) fix typo

? Obviously the misalignment in all the examples (except the EOL format) gets
worse when you have some short and some long branch names.

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  2026-10-16 18:36 ` D. Ben Knoble
@ 2026-10-17 10:03   ` Harald Nordgren
  0 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-17 10:03 UTC (permalink / raw)
  To: D. Ben Knoble; +Cc: Harald Nordgren via GitGitGadget, git

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

Thanks!

This is my favorite then.


Harald

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

* [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
                   ` (4 preceding siblings ...)
  2026-10-16 18:36 ` D. Ben Knoble
@ 2026-10-18 18:27 ` 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
                     ` (4 more replies)
  5 siblings, 5 replies; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-18 18:27 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren

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

"git reflog" lists every step of a rebase and every branch switch, which
makes it hard to see what was done and on which branch. This adds two
options to "git reflog" and "git log -g":

 * --operations shows one entry per command, folding a rebase into the entry
   that finished or aborted it, and leaves out entries that change no
   branch.
 * --historical-branch decorates each entry with the branch HEAD was on when
   it was recorded.

Changes in v2:

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

Harald Nordgren (3):
  reflog: name the strings that mark switches, renames and rebase steps
  reflog: add --operations to show one entry per command
  reflog: add --historical-branch to name the branch of each entry

 Documentation/git-reflog.adoc       |   4 +
 Documentation/rev-list-options.adoc |  21 +++
 builtin/branch.c                    |   3 +-
 builtin/checkout.c                  |   3 +-
 builtin/rebase.c                    |  17 +--
 log-tree.c                          |  28 +++-
 object-name.c                       |   3 +-
 reflog-walk.c                       | 183 +++++++++++++++++++++++--
 reflog-walk.h                       |   6 +-
 reflog.h                            |   6 +
 revision.c                          |  14 ++
 revision.h                          |   2 +
 sequencer.c                         |  13 +-
 t/t1414-reflog-walk.sh              | 198 ++++++++++++++++++++++++++++
 wt-status.c                         |   3 +-
 15 files changed, 471 insertions(+), 33 deletions(-)


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

Range-diff vs v1:

 1:  d95ed5bd67 ! 1:  745929bd80 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
      
     -    "git checkout" records a move of HEAD in the reflog as "checkout:
     -    moving from <old> to <new>", "git branch -m" records a rename of the
     -    current branch as "Branch: renamed <old> to <new>", and "git rebase"
     -    marks where it starts and ends with steps like "rebase (start)" and
     -    "rebase (finish)". "@{-<n>}" and "git status" read the switch message
     -    back, each with its own copy of the prefix, and a later commit adds a
     -    reader for all of them.
     +    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.
      
     -    Name these strings in reflog.h and use them where the messages are
     -    written and read, so they cannot drift apart.
     +    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".
     +
     +    No change in behavior.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
 2:  579e13b36a = 2:  1c47ba225e reflog: add --operations to show one entry per command
 3:  4efe74b04e ! 3:  aa5a98b80d reflog: add --historical-branch to name the branch of each entry
     @@ Commit message
          still point at that commit, and a rebased or amended commit has none.
      
          Teach "git reflog" and "git log -g" a "--historical-branch" option
     -    that decorates each entry with the branch it was made on, in place of
     -    the refs that point at the commit now. The name is colored like a
     -    branch decoration:
     +    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:
      
            $ git reflog --historical-branch
     -      4e5f6a7 (topic) HEAD@{0}: commit: add tests
     -      1a2b3c4 (topic) HEAD@{1}: checkout: moving from main to topic
     -      8c9d0e1 (main) HEAD@{2}: commit: fix typo
     +      4e5f6a7 HEAD@{0}: (topic) commit: add tests
     +      1a2b3c4 HEAD@{1}: (topic) checkout: moving from main to topic
     +      8c9d0e1 HEAD@{2}: (main) commit: fix typo
      
          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,
     @@ Documentation/git-reflog.adoc: actions, and in addition the `HEAD` reflog record
       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 is decorated with the branch it was made on.
     ++each entry shows the branch it was made on before its message.
       
       The "list" subcommand lists all refs which have a corresponding reflog.
       
     @@ Documentation/rev-list-options.adoc: Under `--pretty=reference`, this informatio
       	rebase started. It is an error to use this option unless
       	`--walk-reflogs` is in use.
       
     ++ifndef::git-rev-list[]
      +`--historical-branch`::
     -+	Decorate each reflog entry with the branch `HEAD` was on when
     -+	the entry was recorded, such as `(topic)`, 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.
     ++	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>`,
     @@ log-tree.c: void format_decorations(struct strbuf *sb,
       	}
       }
       
     -+static void show_historical_branch(struct rev_info *opt)
     ++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;
     @@ log-tree.c: void format_decorations(struct strbuf *sb,
      +
      +	color_commit = diff_get_color(use_color, DIFF_COMMIT);
      +	color_reset = decorate_get_color(use_color, DECORATION_NONE);
     -+	fprintf(opt->diffopt.file, "%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);
     ++	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)
     @@ 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->historical_branch) {
     -+		show_historical_branch(opt);
     -+		return;
     -+	}
     - 	if (!opt->show_decorations)
     +-	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;
     + 		}
      
       ## reflog-walk.c ##
      @@
     @@ reflog-walk.c: void get_reflog_message(struct strbuf *sb,
       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;
     @@ reflog-walk.h: struct date_mode;
       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);
     ++			 struct date_mode, int force_date,
     ++			 const char *branch);
       void get_reflog_message(struct strbuf *sb,
       			struct reflog_walk_info *reflog_info);
      +const char *get_reflog_branch(struct reflog_walk_info *reflog_info);
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
      +		cd operations &&
      +
      +		cat >expect <<-\EOF &&
     -+		(topic) HEAD@{0}: rebase (finish): returning to refs/heads/topic
     -+		(topic) HEAD@{1}: rebase (pick): C
     -+		(topic) HEAD@{2}: rebase (pick): B
     -+		(topic) HEAD@{3}: rebase (pick): A
     -+		(topic) HEAD@{4}: rebase (start): checkout main
     -+		(topic) HEAD@{5}: checkout: moving from main to topic
     -+		(main) HEAD@{6}: commit: upstream
     -+		(main) HEAD@{7}: checkout: moving from topic to main
     -+		(topic) HEAD@{8}: commit: C
     -+		(topic) HEAD@{9}: commit: B
     -+		(topic) HEAD@{10}: commit: A
     -+		(topic) HEAD@{11}: checkout: moving from main to topic
     -+		(main) HEAD@{12}: commit (initial): base
     ++		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
      +		EOF
      +		git reflog --historical-branch >out &&
      +		cut -d " " -f 2- out >actual &&
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
      +	(
      +		cd operations &&
      +		hash=$(git rev-parse --short HEAD) &&
     -+		echo "<YELLOW>$hash<RESET><YELLOW> (<RESET><BOLD;GREEN>topic<RESET><YELLOW>)<RESET>" >expect &&
     ++		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 | sed "s/ HEAD@.*//" >actual &&
     ++		test_decode_color <out >actual &&
      +		test_cmp expect actual
      +	)
      +'
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
      +test_expect_success '--historical-branch names the ref of other reflogs' '
      +	(
      +		cd operations &&
     -+		echo "(main) main@{0}: commit: upstream" >expect &&
     ++		echo "main@{0}: (main) commit: upstream" >expect &&
      +		git reflog -1 main --historical-branch >out &&
      +		cut -d " " -f 2- out >actual &&
      +		test_cmp expect actual
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
      +		cd downstream &&
      +		echo "(topic)" >expect &&
      +		git reflog --historical-branch >out &&
     -+		cut -d " " -f 2 out | sort -u >actual &&
     ++		cut -d " " -f 3 out | sort -u >actual &&
      +		test_cmp expect actual
      +	)
      +'
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
      +		test_commit E &&
      +
      +		cat >expect <<-\EOF &&
     -+		(renamed) HEAD@{0}: commit: E
     -+		(renamed) HEAD@{1}: Branch: renamed refs/heads/topic to refs/heads/renamed
     -+		(topic) HEAD@{3}: commit: D
     ++		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 &&
     @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
      +		git checkout renamed &&
      +
      +		cat >expect <<-EOF &&
     -+		(renamed) HEAD@{0}: checkout: moving from $(git rev-parse F) to renamed
     -+		(detached at $(git rev-parse --short F)) HEAD@{1}: commit: F
     ++		HEAD@{0}: (renamed) checkout: moving from $(git rev-parse F) to renamed
     ++		HEAD@{1}: (detached at $(git rev-parse --short F)) commit: F
      +		EOF
      +		git reflog --historical-branch -2 >out &&
      +		cut -d " " -f 2- out >actual &&

-- 
gitgitgadget

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

* [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps
  2026-10-18 18:27 ` [PATCH v2 " Harald Nordgren via GitGitGadget
@ 2026-10-18 18:27   ` Harald Nordgren via GitGitGadget
  2026-10-23 21:19     ` 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
                     ` (3 subsequent siblings)
  4 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-18 18:27 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren,
	Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

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.

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

No change in behavior.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/branch.c   |  3 ++-
 builtin/checkout.c |  3 ++-
 builtin/rebase.c   | 17 +++++++++--------
 object-name.c      |  3 ++-
 reflog.h           |  6 ++++++
 sequencer.c        | 13 +++++++------
 wt-status.c        |  3 ++-
 7 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index a613148fc7..30062ceb7c 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -13,6 +13,7 @@
 #include "editor.h"
 #include "environment.h"
 #include "refs.h"
+#include "reflog.h"
 #include "commit.h"
 #include "gettext.h"
 #include "object-name.h"
@@ -669,7 +670,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
 		strbuf_addf(&logmsg, "Branch: copied %s to %s",
 			    oldref.buf, newref.buf);
 	else
-		strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+		strbuf_addf(&logmsg, REFLOG_RENAME_PREFIX "%s to %s",
 			    oldref.buf, newref.buf);
 
 	if (!copy && !(oldref_usage & IS_ORPHAN) &&
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 2d575a4f57..f011284084 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,6 +25,7 @@
 #include "preload-index.h"
 #include "read-cache.h"
 #include "refs.h"
+#include "reflog.h"
 #include "remote.h"
 #include "repo-settings.h"
 #include "resolve-undo.h"
@@ -999,7 +1000,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
 
 	reflog_msg = getenv("GIT_REFLOG_ACTION");
 	if (!reflog_msg)
-		strbuf_addf(&msg, "checkout: moving from %s to %s",
+		strbuf_addf(&msg, REFLOG_SWITCH_PREFIX "%s to %s",
 			old_desc ? old_desc : "(invalid)", new_branch_info->name);
 	else
 		strbuf_insertstr(&msg, 0, reflog_msg);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 535db60181..26ef589e89 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -17,6 +17,7 @@
 #include "strvec.h"
 #include "dir.h"
 #include "refs.h"
+#include "reflog.h"
 #include "config.h"
 #include "unpack-trees.h"
 #include "lockfile.h"
@@ -601,11 +602,11 @@ static int move_to_original_branch(struct rebase_options *opts)
 	if (!opts->onto)
 		BUG("move_to_original_branch without onto");
 
-	strbuf_addf(&branch_reflog, "%s (finish): %s onto %s",
-		    opts->reflog_action,
+	strbuf_addf(&branch_reflog, "%s (%s): %s onto %s",
+		    opts->reflog_action, REFLOG_STEP_FINISH,
 		    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",
+		    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;
@@ -1413,8 +1414,8 @@ 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",
+			    options.reflog_action, REFLOG_STEP_ABORT,
 			    options.head_name ? options.head_name
 					      : oid_to_hex(&options.orig_head->object.oid));
 		ropts.oid = &options.orig_head->object.oid;
@@ -1884,8 +1885,8 @@ int cmd_rebase(int argc,
 		printf(_("First, rewinding head to replay your work on top of "
 			 "it...\n"));
 
-	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);
 	ropts.oid = &options.onto->object.oid;
 	ropts.orig_head = &options.orig_head->object.oid;
 	ropts.flags = RESET_WORKING_TREE_DETACH |
diff --git a/object-name.c b/object-name.c
index 6546685760..5b6ce8e0e0 100644
--- a/object-name.c
+++ b/object-name.c
@@ -13,6 +13,7 @@
 #include "tree.h"
 #include "tree-walk.h"
 #include "refs.h"
+#include "reflog.h"
 #include "remote.h"
 #include "dir.h"
 #include "odb.h"
@@ -1251,7 +1252,7 @@ static int grab_nth_branch_switch(const char *refname UNUSED,
 	const char *match = NULL, *target = NULL;
 	size_t len;
 
-	if (skip_prefix(message, "checkout: moving from ", &match))
+	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &match))
 		target = strstr(match, " to ");
 
 	if (!match || !target)
diff --git a/reflog.h b/reflog.h
index 3bdd1ca8a5..32c3737db4 100644
--- a/reflog.h
+++ b/reflog.h
@@ -2,6 +2,12 @@
 #define REFLOG_H
 #include "refs.h"
 
+#define REFLOG_SWITCH_PREFIX "checkout: moving from "
+#define REFLOG_RENAME_PREFIX "Branch: renamed "
+#define REFLOG_STEP_START "start"
+#define REFLOG_STEP_FINISH "finish"
+#define REFLOG_STEP_ABORT "abort"
+
 #define REFLOG_EXPIRE_TOTAL   (1 << 0)
 #define REFLOG_EXPIRE_UNREACH (1 << 1)
 
diff --git a/sequencer.c b/sequencer.c
index efbfd1315b..c55df2d38f 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -31,6 +31,7 @@
 #include "merge-ort.h"
 #include "merge-ort-wrappers.h"
 #include "refs.h"
+#include "reflog.h"
 #include "sparse-index.h"
 #include "strvec.h"
 #include "quote.h"
@@ -5011,8 +5012,8 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
 			 RESET_WORKING_TREE_UPDATE_HEAD |
 			 RESET_WORKING_TREE_UPDATE_ORIG_HEAD |
 			 RESET_WORKING_TREE_RUN_POST_CHECKOUT_HOOK,
-		.head_msg = reflog_message(opts, "start", "checkout %s",
-					   onto_name),
+		.head_msg = reflog_message(opts, REFLOG_STEP_START,
+					   "checkout %s", onto_name),
 		.default_reflog_action = sequencer_reflog_action(opts)
 	};
 	if (reset_working_tree(r, &ropts)) {
@@ -5310,16 +5311,16 @@ cleanup_head_ref:
 				res = error(_("could not read 'onto'"));
 				goto cleanup_head_ref;
 			}
-			msg = reflog_message(opts, "finish", "%s onto %s",
-				head_ref.buf, buf.buf);
+			msg = reflog_message(opts, REFLOG_STEP_FINISH,
+				"%s onto %s", head_ref.buf, buf.buf);
 			if (refs_update_ref(get_main_ref_store(the_repository), msg, head_ref.buf, &head, &orig,
 					    REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
 				res = error(_("could not update %s"),
 					head_ref.buf);
 				goto 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);
 			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);
diff --git a/wt-status.c b/wt-status.c
index 57772c7501..ffd54353a8 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -22,6 +22,7 @@
 #include "strvec.h"
 #include "remote.h"
 #include "refs.h"
+#include "reflog.h"
 #include "submodule.h"
 #include "column.h"
 #include "read-cache.h"
@@ -1790,7 +1791,7 @@ static int grab_1st_switch(const char *refname UNUSED,
 	struct grab_1st_switch_cbdata *cb = cb_data;
 	const char *target = NULL, *end;
 
-	if (!skip_prefix(message, "checkout: moving from ", &message))
+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &message))
 		return 0;
 	target = strstr(message, " to ");
 	if (!target)
-- 
gitgitgadget


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

* [PATCH v2 2/3] reflog: add --operations to show one entry per command
  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-18 18:27   ` 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
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-18 18:27 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren,
	Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

After a rebase, "git reflog" shows one entry for every commit the
rebase replayed, plus one for its start and one for its end. Together
with an entry for every branch switch, finding where a branch was
before the rebase means reading past all of them.

Teach "git reflog" and "git log -g" an "--operations" option that
shows each rebase as the single entry that finished or aborted it, and
leaves out entries that change no branch, like branch switches and the
"reset: moving to HEAD" that "git stash" and "git merge --abort" leave
behind:

  $ git reflog --operations
  4e5f6a7 HEAD@{0}: rebase (finish): returning to refs/heads/topic
  8c9d0e1 HEAD@{5}: commit: fix typo
  1a2b3c4 HEAD@{7}: commit: add tests

The HEAD@{<n>} numbers keep their usual values, so the previous entry
on the same branch can be used to undo the rebase:

  $ git reset --hard HEAD@{7}

Commands run while a rebase is stopped, such as an amend after an
"edit", are folded into that rebase, and a "git pull --rebase" is shown
as one entry the same way.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-reflog.adoc       |   3 +
 Documentation/rev-list-options.adoc |  11 +++
 reflog-walk.c                       |  61 +++++++++++++++-
 reflog-walk.h                       |   1 +
 revision.c                          |   8 +++
 revision.h                          |   1 +
 t/t1414-reflog-walk.sh              | 105 ++++++++++++++++++++++++++++
 7 files changed, 188 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
index 38af0c977a..672b9ea72e 100644
--- a/Documentation/git-reflog.adoc
+++ b/Documentation/git-reflog.adoc
@@ -41,6 +41,9 @@ command-line (or `HEAD`, by default). The reflog covers all recent
 actions, and in addition the `HEAD` reflog records branch switching.
 `git reflog show` is an alias for `git log -g --abbrev-commit
 --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.
 
 The "list" subcommand lists all refs which have a corresponding reflog.
 
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index bd9f345690..a40ba2641b 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -348,6 +348,17 @@ See also linkgit:git-reflog[1].
 +
 Under `--pretty=reference`, this information will not be shown at all.
 
+`--operations`::
+	Show one reflog entry for each command that changed a branch.
+	A rebase is shown as only the entry that finished or aborted it,
+	including any commands run while it was stopped, such as
+	`git commit --amend`. Switching branches is not shown, and
+	neither is a reset that left `HEAD` where it was, such as the one
+	`git stash` does.
+	The previous entry on the same branch is the state before the
+	rebase started. It is an error to use this option unless
+	`--walk-reflogs` is in use.
+
 `--merge`::
 	Show commits touching conflicted paths in the range `HEAD...<other>`,
 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
diff --git a/reflog-walk.c b/reflog-walk.c
index 4dbeaa93a7..82528f5b66 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -3,6 +3,7 @@
 #include "git-compat-util.h"
 #include "commit.h"
 #include "refs.h"
+#include "reflog.h"
 #include "diff.h"
 #include "repository.h"
 #include "revision.h"
@@ -18,6 +19,7 @@ struct complete_reflogs {
 		timestamp_t timestamp;
 		int tz;
 		char *message;
+		bool user_operation;
 	} *items;
 	int nr, alloc;
 };
@@ -38,6 +40,7 @@ static int read_one_reflog(const char *refname UNUSED,
 	item->timestamp = timestamp;
 	item->tz = tz;
 	item->message = xstrdup(message);
+	item->user_operation = true;
 	array->nr++;
 	return 0;
 }
@@ -65,6 +68,46 @@ static void complete_reflogs_clear(void *util, const char *str UNUSED)
 	free_complete_reflog(array);
 }
 
+static bool has_rebase_step(const char *message, const char *step)
+{
+	size_t end = strcspn(message, ":\n");
+	size_t len = strlen(step);
+
+	return end >= len + 3 && message[end - 1] == ')' &&
+	       !strncmp(message + end - len - 3, " (", 2) &&
+	       !strncmp(message + end - len - 1, step, len);
+}
+
+static void mark_operations(struct complete_reflogs *array)
+{
+	int start = -1;
+
+	for (int i = 0; i < array->nr; i++) {
+		struct reflog_info *item = &array->items[i];
+		const char *msg = item->message;
+
+		if (has_rebase_step(msg, REFLOG_STEP_FINISH) ||
+		    has_rebase_step(msg, REFLOG_STEP_ABORT)) {
+			if (start >= 0)
+				for (int j = start; j < i; j++)
+					array->items[j].user_operation = false;
+			start = -1;
+			item->user_operation = true;
+			continue;
+		}
+		if (has_rebase_step(msg, REFLOG_STEP_START))
+			start = i;
+
+		if (starts_with(msg, REFLOG_SWITCH_PREFIX))
+			item->user_operation = false;
+		else if (starts_with(msg, REFLOG_RENAME_PREFIX))
+			item->user_operation = true;
+		else
+			item->user_operation =
+				!oideq(&item->ooid, &item->noid);
+	}
+}
+
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
 	struct complete_reflogs *reflogs =
@@ -135,6 +178,16 @@ 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)
+{
+	struct string_list_item *item;
+
+	for_each_string_list_item(item, &info->complete_reflogs) {
+		if (operations)
+			mark_operations(item->util);
+	}
+}
+
 void reflog_walk_info_release(struct reflog_walk_info *info)
 {
 	size_t i;
@@ -342,8 +395,12 @@ static struct commit *next_reflog_commit(struct commit_reflog *log)
 {
 	for (; log->recno >= 0; log->recno--) {
 		struct reflog_info *entry = &log->reflogs->items[log->recno];
-		struct object *obj = parse_object(the_repository,
-						  &entry->noid);
+		struct object *obj;
+
+		if (!entry->user_operation)
+			continue;
+
+		obj = parse_object(the_repository, &entry->noid);
 
 		if (obj && obj->type == OBJ_COMMIT)
 			return (struct commit *)obj;
diff --git a/reflog-walk.h b/reflog-walk.h
index 989583dc55..1d86795f84 100644
--- a/reflog-walk.h
+++ b/reflog-walk.h
@@ -7,6 +7,7 @@ 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);
 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,
diff --git a/revision.c b/revision.c
index a83e499047..88785651fe 100644
--- a/revision.c
+++ b/revision.c
@@ -2458,6 +2458,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		commit_list_insert(c, &revs->ancestry_path_bottoms);
 	} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
 		init_reflog_walk(&revs->reflog_info);
+	} else if (!strcmp(arg, "--operations")) {
+		revs->reflog_operations = 1;
 	} else if (!strcmp(arg, "--default")) {
 		if (argc <= 1)
 			return error("bad --default argument");
@@ -3247,6 +3249,12 @@ 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->reflog_operations)
+		die(_("the option '%s' requires '%s'"), "--operations", "--walk-reflogs");
+
+	if (revs->reflog_info)
+		reflog_walk_mark(revs->reflog_info, revs->reflog_operations);
+
 	if (revs->line_level_traverse &&
 	    (revs->full_diff ||
 	     (revs->diffopt.output_format &
diff --git a/revision.h b/revision.h
index e5dabd18ce..c4f5e06698 100644
--- a/revision.h
+++ b/revision.h
@@ -221,6 +221,7 @@ struct rev_info {
 			exclude_first_parent_only:1,
 			line_level_traverse:1,
 			tree_blobs_in_commit_order:1,
+			reflog_operations:1,
 
 			/*
 			 * Blobs are shown without regard for their existence.
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index be6c3f472c..e15f6f2381 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 
 test_expect_success 'set up some reflog entries' '
 	test_commit one &&
@@ -131,6 +132,110 @@ test_expect_success 'walk prefers reflog to ref tip' '
 	test_cmp expect actual
 '
 
+test_expect_success '--operations shows a rebase as one entry' '
+	git init operations &&
+	(
+		cd operations &&
+		test_commit base &&
+		git switch -c topic &&
+		test_commit A &&
+		test_commit B &&
+		test_commit C &&
+		git switch main &&
+		test_commit upstream &&
+		git switch topic &&
+		git rebase main &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (finish): returning to refs/heads/topic
+		HEAD@{6} commit: upstream
+		HEAD@{8} commit: C
+		HEAD@{9} commit: B
+		HEAD@{10} commit: A
+		HEAD@{12} commit (initial): base
+		EOF
+		git log -g --format="%gd %gs" HEAD --operations >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows a stopped rebase as one entry once it finishes' '
+	set_fake_editor &&
+	(
+		cd operations &&
+		FAKE_LINES="edit 1 2" git rebase -i HEAD~2 &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase: fast-forward
+		HEAD@{1} rebase (start): checkout HEAD~2
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual &&
+
+		git commit --amend -m "B amended" &&
+		git rebase --continue &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (finish): returning to refs/heads/topic
+		HEAD@{5} rebase (finish): returning to refs/heads/topic
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows an aborted rebase as one entry' '
+	(
+		cd operations &&
+		FAKE_LINES="edit 1 2" git rebase -i HEAD~2 &&
+		git rebase --abort &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} rebase (abort): returning to refs/heads/topic
+		HEAD@{3} rebase (finish): returning to refs/heads/topic
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations leaves out the reset done by git stash' '
+	(
+		cd operations &&
+		echo change >>C.t &&
+		git stash &&
+
+		echo "HEAD@{1} rebase (abort): returning to refs/heads/topic" >expect &&
+		git log -g --operations --format="%gd %gs" -1 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations shows a pull --rebase as one entry' '
+	git clone operations downstream &&
+	(
+		cd operations &&
+		test_commit upstream-change
+	) &&
+	(
+		cd downstream &&
+		test_commit local-change &&
+		git pull --rebase &&
+
+		cat >expect <<-\EOF &&
+		HEAD@{0} pull --rebase (finish): returning to refs/heads/topic
+		HEAD@{3} commit: local-change
+		EOF
+		git log -g --operations --format="%gd %gs" -2 >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--operations requires --walk-reflogs' '
+	test_must_fail git log --operations 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


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

* [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry
  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-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   ` Harald Nordgren via GitGitGadget
  2026-10-25 19:18     ` Kristoffer Haugsbakk
  2026-10-23 15:32   ` [PATCH v2 0/3] reflog: add --operations and --historical-branch Harald Nordgren
  2026-10-24 13:51   ` Phillip Wood
  4 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren via GitGitGadget @ 2026-10-18 18:27 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren,
	Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

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

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:

  $ 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

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.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-reflog.adoc       |   3 +-
 Documentation/rev-list-options.adoc |  10 +++
 log-tree.c                          |  28 ++++++-
 reflog-walk.c                       | 124 +++++++++++++++++++++++++---
 reflog-walk.h                       |   7 +-
 revision.c                          |   8 +-
 revision.h                          |   1 +
 t/t1414-reflog-walk.sh              |  95 ++++++++++++++++++++-
 8 files changed, 258 insertions(+), 18 deletions(-)

diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc
index 672b9ea72e..04ed7849c2 100644
--- a/Documentation/git-reflog.adoc
+++ b/Documentation/git-reflog.adoc
@@ -43,7 +43,8 @@ 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.
 
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc
index a40ba2641b..8c0010695b 100644
--- a/Documentation/rev-list-options.adoc
+++ b/Documentation/rev-list-options.adoc
@@ -359,6 +359,16 @@ 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`,
diff --git a/log-tree.c b/log-tree.c
index 83a3c4bf9b..b2d9a954e7 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -421,6 +421,24 @@ 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;
@@ -431,7 +449,7 @@ 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);
@@ -834,16 +852,22 @@ 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;
 		}
diff --git a/reflog-walk.c b/reflog-walk.c
index 82528f5b66..0cdca76efd 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -5,6 +5,8 @@
 #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"
@@ -19,6 +21,7 @@ struct complete_reflogs {
 		timestamp_t timestamp;
 		int tz;
 		char *message;
+		char *branch;
 		bool user_operation;
 	} *items;
 	int nr, alloc;
@@ -40,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;
 	item->user_operation = true;
 	array->nr++;
 	return 0;
@@ -55,6 +59,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);
@@ -108,6 +113,80 @@ static void mark_operations(struct complete_reflogs *array)
 	}
 }
 
+static char *branch_label(const char *name, size_t len)
+{
+	struct object_id oid;
+	const char *short_name;
+
+	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);
+}
+
+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;
+
+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) &&
+	    !skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from))
+		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");
+	return true;
+}
+
+static void mark_branches(struct complete_reflogs *array)
+{
+	char *branch = NULL;
+
+	if (strcmp(array->ref, "HEAD"))
+		return;
+
+	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)
+			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) {
+		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);
+	}
+	free(branch);
+}
+
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
 	struct complete_reflogs *reflogs =
@@ -178,13 +257,16 @@ 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);
 	}
 }
 
@@ -287,6 +369,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,
@@ -300,12 +392,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;
 	}
@@ -340,6 +427,18 @@ void get_reflog_message(struct strbuf *sb,
 	strbuf_add(sb, info->message, len);
 }
 
+const char *get_reflog_branch(struct reflog_walk_info *reflog_info)
+{
+	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+
+	if (!commit_reflog)
+		return NULL;
+
+	if (strcmp(commit_reflog->reflogs->ref, "HEAD"))
+		return short_ref(commit_reflog->reflogs);
+	return commit_reflog->reflogs->items[commit_reflog->recno+1].branch;
+}
+
 const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
 {
 	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
@@ -365,7 +464,8 @@ 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;
@@ -375,11 +475,13 @@ 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);
diff --git a/reflog-walk.h b/reflog-walk.h
index 1d86795f84..8951251f8a 100644
--- a/reflog-walk.h
+++ b/reflog-walk.h
@@ -7,13 +7,16 @@ 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);
 void get_reflog_message(struct strbuf *sb,
 			struct reflog_walk_info *reflog_info);
+const char *get_reflog_branch(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/revision.c b/revision.c
index 88785651fe..0367ad9b5e 100644
--- a/revision.c
+++ b/revision.c
@@ -2622,6 +2622,8 @@ 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;
@@ -3249,11 +3251,15 @@ 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 ||
diff --git a/revision.h b/revision.h
index c4f5e06698..6a0fd120e5 100644
--- a/revision.h
+++ b/revision.h
@@ -269,6 +269,7 @@ 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,
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index e15f6f2381..6d5272aacf 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -159,6 +159,52 @@ test_expect_success '--operations shows a rebase as one entry' '
 	)
 '
 
+test_expect_success '--historical-branch names the branch of each entry' '
+	(
+		cd operations &&
+
+		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
+		EOF
+		git reflog --historical-branch >out &&
+		cut -d " " -f 2- out >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch takes the place of decorations' '
+	(
+		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 &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch names the ref of other reflogs' '
+	(
+		cd operations &&
+		echo "main@{0}: (main) commit: upstream" >expect &&
+		git reflog -1 main --historical-branch >out &&
+		cut -d " " -f 2- out >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success '--operations shows a stopped rebase as one entry once it finishes' '
 	set_fake_editor &&
 	(
@@ -231,8 +277,55 @@ 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' '
+	(
+		cd downstream &&
+		echo "(topic)" >expect &&
+		git reflog --historical-branch >out &&
+		cut -d " " -f 3 out | sort -u >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch follows a renamed branch' '
+	(
+		cd operations &&
+		test_commit D &&
+		git branch -m topic renamed &&
+		test_commit E &&
+
+		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 &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '--historical-branch shows where HEAD was detached' '
+	(
+		cd operations &&
+		git checkout --detach &&
+		test_commit F &&
+		git checkout renamed &&
+
+		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
+		EOF
+		git reflog --historical-branch -2 >out &&
+		cut -d " " -f 2- out >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
 '
 
-- 
gitgitgadget

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

* Re: [PATCH 0/3] reflog: add --operations and --historical-branch
  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-23 15:31   ` Harald Nordgren
  1 sibling, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-23 15:31 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, GGG

> This sounds very useful. That you rebase many commits and then the reset
> points (where each one started and ended) get obscured in the reflog is
> classic for me. And I think many have had the same issue. This is why I
> sometimes use `ORIG_HEAD` instead, or maybe make a backup ref.

Hi Kristoffer!

Would you have any time to take a look at the latest version of this?


Harald

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-18 18:27 ` [PATCH v2 " Harald Nordgren via GitGitGadget
                     ` (2 preceding siblings ...)
  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-23 15:32   ` Harald Nordgren
  2026-10-24 13:51   ` Phillip Wood
  4 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-23 15:32 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, D. Ben Knoble; +Cc: git

Hi Ben!

Would you have some time to take a look at the latest version here?


Harald

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

* Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps
  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
  0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2026-10-23 21:19 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren

"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:

> 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".
>
> No change in behavior.

Would it make sense to test the behaviour to demonstrate this claim?

> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
>  builtin/branch.c   |  3 ++-
>  builtin/checkout.c |  3 ++-
>  builtin/rebase.c   | 17 +++++++++--------
>  object-name.c      |  3 ++-
>  reflog.h           |  6 ++++++
>  sequencer.c        | 13 +++++++------
>  wt-status.c        |  3 ++-
>  7 files changed, 30 insertions(+), 18 deletions(-)

> diff --git a/reflog.h b/reflog.h
> index 3bdd1ca8a5..32c3737db4 100644
> --- a/reflog.h
> +++ b/reflog.h
> @@ -2,6 +2,12 @@
>  #define REFLOG_H
>  #include "refs.h"
>  
> +#define REFLOG_SWITCH_PREFIX "checkout: moving from "
> +#define REFLOG_RENAME_PREFIX "Branch: renamed "
> +#define REFLOG_STEP_START "start"
> +#define REFLOG_STEP_FINISH "finish"
> +#define REFLOG_STEP_ABORT "abort"

I was hoping that we could eventually make reflog messages
translatable, by splitting each entry in the reflog record into
two: a human-readable part (shown in the usual output of the
'git reflog' command) and a machine-parseable part to be used
exactly for features like this.  We would hopefully need to update
only reflog_message() and its dozens of callers to make it happen.

I wonder how far this change will set us back from that future.
The best case would be that this detour does not make it any
harder.  The locations this patch touches are where we would
need to leave clues in the machine-parseable format, and the
locations touched by later patches in the series are the places
that need to be updated to look at the machine-parseable part.

The changes in this series looked sensible, though.

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

* Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps
  2026-10-23 21:19     ` Junio C Hamano
@ 2026-10-23 22:47       ` Harald Nordgren
  2026-10-23 22:55         ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren @ 2026-10-23 22:47 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	D. Ben Knoble

On Fri, Oct 23, 2026 at 11:19 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > 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".
> >
> > No change in behavior.
>
> Would it make sense to test the behaviour to demonstrate this claim?

We have previous tests for this which I think would break if this was
incorrectly refactored.


Harald

On Fri, Oct 23, 2026 at 11:19 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > 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".
> >
> > No change in behavior.
>
> Would it make sense to test the behaviour to demonstrate this claim?
>
> > Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> > ---
> >  builtin/branch.c   |  3 ++-
> >  builtin/checkout.c |  3 ++-
> >  builtin/rebase.c   | 17 +++++++++--------
> >  object-name.c      |  3 ++-
> >  reflog.h           |  6 ++++++
> >  sequencer.c        | 13 +++++++------
> >  wt-status.c        |  3 ++-
> >  7 files changed, 30 insertions(+), 18 deletions(-)
>
> > diff --git a/reflog.h b/reflog.h
> > index 3bdd1ca8a5..32c3737db4 100644
> > --- a/reflog.h
> > +++ b/reflog.h
> > @@ -2,6 +2,12 @@
> >  #define REFLOG_H
> >  #include "refs.h"
> >
> > +#define REFLOG_SWITCH_PREFIX "checkout: moving from "
> > +#define REFLOG_RENAME_PREFIX "Branch: renamed "
> > +#define REFLOG_STEP_START "start"
> > +#define REFLOG_STEP_FINISH "finish"
> > +#define REFLOG_STEP_ABORT "abort"
>
> I was hoping that we could eventually make reflog messages
> translatable, by splitting each entry in the reflog record into
> two: a human-readable part (shown in the usual output of the
> 'git reflog' command) and a machine-parseable part to be used
> exactly for features like this.  We would hopefully need to update
> only reflog_message() and its dozens of callers to make it happen.
>
> I wonder how far this change will set us back from that future.
> The best case would be that this detour does not make it any
> harder.  The locations this patch touches are where we would
> need to leave clues in the machine-parseable format, and the
> locations touched by later patches in the series are the places
> that need to be updated to look at the machine-parseable part.
>
> The changes in this series looked sensible, though.

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

* Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps
  2026-10-23 22:47       ` Harald Nordgren
@ 2026-10-23 22:55         ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-23 22:55 UTC (permalink / raw)
  To: Harald Nordgren
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	D. Ben Knoble

Harald Nordgren <haraldnordgren@gmail.com> writes:

> On Fri, Oct 23, 2026 at 11:19 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>
>> > 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".
>> >
>> > No change in behavior.
>>
>> Would it make sense to test the behaviour to demonstrate this claim?
>
> We have previous tests for this which I think would break if this was
> incorrectly refactored.

OK, if we have sufficient coverage, that would be fine.


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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-18 18:27 ` [PATCH v2 " Harald Nordgren via GitGitGadget
                     ` (3 preceding siblings ...)
  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
                       ` (2 more replies)
  4 siblings, 3 replies; 35+ messages in thread
From: Phillip Wood @ 2026-10-24 13:51 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, D. Ben Knoble, Harald Nordgren,
	Junio C Hamano

Hi Harald

On 18/10/2026 19:27, Harald Nordgren via GitGitGadget wrote:
> Create a better reflog. Follows the discussion in
> https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/.
> 
> "git reflog" lists every step of a rebase and every branch switch, which
> makes it hard to see what was done and on which branch. This adds two
> options to "git reflog" and "git log -g":
> 
>   * --operations shows one entry per command, folding a rebase into the entry
>     that finished or aborted it, and leaves out entries that change no
>     branch.

I'm rather skeptical of this in its current form. Not all commands that 
modify the working tree update the reflog and some commands such as "git 
cherry-pick A B C" create multiple reflog entires that are 
indistinguishable from multiple invocations of a single command. The 
message you cite above is arguing that we need a separate operations log 
if we want to overcome these problems. Most of what this option does can 
be achieved by looking at the branch reflog instead (I really wish that 
"git reflog" defaulted to the current branch rather than HEAD). I'm not 
opposed to supporting some kind of filtering/simplification particularly 
for HEAD, but we should not pretend we're showing users one entry per 
command.

>   * --historical-branch decorates each entry with the branch HEAD was on when
>     it was recorded.

I quite like this idea, but think it would be nicer implemented as a 
format specifier ("%gb" seems to be free) so that users can use it in 
their custom formats. I'd be quite happy to change the default format do
for HEAD to include the branch name after the reflog message but I'm not 
sure others would be so keen (perhaps we could get away with guarding 
any change by isatty()/GIT_PAGER_IN_USE?).

If we're going to do this we should make sure it supports "git rebase 
<upstream> <branch>" so that the branch names are accurate. That is a 
bit involved because you have to compare the name of the branch being 
rebased (which is in the "(finish)" reflog message) to the name of the 
branch that was checked out before (which isn't in that message so you 
have to go looking for a previous rebase or checkout). It also needs to 
take account of rebases that checkout a detached HEAD which don't have 
the "(finish)" message and those that checkout an up-to-date branch or 
detached HEAD where the message is "$GIT_REFLOG_ACTION: checkout 
$revision". I've got a patch [1] that fixes this for "@{-N}" which I 
think works, but it needs some more tests.

Thanks

Phillip

[1] 
https://github.com/git/git/compare/master...phillipwood:git:fix-nth-prior-checkout

> Changes in v2:
> 
>   * Move the "historical branch" rightward in the display for cleaner
>     alignment.
>   * Improve commit messages.
> 
> Harald Nordgren (3):
>    reflog: name the strings that mark switches, renames and rebase steps
>    reflog: add --operations to show one entry per command
>    reflog: add --historical-branch to name the branch of each entry
> 
>   Documentation/git-reflog.adoc       |   4 +
>   Documentation/rev-list-options.adoc |  21 +++
>   builtin/branch.c                    |   3 +-
>   builtin/checkout.c                  |   3 +-
>   builtin/rebase.c                    |  17 +--
>   log-tree.c                          |  28 +++-
>   object-name.c                       |   3 +-
>   reflog-walk.c                       | 183 +++++++++++++++++++++++--
>   reflog-walk.h                       |   6 +-
>   reflog.h                            |   6 +
>   revision.c                          |  14 ++
>   revision.h                          |   2 +
>   sequencer.c                         |  13 +-
>   t/t1414-reflog-walk.sh              | 198 ++++++++++++++++++++++++++++
>   wt-status.c                         |   3 +-
>   15 files changed, 471 insertions(+), 33 deletions(-)
> 
> 
> base-commit: c51408889c3b372814bc20b9431c3b74d0d268eb
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2448%2FHaraldNordgren%2Freflog-operations-v2
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2448/HaraldNordgren/reflog-operations-v2
> Pull-Request: https://github.com/git/git/pull/2448
> 
> Range-diff vs v1:
> 
>   1:  d95ed5bd67 ! 1:  745929bd80 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
>        
>       -    "git checkout" records a move of HEAD in the reflog as "checkout:
>       -    moving from <old> to <new>", "git branch -m" records a rename of the
>       -    current branch as "Branch: renamed <old> to <new>", and "git rebase"
>       -    marks where it starts and ends with steps like "rebase (start)" and
>       -    "rebase (finish)". "@{-<n>}" and "git status" read the switch message
>       -    back, each with its own copy of the prefix, and a later commit adds a
>       -    reader for all of them.
>       +    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.
>        
>       -    Name these strings in reflog.h and use them where the messages are
>       -    written and read, so they cannot drift apart.
>       +    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".
>       +
>       +    No change in behavior.
>        
>            Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
>        
>   2:  579e13b36a = 2:  1c47ba225e reflog: add --operations to show one entry per command
>   3:  4efe74b04e ! 3:  aa5a98b80d reflog: add --historical-branch to name the branch of each entry
>       @@ Commit message
>            still point at that commit, and a rebased or amended commit has none.
>        
>            Teach "git reflog" and "git log -g" a "--historical-branch" option
>       -    that decorates each entry with the branch it was made on, in place of
>       -    the refs that point at the commit now. The name is colored like a
>       -    branch decoration:
>       +    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:
>        
>              $ git reflog --historical-branch
>       -      4e5f6a7 (topic) HEAD@{0}: commit: add tests
>       -      1a2b3c4 (topic) HEAD@{1}: checkout: moving from main to topic
>       -      8c9d0e1 (main) HEAD@{2}: commit: fix typo
>       +      4e5f6a7 HEAD@{0}: (topic) commit: add tests
>       +      1a2b3c4 HEAD@{1}: (topic) checkout: moving from main to topic
>       +      8c9d0e1 HEAD@{2}: (main) commit: fix typo
>        
>            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,
>       @@ Documentation/git-reflog.adoc: actions, and in addition the `HEAD` reflog record
>         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 is decorated with the branch it was made on.
>       ++each entry shows the branch it was made on before its message.
>         
>         The "list" subcommand lists all refs which have a corresponding reflog.
>         
>       @@ Documentation/rev-list-options.adoc: Under `--pretty=reference`, this informatio
>         	rebase started. It is an error to use this option unless
>         	`--walk-reflogs` is in use.
>         
>       ++ifndef::git-rev-list[]
>        +`--historical-branch`::
>       -+	Decorate each reflog entry with the branch `HEAD` was on when
>       -+	the entry was recorded, such as `(topic)`, 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.
>       ++	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>`,
>       @@ log-tree.c: void format_decorations(struct strbuf *sb,
>         	}
>         }
>         
>       -+static void show_historical_branch(struct rev_info *opt)
>       ++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;
>       @@ log-tree.c: void format_decorations(struct strbuf *sb,
>        +
>        +	color_commit = diff_get_color(use_color, DIFF_COMMIT);
>        +	color_reset = decorate_get_color(use_color, DECORATION_NONE);
>       -+	fprintf(opt->diffopt.file, "%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);
>       ++	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)
>       @@ 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->historical_branch) {
>       -+		show_historical_branch(opt);
>       -+		return;
>       -+	}
>       - 	if (!opt->show_decorations)
>       +-	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;
>       + 		}
>        
>         ## reflog-walk.c ##
>        @@
>       @@ reflog-walk.c: void get_reflog_message(struct strbuf *sb,
>         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;
>       @@ reflog-walk.h: struct date_mode;
>         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);
>       ++			 struct date_mode, int force_date,
>       ++			 const char *branch);
>         void get_reflog_message(struct strbuf *sb,
>         			struct reflog_walk_info *reflog_info);
>        +const char *get_reflog_branch(struct reflog_walk_info *reflog_info);
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
>        +		cd operations &&
>        +
>        +		cat >expect <<-\EOF &&
>       -+		(topic) HEAD@{0}: rebase (finish): returning to refs/heads/topic
>       -+		(topic) HEAD@{1}: rebase (pick): C
>       -+		(topic) HEAD@{2}: rebase (pick): B
>       -+		(topic) HEAD@{3}: rebase (pick): A
>       -+		(topic) HEAD@{4}: rebase (start): checkout main
>       -+		(topic) HEAD@{5}: checkout: moving from main to topic
>       -+		(main) HEAD@{6}: commit: upstream
>       -+		(main) HEAD@{7}: checkout: moving from topic to main
>       -+		(topic) HEAD@{8}: commit: C
>       -+		(topic) HEAD@{9}: commit: B
>       -+		(topic) HEAD@{10}: commit: A
>       -+		(topic) HEAD@{11}: checkout: moving from main to topic
>       -+		(main) HEAD@{12}: commit (initial): base
>       ++		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
>        +		EOF
>        +		git reflog --historical-branch >out &&
>        +		cut -d " " -f 2- out >actual &&
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
>        +	(
>        +		cd operations &&
>        +		hash=$(git rev-parse --short HEAD) &&
>       -+		echo "<YELLOW>$hash<RESET><YELLOW> (<RESET><BOLD;GREEN>topic<RESET><YELLOW>)<RESET>" >expect &&
>       ++		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 | sed "s/ HEAD@.*//" >actual &&
>       ++		test_decode_color <out >actual &&
>        +		test_cmp expect actual
>        +	)
>        +'
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one
>        +test_expect_success '--historical-branch names the ref of other reflogs' '
>        +	(
>        +		cd operations &&
>       -+		echo "(main) main@{0}: commit: upstream" >expect &&
>       ++		echo "main@{0}: (main) commit: upstream" >expect &&
>        +		git reflog -1 main --historical-branch >out &&
>        +		cut -d " " -f 2- out >actual &&
>        +		test_cmp expect actual
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
>        +		cd downstream &&
>        +		echo "(topic)" >expect &&
>        +		git reflog --historical-branch >out &&
>       -+		cut -d " " -f 2 out | sort -u >actual &&
>       ++		cut -d " " -f 3 out | sort -u >actual &&
>        +		test_cmp expect actual
>        +	)
>        +'
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
>        +		test_commit E &&
>        +
>        +		cat >expect <<-\EOF &&
>       -+		(renamed) HEAD@{0}: commit: E
>       -+		(renamed) HEAD@{1}: Branch: renamed refs/heads/topic to refs/heads/renamed
>       -+		(topic) HEAD@{3}: commit: D
>       ++		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 &&
>       @@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a pull --rebase
>        +		git checkout renamed &&
>        +
>        +		cat >expect <<-EOF &&
>       -+		(renamed) HEAD@{0}: checkout: moving from $(git rev-parse F) to renamed
>       -+		(detached at $(git rev-parse --short F)) HEAD@{1}: commit: F
>       ++		HEAD@{0}: (renamed) checkout: moving from $(git rev-parse F) to renamed
>       ++		HEAD@{1}: (detached at $(git rev-parse --short F)) commit: F
>        +		EOF
>        +		git reflog --historical-branch -2 >out &&
>        +		cut -d " " -f 2- out >actual &&
> 


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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  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 19:12     ` Harald Nordgren
  2 siblings, 0 replies; 35+ messages in thread
From: D. Ben Knoble @ 2026-10-24 15:22 UTC (permalink / raw)
  To: phillip.wood
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	Harald Nordgren, Junio C Hamano

On Sat, Oct 24, 2026 at 9:51 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Harald
>
> On 18/10/2026 19:27, Harald Nordgren via GitGitGadget wrote:
> > Create a better reflog. Follows the discussion in
> > https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/.
> >
> > "git reflog" lists every step of a rebase and every branch switch, which
> > makes it hard to see what was done and on which branch. This adds two
> > options to "git reflog" and "git log -g":
> >
> >   * --operations shows one entry per command, folding a rebase into the entry
> >     that finished or aborted it, and leaves out entries that change no
> >     branch.
>
> I'm rather skeptical of this in its current form. Not all commands that
> modify the working tree update the reflog and some commands such as "git
> cherry-pick A B C" create multiple reflog entires that are
> indistinguishable from multiple invocations of a single command.

Great point. I thought it was very neat that we could make progress
towards this simply by parsing out existing reflogs, but it's always
going to be a bit heuristic.

> The
> message you cite above is arguing that we need a separate operations log
> if we want to overcome these problems.

I also thought this series would be doing something like a true ops
log, which would be very cool.

> Most of what this option does can
> be achieved by looking at the branch reflog instead (I really wish that
> "git reflog" defaulted to the current branch rather than HEAD). I'm not
> opposed to supporting some kind of filtering/simplification particularly
> for HEAD, but we should not pretend we're showing users one entry per
> command.

Ditto

> >   * --historical-branch decorates each entry with the branch HEAD was on when
> >     it was recorded.
>
> I quite like this idea, but think it would be nicer implemented as a
> format specifier ("%gb" seems to be free) so that users can use it in
> their custom formats. I'd be quite happy to change the default format do
> for HEAD to include the branch name after the reflog message but I'm not
> sure others would be so keen (perhaps we could get away with guarding
> any change by isatty()/GIT_PAGER_IN_USE?).

I'm quite a bit more positive on this change, but I like the
composability of a format-specifier. Great idea.

> If we're going to do this we should make sure it supports "git rebase
> <upstream> <branch>" so that the branch names are accurate. That is a
> bit involved because you have to compare the name of the branch being
> rebased (which is in the "(finish)" reflog message) to the name of the
> branch that was checked out before (which isn't in that message so you
> have to go looking for a previous rebase or checkout). It also needs to
> take account of rebases that checkout a detached HEAD which don't have
> the "(finish)" message and those that checkout an up-to-date branch or
> detached HEAD where the message is "$GIT_REFLOG_ACTION: checkout
> $revision". I've got a patch [1] that fixes this for "@{-N}" which I
> think works, but it needs some more tests.

Ouch. This definitely points to my "heuristic" above, and while we
could maybe invest time in the reflog + parsing to make this work, it
seems somehow simpler to have an event or ops log with more structure
for this new purpose.

--
D. Ben Knoble

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  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-26 10:52       ` Phillip Wood
  2026-10-24 19:12     ` Harald Nordgren
  2 siblings, 2 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-24 16:40 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	D. Ben Knoble, Harald Nordgren

Phillip Wood <phillip.wood123@gmail.com> writes:

> I'm rather skeptical of this in its current form. Not all commands that 
> modify the working tree update the reflog and some commands such as "git 
> cherry-pick A B C" create multiple reflog entires that are 
> indistinguishable from multiple invocations of a single command.

I like the direction to make the default 'git reflog' output on a
single branch easier to understand without needing any extra
filtering in this series.

'git rebase' doing its work on a detached HEAD was likely the
first, and we may want to convert more multi-step commands to do
so, including the multiple 'git cherry-pick' you mention.  'git am'
way predates the rebase that works on a detached HEAD ('git am'
introduced in Oct 2005 predates the concept of detached HEAD that
was introduced in Git 1.5.0 released in Feb 2007), and it does its
work without detaching HEAD.  Its users grew to expect it to work
that way, but I am not sure how problematic a regression it would
be if we taught it to work more like 'git rebase'.  For example,
because I am used to 'git am' working on the current branch, I
often do

    $ git checkout --detach master
    $ git am -s ...mailbox...
    $ git checkout -b pw/topic-by-phillip

to compensate for the lack of it, in order to keep the events
recorded in the reflog of the pw/topic-by-phillip branch to "one
iteration per entry".  This helps me compare iterations more easily,
e.g.,

    $ git checkout pw/topic-by-phillip
    $ git diff @{1}
    $ git range-diff @{1}...

But the workflow to actively detach by the end user would work just
fine if 'git am' learns to detach first when started on a concrete
branch, to work more like 'git rebase'.

By the way, I too was annoyed that 'git reflog' defaults to the log
of HEAD a long time ago, but the irritation soon waned once I made
it a habit to always provide @{now} or @{0} to the command, as that
notation refers to the current branch.

Thanks.

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  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
  1 sibling, 1 reply; 35+ messages in thread
From: Harald Nordgren @ 2026-10-24 18:46 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Phillip Wood, Harald Nordgren via GitGitGadget, git,
	Kristoffer Haugsbakk, D. Ben Knoble

On Sat, Oct 24, 2026 at 6:40 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > I'm rather skeptical of this in its current form. Not all commands that
> > modify the working tree update the reflog and some commands such as "git
> > cherry-pick A B C" create multiple reflog entires that are
> > indistinguishable from multiple invocations of a single command.
>
> I like the direction to make the default 'git reflog' output on a
> single branch easier to understand without needing any extra
> filtering in this series.
>
> 'git rebase' doing its work on a detached HEAD was likely the
> first, and we may want to convert more multi-step commands to do
> so, including the multiple 'git cherry-pick' you mention.  'git am'
> way predates the rebase that works on a detached HEAD ('git am'
> introduced in Oct 2005 predates the concept of detached HEAD that
> was introduced in Git 1.5.0 released in Feb 2007), and it does its
> work without detaching HEAD.  Its users grew to expect it to work
> that way, but I am not sure how problematic a regression it would
> be if we taught it to work more like 'git rebase'.  For example,
> because I am used to 'git am' working on the current branch, I
> often do
>
>     $ git checkout --detach master
>     $ git am -s ...mailbox...
>     $ git checkout -b pw/topic-by-phillip
>
> to compensate for the lack of it, in order to keep the events
> recorded in the reflog of the pw/topic-by-phillip branch to "one
> iteration per entry".  This helps me compare iterations more easily,
> e.g.,
>
>     $ git checkout pw/topic-by-phillip
>     $ git diff @{1}
>     $ git range-diff @{1}...
>
> But the workflow to actively detach by the end user would work just
> fine if 'git am' learns to detach first when started on a concrete
> branch, to work more like 'git rebase'.
>
> By the way, I too was annoyed that 'git reflog' defaults to the log
> of HEAD a long time ago, but the irritation soon waned once I made
> it a habit to always provide @{now} or @{0} to the command, as that
> notation refers to the current branch.

Thanks Phillip, Ben and Junio for pointing this out!

I didn't realize that reflog could be changed to operate only on one
branch. And it makes it a lot better! Seems like it should be the
default, and I don't think a lot of people would complain, I'll
certainly never run the old reflog again.

However, the format is quite heavy with the full name references,
seems like they could be cleaned up:

```
0b8dc70369 (HEAD -> fetch-shallow-narrow-refspec,
origin/fetch-shallow-narrow-refspec)
refs/heads/fetch-shallow-narrow-refspec@{0}: >
e4882d2794 refs/heads/fetch-shallow-narrow-refspec@{1}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 7e34d8174dcda3>
4b0cb31dd7 refs/heads/fetch-shallow-narrow-refspec@{2}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 7e34d8174dcda3>
11d2326038 refs/heads/fetch-shallow-narrow-refspec@{3}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 4603c84d688c32>
83ce62663e refs/heads/fetch-shallow-narrow-refspec@{4}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 7e34d8174dcda3>
d633b1fc1b refs/heads/fetch-shallow-narrow-refspec@{5}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 7e34d8174dcda3>
9ca7fe6077 refs/heads/fetch-shallow-narrow-refspec@{6}: pull (finish):
refs/heads/fetch-shallow-narrow-refspec onto 7e34d8174dcda343>
552bf08971 (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v10)
refs/heads/fetch-shallow-narrow-refspec@{7}: rebase (f>
9ee090d3d0 (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v9)
refs/heads/fetch-shallow-narrow-refspec@{8}: rebase (fi>
db27c29086 (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v8)
refs/heads/fetch-shallow-narrow-refspec@{9}: rebase (fi>
10c6be7ce9 refs/heads/fetch-shallow-narrow-refspec@{10}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 6de20f6092dcf>
acb47efdc1 refs/heads/fetch-shallow-narrow-refspec@{11}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto b5db64d56fa32>
9fe17a1968 refs/heads/fetch-shallow-narrow-refspec@{12}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto b5db64d56fa32>
591e69aa88 refs/heads/fetch-shallow-narrow-refspec@{13}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto b5db64d56fa32>
16c467682b refs/heads/fetch-shallow-narrow-refspec@{14}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto b5db64d56fa32>
d5107dcb5d refs/heads/fetch-shallow-narrow-refspec@{15}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 6de20f6092dcf>
c8fd073de3 (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v7)
refs/heads/fetch-shallow-narrow-refspec@{16}: rebase (f>
632ad9e6eb refs/heads/fetch-shallow-narrow-refspec@{17}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 6de20f6092dcf>
cbeb1bf547 refs/heads/fetch-shallow-narrow-refspec@{18}: pull
(finish): refs/heads/fetch-shallow-narrow-refspec onto
6de20f6092dcf9b>
005e30ee30 refs/heads/fetch-shallow-narrow-refspec@{19}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
2609473369 refs/heads/fetch-shallow-narrow-refspec@{20}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
d986bb7abe refs/heads/fetch-shallow-narrow-refspec@{21}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 7303c95b3b667>
01d4bba698 refs/heads/fetch-shallow-narrow-refspec@{22}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
844ca50185 refs/heads/fetch-shallow-narrow-refspec@{23}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 2e37944e2883f>
864fc79e86 refs/heads/fetch-shallow-narrow-refspec@{24}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 2e37944e2883f>
8212be8acc refs/heads/fetch-shallow-narrow-refspec@{25}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
de83f683aa refs/heads/fetch-shallow-narrow-refspec@{26}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
bc9f5f0a75 refs/heads/fetch-shallow-narrow-refspec@{27}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto f9856e2cf3f65>
29869baf42 refs/heads/fetch-shallow-narrow-refspec@{28}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 8103b446517e0>
bb9c735cc5 refs/heads/fetch-shallow-narrow-refspec@{29}: pull
(finish): refs/heads/fetch-shallow-narrow-refspec onto
8103b446517e0c4>
84d192445c (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v6)
refs/heads/fetch-shallow-narrow-refspec@{30}: rebase (f>
5dd7145ab7 refs/heads/fetch-shallow-narrow-refspec@{31}: pull
(finish): refs/heads/fetch-shallow-narrow-refspec onto
c46c1e37724f047>
8c2a144eb7 (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v5)
refs/heads/fetch-shallow-narrow-refspec@{32}: rebase (f>
43b9711a2c (tag:
pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v4)
refs/heads/fetch-shallow-narrow-refspec@{33}: pull (fin>
73fec138c3 refs/heads/fetch-shallow-narrow-refspec@{34}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto d3ee079ea9d66>
49f194522a refs/heads/fetch-shallow-narrow-refspec@{35}: commit
(amend): remote: default to --limited-fetch in a shallow repository
84f6640ab9 refs/heads/fetch-shallow-narrow-refspec@{36}: rebase
(finish): refs/heads/fetch-shallow-narrow-refspec onto 0f8e75abebff0>
```


Harald

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  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 19:12     ` Harald Nordgren
  2 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-24 19:12 UTC (permalink / raw)
  To: phillip.wood
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	D. Ben Knoble, Junio C Hamano

> >   * --historical-branch decorates each entry with the branch HEAD was on when
> >     it was recorded.
>
> I quite like this idea, but think it would be nicer implemented as a
> format specifier ("%gb" seems to be free) so that users can use it in
> their custom formats. I'd be quite happy to change the default format do
> for HEAD to include the branch name after the reflog message but I'm not
> sure others would be so keen (perhaps we could get away with guarding
> any change by isatty()/GIT_PAGER_IN_USE?).

Seems like an interesting plan to do `%gb`, instead. It radically
changes this series and drops `--operations` altogether then.

> If we're going to do this we should make sure it supports "git rebase
> <upstream> <branch>" so that the branch names are accurate. That is a
> bit involved because you have to compare the name of the branch being
> rebased (which is in the "(finish)" reflog message) to the name of the
> branch that was checked out before (which isn't in that message so you
> have to go looking for a previous rebase or checkout). It also needs to
> take account of rebases that checkout a detached HEAD which don't have
> the "(finish)" message and those that checkout an up-to-date branch or
> detached HEAD where the message is "$GIT_REFLOG_ACTION: checkout
> $revision". I've got a patch [1] that fixes this for "@{-N}" which I
> think works, but it needs some more tests.

Nice, looking forward to it.


Harald

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-24 18:46       ` Harald Nordgren
@ 2026-10-25 18:52         ` Kristoffer Haugsbakk
  0 siblings, 0 replies; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-25 18:52 UTC (permalink / raw)
  To: Harald Nordgren, Junio C Hamano; +Cc: Phillip Wood, git, D. Ben Knoble, GGG

On Sat, Oct 24, 2026, at 20:46, Harald Nordgren wrote:
> On Sat, Oct 24, 2026 at 6:40 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>
>> > I'm rather skeptical of this in its current form. Not all commands that
>> > modify the working tree update the reflog and some commands such as "git
>> > cherry-pick A B C" create multiple reflog entires that are
>> > indistinguishable from multiple invocations of a single command.
>>
>>[snip]
>>
>> By the way, I too was annoyed that 'git reflog' defaults to the log
>> of HEAD a long time ago, but the irritation soon waned once I made
>> it a habit to always provide @{now} or @{0} to the command, as that
>> notation refers to the current branch.
>
> Thanks Phillip, Ben and Junio for pointing this out!
>
> I didn't realize that reflog could be changed to operate only on one
> branch. And it makes it a lot better! Seems like it should be the
> default, and I don't think a lot of people would complain, I'll
> certainly never run the old reflog again.

Well. I wrote in this thread:

> That you rebase many commits and then the reset points (where each one
> started and ended) get obscured in the reflog is classic for me. And I
> think many have had the same issue.

But now I wonder if that was just an illusion:

    $ # The rebase was for five commits
    $ git reflog --no-decorate test-branch
    0e77d8619b8 test-branch@{0}: rebase (finish): refs/heads/test-branch onto 7e34d8174dcda3435d1404c9a30bef46afe6c13e
    d1bcad06e24 test-branch@{1}: branch: Created from HEAD

I have always known about per-branch reflogs and that `HEAD` has its
own, but maybe I was too much in the habit of running without the branch
name after rebases?

So maybe my use case was fine all along. But that still leaves
Patrick’s[1] problems with it.

[1] https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/

>
> However, the format is quite heavy with the full name references,
> seems like they could be cleaned up:
>
>[snip]

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

* Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry
  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
  0 siblings, 1 reply; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-25 19:18 UTC (permalink / raw)
  To: git, GGG; +Cc: D. Ben Knoble, Harald Nordgren

On Sun, Oct 18, 2026, at 20:27, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>[snip]
> diff --git a/reflog-walk.c b/reflog-walk.c
>[snip]
>  static struct complete_reflogs *read_complete_reflog(const char *ref)
>  {
>  	struct complete_reflogs *reflogs =
> @@ -178,13 +257,16 @@ 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);

This was from the previous commit. A function that uses a `bool` param,
iterates/walks over a list, and then *if* `operations` is `true` (1)
calls a function, that looks weird to me. You’re either going to do it
to every item or just walk the list without doing anything.

> +		if (historical_branch)
> +			mark_branches(item->util);

But now you have two `bool` and you do two checks.

This is less weird to my eyes but these conditions inside a for-each
construct/macro still looks like a bad approach. Can’t you check each
condition and then call for-each with an unconditional `mark_branches`
for each item?

    if (cond)
        for-each item in items
            fun(item)

>  	}
>  }
>
> @@ -287,6 +369,16 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
>  	return 0;
>  }
>[snip]

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

* Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry
  2026-10-25 19:18     ` Kristoffer Haugsbakk
@ 2026-10-25 20:22       ` Harald Nordgren
  2026-10-26  8:39         ` Kristoffer Haugsbakk
  0 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren @ 2026-10-25 20:22 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, GGG, D. Ben Knoble

On Sun, Oct 25, 2026 at 9:18 PM Kristoffer Haugsbakk
<kristofferhaugsbakk@fastmail.com> wrote:
>
> On Sun, Oct 18, 2026, at 20:27, Harald Nordgren via GitGitGadget wrote:
> > From: Harald Nordgren <haraldnordgren@gmail.com>
> >[snip]
> > diff --git a/reflog-walk.c b/reflog-walk.c
> >[snip]
> >  static struct complete_reflogs *read_complete_reflog(const char *ref)
> >  {
> >       struct complete_reflogs *reflogs =
> > @@ -178,13 +257,16 @@ 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);
>
> This was from the previous commit. A function that uses a `bool` param,
> iterates/walks over a list, and then *if* `operations` is `true` (1)
> calls a function, that looks weird to me. You’re either going to do it
> to every item or just walk the list without doing anything.
>
> > +             if (historical_branch)
> > +                     mark_branches(item->util);
>
> But now you have two `bool` and you do two checks.
>
> This is less weird to my eyes but these conditions inside a for-each
> construct/macro still looks like a bad approach. Can’t you check each
> condition and then call for-each with an unconditional `mark_branches`
> for each item?
>
>     if (cond)
>         for-each item in items
>             fun(item)
>
> >       }
> >  }
> >
> > @@ -287,6 +369,16 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
> >       return 0;
> >  }
> >[snip]

Thanks! But maybe not worth reviewing this code now considering that
we don't know if we will even do any of this now?


Harald

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

* Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry
  2026-10-25 20:22       ` Harald Nordgren
@ 2026-10-26  8:39         ` Kristoffer Haugsbakk
  2026-10-26  8:42           ` Harald Nordgren
  0 siblings, 1 reply; 35+ messages in thread
From: Kristoffer Haugsbakk @ 2026-10-26  8:39 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: git, D. Ben Knoble, GiGiGa

On Sun, Oct 25, 2026, at 21:22, Harald Nordgren wrote:
> On Sun, Oct 25, 2026 at 9:18 PM Kristoffer Haugsbakk
> <kristofferhaugsbakk@fastmail.com> wrote:
>> >
>> >[snip]
>
> Thanks! But maybe not worth reviewing this code now considering that
> we don't know if we will even do any of this now?

I think reviewing can be about the author and the other person
learning something.

In this case I remembered that part of the code from
skimming previously.

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

* Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry
  2026-10-26  8:39         ` Kristoffer Haugsbakk
@ 2026-10-26  8:42           ` Harald Nordgren
  0 siblings, 0 replies; 35+ messages in thread
From: Harald Nordgren @ 2026-10-26  8:42 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: git, D. Ben Knoble, GiGiGa

On Mon, Oct 26, 2026 at 10:40 AM Kristoffer Haugsbakk
<kristofferhaugsbakk@fastmail.com> wrote:
>
> On Sun, Oct 25, 2026, at 21:22, Harald Nordgren wrote:
> > On Sun, Oct 25, 2026 at 9:18 PM Kristoffer Haugsbakk
> > <kristofferhaugsbakk@fastmail.com> wrote:
> >> >
> >> >[snip]
> >
> > Thanks! But maybe not worth reviewing this code now considering that
> > we don't know if we will even do any of this now?
>
> I think reviewing can be about the author and the other person
> learning something.
>
> In this case I remembered that part of the code from
> skimming previously.

I appreciated it, I just didn't want you to waste your time!


Harald

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-24 16:40     ` Junio C Hamano
  2026-10-24 18:46       ` Harald Nordgren
@ 2026-10-26 10:52       ` Phillip Wood
  2026-10-26 11:00         ` Harald Nordgren
  1 sibling, 1 reply; 35+ messages in thread
From: Phillip Wood @ 2026-10-26 10:52 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	D. Ben Knoble, Harald Nordgren

On 24/10/2026 17:40, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> I'm rather skeptical of this in its current form. Not all commands that
>> modify the working tree update the reflog and some commands such as "git
>> cherry-pick A B C" create multiple reflog entires that are
>> indistinguishable from multiple invocations of a single command.
> 
> I like the direction to make the default 'git reflog' output on a
> single branch easier to understand without needing any extra
> filtering in this series.
> 
> 'git rebase' doing its work on a detached HEAD was likely the
> first, and we may want to convert more multi-step commands to do
> so, including the multiple 'git cherry-pick' you mention. 

It would certainly be nice to avoid creating lots of entries in the 
branch reflog when cherry-picking. I'm not sure what users would make of 
us switching to a detached HEAD as some people seem to find it 
confusing; if we do decide to do that we should make sure we update "git 
status" to explain what's happening. I'm more relaxed about changing 
"git am", but like you I normally detach HEAD first so maybe I'm not the 
best person to ask about that.

> By the way, I too was annoyed that 'git reflog' defaults to the log
> of HEAD a long time ago, but the irritation soon waned once I made
> it a habit to always provide @{now} or @{0} to the command, as that
> notation refers to the current branch.
Oh, I hadn't realized that - thanks for the tip

Phillip

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-26 10:52       ` Phillip Wood
@ 2026-10-26 11:00         ` Harald Nordgren
  2026-10-26 15:07           ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Harald Nordgren @ 2026-10-26 11:00 UTC (permalink / raw)
  To: phillip.wood
  Cc: Junio C Hamano, Harald Nordgren via GitGitGadget, git,
	Kristoffer Haugsbakk, D. Ben Knoble

> It would certainly be nice to avoid creating lots of entries in the
> branch reflog when cherry-picking. I'm not sure what users would make of
> us switching to a detached HEAD as some people seem to find it
> confusing; if we do decide to do that we should make sure we update "git
> status" to explain what's happening. I'm more relaxed about changing
> "git am", but like you I normally detach HEAD first so maybe I'm not the
> best person to ask about that.

I think that the fact that all of us have our tricks to work around
things is indicative of a real problem.

We should attack the root cause and simplify git so that even us would
prefer vanilla git.


Harald

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

* Re: [PATCH v2 0/3] reflog: add --operations and --historical-branch
  2026-10-26 11:00         ` Harald Nordgren
@ 2026-10-26 15:07           ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-26 15:07 UTC (permalink / raw)
  To: Harald Nordgren
  Cc: phillip.wood, Harald Nordgren via GitGitGadget, git,
	Kristoffer Haugsbakk, D. Ben Knoble

Harald Nordgren <haraldnordgren@gmail.com> writes:

>> It would certainly be nice to avoid creating lots of entries in the
>> branch reflog when cherry-picking. I'm not sure what users would make of
>> us switching to a detached HEAD as some people seem to find it
>> confusing; if we do decide to do that we should make sure we update "git
>> status" to explain what's happening. I'm more relaxed about changing
>> "git am", but like you I normally detach HEAD first so maybe I'm not the
>> best person to ask about that.
>
> I think that the fact that all of us have our tricks to work around
> things is indicative of a real problem.
>
> We should attack the root cause and simplify git so that even us would
> prefer vanilla git.

I do use and prefer vanilla Git as it was designed to work, though.


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

end of thread, other threads:[~2026-10-26 15:07 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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

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.