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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox