Git development
 help / color / mirror / Atom feed
From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
	"D. Ben Knoble" <ben.knoble@gmail.com>,
	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

  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