From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
"D. Ben Knoble" <ben.knoble@gmail.com>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v2 0/3] reflog: add --operations and --historical-branch
Date: Sun, 18 Oct 2026 18:27:14 +0000 [thread overview]
Message-ID: <pull.2448.v2.git.git.1792348037.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2448.git.git.1792070038.gitgitgadget@gmail.com>
Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/akZfm-igZKeHaDST@pks.im/.
"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
next prev parent reply other threads:[~2026-10-18 18:27 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 13:13 [PATCH 0/3] reflog: add --operations and --historical-branch Harald Nordgren via GitGitGadget
2026-10-15 13:13 ` [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps Harald Nordgren via GitGitGadget
2026-10-15 14:35 ` Kristoffer Haugsbakk
2026-10-15 20:52 ` Harald Nordgren
2026-10-15 13:13 ` [PATCH 2/3] reflog: add --operations to show one entry per command Harald Nordgren via GitGitGadget
2026-10-15 13:13 ` [PATCH 3/3] reflog: add --historical-branch to name the branch of each entry Harald Nordgren via GitGitGadget
2026-10-15 14:36 ` [PATCH 0/3] reflog: add --operations and --historical-branch Kristoffer Haugsbakk
2026-10-15 15:24 ` Harald Nordgren
2026-10-15 15:35 ` Kristoffer Haugsbakk
2026-10-15 20:47 ` Harald Nordgren
2026-10-16 18:26 ` D. Ben Knoble
2026-10-16 18:36 ` D. Ben Knoble
2026-10-17 10:03 ` Harald Nordgren
2026-10-18 18:27 ` Harald Nordgren via GitGitGadget [this message]
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 ` [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry Harald Nordgren via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2448.v2.git.git.1792348037.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=ben.knoble@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
--cc=kristofferhaugsbakk@fastmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.