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
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ 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] 8+ 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
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ 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] 8+ 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
  2026-10-15 14:36 ` [PATCH 0/3] reflog: add --operations and --historical-branch Kristoffer Haugsbakk
  3 siblings, 0 replies; 8+ 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] 8+ 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
  3 siblings, 0 replies; 8+ 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] 8+ 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
  0 siblings, 0 replies; 8+ 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] 8+ 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
  3 siblings, 1 reply; 8+ 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] 8+ 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
  0 siblings, 1 reply; 8+ 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] 8+ 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
  0 siblings, 0 replies; 8+ 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] 8+ messages in thread

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

Thread overview: 8+ 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 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

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.