Skip to contentSkip to conversation

reflog: add --operations and --historical-branch

PATCH Superseded Harald Nordgren via GitGitGadget 6 participants 51 messages Oct 15 – Nov 7 Documentation

lore
Share & tools
lore
#0 lore raw

cover reflog: add --operations and --historical-branch

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

"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

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/3 reflog: name the strings that mark switches, renames and rebase steps

From: Harald Nordgren <[email protected]>

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

---
 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(-)
builtin/branch.c +2 -1
index a613148fc7..30062ceb7c 100644
@@ -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) &&
builtin/checkout.c +2 -1
index c0f0d2c700..9f0f09e50f 100644
@@ -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);
builtin/rebase.c +9 -8
index 535db60181..26ef589e89 100644
@@ -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 |
object-name.c +2 -1
index 4eda8c8eac..bb349014ee 100644
@@ -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)
reflog.h +6 -0
index 3bdd1ca8a5..32c3737db4 100644
@@ -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)
 
sequencer.c +7 -6
index efbfd1315b..c55df2d38f 100644
@@ -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);
wt-status.c +2 -1
index 57772c7501..ffd54353a8 100644
@@ -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)
Signature · 1 line
gitgitgadget
#2 lore raw

Re: [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps

Quoted 8 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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

Quoted 2 lines

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.

Quoted 7 lines

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

---
[snip]

#3 lore raw

Re: [PATCH 1/3] reflog: name the strings that mark switches, renames and rebase steps

Quoted 6 lines

[snip]

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

Thanks!

Harald

#4 lore raw

PATCH 2/3 reflog: add --operations to show one entry per command

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 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(-)
Documentation/git-reflog.adoc +3 -0
index 38af0c977a..672b9ea72e 100644
@@ -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.
 
Documentation/rev-list-options.adoc +11 -0
index bd9f345690..a40ba2641b 100644
@@ -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`,
reflog-walk.c +59 -2

97 lines not on this page · raw message

reflog-walk.h +1 -0
index 989583dc55..1d86795f84 100644
@@ -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,
revision.c +8 -0
index a83e499047..88785651fe 100644
@@ -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 &
revision.h +1 -0
index e5dabd18ce..c4f5e06698 100644
@@ -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.
t/t1414-reflog-walk.sh +105 -0

117 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#5 lore raw

PATCH 3/3 reflog: add --historical-branch to name the branch of each entry

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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

---
 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(-)
Documentation/git-reflog.adoc +2 -1
index 672b9ea72e..cd988f438b 100644
@@ -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.
 
Documentation/rev-list-options.adoc +8 -0
index a40ba2641b..d2a0a9b3bc 100644
@@ -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`,
log-tree.c +21 -0
index 83a3c4bf9b..d457f0451a 100644
@@ -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);
reflog-walk.c +106 -7

173 lines not on this page · raw message

reflog-walk.h +3 -1
index 1d86795f84..223f4a0dd3 100644
@@ -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,
revision.c +7 -1
index 88785651fe..0367ad9b5e 100644
@@ -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 ||
revision.h +1 -0
index c4f5e06698..6a0fd120e5 100644
@@ -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,
t/t1414-reflog-walk.sh +94 -1

108 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#6 lore raw
In reply to Harald Nordgren via GitGitGadget
Quoted 7 lines · Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

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

Quoted 5 lines

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

#7 lore raw
Quoted 4 lines

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

#8 lore raw
Quoted 10 lines · Harald Nordgren
Quoted 4 lines

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`
#9 lore raw
Quoted 6 lines
Quoted 3 lines

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.

:)

Quoted 7 lines

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

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

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

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

    tig reflog --operations

Harald

#10 lore raw
In reply to Harald Nordgren
Quoted 11 lines · Harald Nordgren
Quoted 5 lines

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

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

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

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

Signature · 1 line
D. Ben Knoble
#11 lore raw
In reply to Kristoffer Haugsbakk
Quoted 4 lines

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

Hi Kristoffer!

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

Harald

#12 lore raw
In reply to Harald Nordgren via GitGitGadget

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

Quoted 11 lines · Harald Nordgren via GitGitGadget

reflog: add --historical-branch to name the branch of each entry

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

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

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

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

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

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

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

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

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

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

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

Thanks!

This is my favorite then.

Harald

#14 lore raw

v2 cover reflog: add --operations and --historical-branch

In reply to Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

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

 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 &&
Signature · 1 line
gitgitgadget
#15 lore raw

PATCH v2 1/3 reflog: name the strings that mark switches, renames and rebase steps

From: Harald Nordgren <[email protected]>

The next commit adds "git reflog --operations", which finds branch switches, branch renames and the start and end of a rebase by the messages they leave in the reflog. The commands that write these messages spell out the strings by hand, and "@{-<n>}" and "git status" each keep their own copy of the switch prefix to read it back. Reading them in one more place would add yet another copy that has to stay in sync.

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps written by "git rebase".

No change in behavior.

---
 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(-)
builtin/branch.c +2 -1
index a613148fc7..30062ceb7c 100644
@@ -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) &&
builtin/checkout.c +2 -1
index 2d575a4f57..f011284084 100644
@@ -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);
builtin/rebase.c +9 -8
index 535db60181..26ef589e89 100644
@@ -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 |
object-name.c +2 -1
index 6546685760..5b6ce8e0e0 100644
@@ -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)
reflog.h +6 -0
index 3bdd1ca8a5..32c3737db4 100644
@@ -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)
 
sequencer.c +7 -6
index efbfd1315b..c55df2d38f 100644
@@ -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);
wt-status.c +2 -1
index 57772c7501..ffd54353a8 100644
@@ -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)
Signature · 1 line
gitgitgadget
#16 lore raw

Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps

Quoted 7 lines · Harald Nordgren via GitGitGadget

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps written by "git rebase".

No change in behavior.

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

Quoted 16 lines
---
 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(-)
reflog.h +0 -0
index 3bdd1ca8a5..32c3737db4 100644

reflog.h line 9

Quoted 8 lines
reflog.h +5 -0
@@ -2,6 +2,12 @@
 #define REFLOG_H
 #include "refs.h"
 
+#define REFLOG_SWITCH_PREFIX "checkout: moving from "
+#define REFLOG_RENAME_PREFIX "Branch: renamed "
+#define REFLOG_STEP_START "start"
+#define REFLOG_STEP_FINISH "finish"
+#define REFLOG_STEP_ABORT "abort"

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

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

The changes in this series looked sensible, though.

#17 lore raw

Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps

Quoted 9 lines · Junio C Hamano
Quoted 6 lines · Harald Nordgren via GitGitGadget

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

Quoted 3 lines
Quoted 1 line

No change in behavior.

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

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

Harald

Quoted 53 lines · Junio C Hamano
Quoted 7 lines · Harald Nordgren via GitGitGadget

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps written by "git rebase".

No change in behavior.

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

Quoted 16 lines
---
 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(-)
reflog.h +0 -0
index 3bdd1ca8a5..32c3737db4 100644

reflog.h line 9

Quoted 8 lines
reflog.h +5 -0
@@ -2,6 +2,12 @@
 #define REFLOG_H
 #include "refs.h"
 
+#define REFLOG_SWITCH_PREFIX "checkout: moving from "
+#define REFLOG_RENAME_PREFIX "Branch: renamed "
+#define REFLOG_STEP_START "start"
+#define REFLOG_STEP_FINISH "finish"
+#define REFLOG_STEP_ABORT "abort"

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

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

The changes in this series looked sensible, though.

#18 lore raw

Re: [PATCH v2 1/3] reflog: name the strings that mark switches, renames and rebase steps

Quoted 10 lines · Harald Nordgren
Quoted 9 lines · Junio C Hamano
Quoted 6 lines · Harald Nordgren via GitGitGadget

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

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

No change in behavior.

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

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

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

#19 lore raw

PATCH v2 2/3 reflog: add --operations to show one entry per command

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

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.

---
 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(-)
Documentation/git-reflog.adoc +3 -0
index 38af0c977a..672b9ea72e 100644
@@ -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.
 
Documentation/rev-list-options.adoc +11 -0
index bd9f345690..a40ba2641b 100644
@@ -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`,
reflog-walk.c +59 -2

97 lines not on this page · raw message

reflog-walk.h +1 -0
index 989583dc55..1d86795f84 100644
@@ -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,
revision.c +8 -0
index a83e499047..88785651fe 100644
@@ -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 &
revision.h +1 -0
index e5dabd18ce..c4f5e06698 100644
@@ -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.
t/t1414-reflog-walk.sh +105 -0

117 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#20 lore raw

PATCH v2 3/3 reflog: add --historical-branch to name the branch of each entry

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

"git reflog" does not say which branch HEAD was on when an entry was recorded. After switching between a few branches, telling which commits and rebases belong to which branch means reading back to the last "checkout: moving from" entry for each of them. Commit decorations do not help either, since they only name branches that still point at that commit, and a rebased or amended commit has none.

Teach "git reflog" and "git log -g" a "--historical-branch" option that shows the branch each entry was made on before its message, in place of the refs that point at the commit now. The hashes and selectors stay lined up in their columns, and the name is colored like a branch decoration:

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

Every entry gets a name, including ones whose commits are no longer on any branch. Entries from before a branch was renamed show its old name, and entries made on a detached HEAD show "detached at <commit>". Combined with "--operations", which leaves out the branch switches themselves, it lists what was done to each branch.

---
 Documentation/git-reflog.adoc       |   3 +-
 Documentation/rev-list-options.adoc |  10 +++
 log-tree.c                          |  28 ++++++-
 reflog-walk.c                       | 124 +++++++++++++++++++++++++---
 reflog-walk.h                       |   7 +-
 revision.c                          |   8 +-
 revision.h                          |   1 +
 t/t1414-reflog-walk.sh              |  95 ++++++++++++++++++++-
 8 files changed, 258 insertions(+), 18 deletions(-)
Documentation/git-reflog.adoc +2 -1
index 672b9ea72e..04ed7849c2 100644
@@ -43,7 +43,8 @@ actions, and in addition the `HEAD` reflog records branch switching.
 --pretty=oneline`; see linkgit:git-log[1] for more information.
 With `--operations`, branch switches and other entries that change no
 branch are left out, and a rebase is shown as a single entry instead
-of one entry for each commit it replayed.
+of one entry for each commit it replayed. With `--historical-branch`,
+each entry shows the branch it was made on before its message.
 
 The "list" subcommand lists all refs which have a corresponding reflog.
 
Documentation/rev-list-options.adoc +10 -0
index a40ba2641b..8c0010695b 100644
@@ -359,6 +359,16 @@ Under `--pretty=reference`, this information will not be shown at all.
 	rebase started. It is an error to use this option unless
 	`--walk-reflogs` is in use.
 
+ifndef::git-rev-list[]
+`--historical-branch`::
+	Show the branch `HEAD` was on when each reflog entry was
+	recorded, such as `(topic)`, before the reflog message, in
+	place of the refs that point at the commit now. When `HEAD` was
+	detached, the entry shows `(detached at <commit>)`. For the
+	reflog of any other ref, the name of that ref is shown. It is
+	an error to use this option unless `--walk-reflogs` is in use.
+endif::git-rev-list[]
+
 `--merge`::
 	Show commits touching conflicted paths in the range `HEAD...<other>`,
 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
log-tree.c +26 -2
index 83a3c4bf9b..b2d9a954e7 100644
@@ -421,6 +421,24 @@ void format_decorations(struct strbuf *sb,
 	}
 }
 
+static void format_historical_branch(struct strbuf *sb,
+				     struct rev_info *opt)
+{
+	const char *branch = get_reflog_branch(opt->reflog_info);
+	enum git_colorbool use_color = opt->diffopt.use_color;
+	const char *color_commit, *color_reset;
+
+	if (!branch)
+		return;
+
+	color_commit = diff_get_color(use_color, DIFF_COMMIT);
+	color_reset = decorate_get_color(use_color, DECORATION_NONE);
+	strbuf_addf(sb, "%s(%s%s%s%s%s)%s ",
+		    color_commit, color_reset,
+		    decorate_get_color(use_color, DECORATION_REF_LOCAL),
+		    branch, color_reset, color_commit, color_reset);
+}
+
 void show_decorations(struct rev_info *opt, struct commit *commit)
 {
 	struct strbuf sb = STRBUF_INIT;
@@ -431,7 +449,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
 		if (slot && *slot)
 			fprintf(opt->diffopt.file, "\t%s", *slot);
 	}
-	if (!opt->show_decorations)
+	if (!opt->show_decorations || opt->historical_branch)
 		return;
 	format_decorations(&sb, commit, opt->diffopt.use_color, NULL);
 	fputs(sb.buf, opt->diffopt.file);
@@ -834,16 +852,22 @@ void show_log(struct rev_info *opt)
 			graph_show_oneline(opt->graph);
 		}
 		if (opt->reflog_info) {
+			struct strbuf branch = STRBUF_INIT;
+
 			/*
 			 * setup_revisions() ensures that opt->reflog_info
 			 * and opt->graph cannot both be set,
 			 * so we don't need to worry about printing the
 			 * graph info here.
 			 */
+			if (opt->historical_branch)
+				format_historical_branch(&branch, opt);
 			show_reflog_message(opt->reflog_info,
 					    opt->commit_format == CMIT_FMT_ONELINE,
 					    opt->date_mode,
-					    opt->date_mode_explicit);
+					    opt->date_mode_explicit,
+					    branch.buf);
+			strbuf_release(&branch);
 			if (opt->commit_format == CMIT_FMT_ONELINE)
 				return;
 		}
reflog-walk.c +113 -11

198 lines not on this page · raw message

reflog-walk.h +5 -2
index 1d86795f84..8951251f8a 100644
@@ -7,13 +7,16 @@ struct date_mode;
 
 void init_reflog_walk(struct reflog_walk_info **info);
 void reflog_walk_info_release(struct reflog_walk_info *info);
-void reflog_walk_mark(struct reflog_walk_info *info, bool operations);
+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
+		      bool historical_branch);
 int add_reflog_for_walk(struct reflog_walk_info *info,
 			struct commit *commit, const char *name);
 void show_reflog_message(struct reflog_walk_info *info, int,
-			 struct date_mode, int force_date);
+			 struct date_mode, int force_date,
+			 const char *branch);
 void get_reflog_message(struct strbuf *sb,
 			struct reflog_walk_info *reflog_info);
+const char *get_reflog_branch(struct reflog_walk_info *reflog_info);
 const char *get_reflog_ident(struct reflog_walk_info *reflog_info);
 timestamp_t get_reflog_timestamp(struct reflog_walk_info *reflog_info);
 void get_reflog_selector(struct strbuf *sb,
revision.c +7 -1
index 88785651fe..0367ad9b5e 100644
@@ -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 ||
revision.h +1 -0
index c4f5e06698..6a0fd120e5 100644
@@ -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,
t/t1414-reflog-walk.sh +94 -1

108 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#21 lore raw

Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry

Quoted 10 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>
[snip]

reflog-walk.c +0 -0
[snip]
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
 	struct complete_reflogs *reflogs =
@@ -178,13 +257,16 @@ void init_reflog_walk(struct reflog_walk_info **info)
 	(*info)->complete_reflogs.strdup_strings = 1;
 }

reflog-walk.c void init_reflog_walk(struct reflog_walk_info **info) · line 267

Quoted 10 lines
reflog-walk.c +2 -1
 
-void reflog_walk_mark(struct reflog_walk_info *info, bool operations)
+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
+		      bool historical_branch)
 {
 	struct string_list_item *item;
 
 	for_each_string_list_item(item, &info->complete_reflogs) {
 		if (operations)
 			mark_operations(item->util);

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

reflog-walk.c void init_reflog_walk(struct reflog_walk_info **info) · line 269

Quoted 2 lines
reflog-walk.c +2 -0
+		if (historical_branch)
+			mark_branches(item->util);

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

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

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

Quoted 7 lines
reflog-walk.c +0 -0
 	}
 }
 
@@ -287,6 +369,16 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
 	return 0;
 }
[snip]
#22 lore raw

Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry

Quoted 42 lines · Kristoffer Haugsbakk
Quoted 10 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>
[snip]

reflog-walk.c +0 -0
[snip]
 static struct complete_reflogs *read_complete_reflog(const char *ref)
 {
      struct complete_reflogs *reflogs =
@@ -178,13 +257,16 @@ void init_reflog_walk(struct reflog_walk_info **info)
      (*info)->complete_reflogs.strdup_strings = 1;
 }

reflog-walk.c void init_reflog_walk(struct reflog_walk_info **info) · line 267

Quoted 10 lines
reflog-walk.c +2 -1
 
-void reflog_walk_mark(struct reflog_walk_info *info, bool operations)
+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
+                   bool historical_branch)
 {
      struct string_list_item *item;
 
      for_each_string_list_item(item, &info->complete_reflogs) {
              if (operations)
                      mark_operations(item->util);

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

reflog-walk.c void init_reflog_walk(struct reflog_walk_info **info) · line 269

Quoted 2 lines
reflog-walk.c +2 -0
+             if (historical_branch)
+                     mark_branches(item->util);

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

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

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

Quoted 7 lines

reflog-walk.c int add_reflog_for_walk(struct reflog_walk_info *info, · line 370

Quoted 7 lines
reflog-walk.c +0 -0
      }
 }
 
@@ -287,6 +369,16 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
      return 0;
 }
[snip]

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

Harald

#23 lore raw

Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry

Quoted 7 lines · Harald Nordgren
Quoted 2 lines · Kristoffer Haugsbakk
Quoted 2 lines

[snip]

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

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

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

#24 lore raw

Re: [PATCH v2 3/3] reflog: add --historical-branch to name the branch of each entry

Quoted 7 lines · Kristoffer Haugsbakk
Quoted 5 lines · Harald Nordgren
Quoted 2 lines · Kristoffer Haugsbakk
Quoted 2 lines

[snip]

Quoted 8 lines
Quoted 2 lines

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

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

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

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

Harald

#26 lore raw
In reply to Harald Nordgren via GitGitGadget

Hi Harald

Quoted 10 lines · Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

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

* --operations shows one entry per command, folding a rebase into the entry that finished or aborted it, and leaves out entries that change no branch.

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

Quoted 2 lines

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

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

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

Thanks

Phillip

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

Quoted 322 lines

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.

  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 &&
#27 lore raw
Quoted 11 lines · Phillip Wood

Hi Harald

Quoted 7 lines · Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

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

Quoted 8 lines
Quoted 3 lines

* --operations shows one entry per command, folding a rebase into the entry that finished or aborted it, and leaves out entries that change no branch.

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

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

Quoted 3 lines

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

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

Quoted 6 lines

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

Ditto

Quoted 9 lines
Quoted 2 lines

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

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

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

Quoted 11 lines

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

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

--
D. Ben Knoble

#28 lore raw
In reply to Phillip Wood
Quoted 4 lines · Phillip Wood

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

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

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

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

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

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

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

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

Thanks.

#29 lore raw
Quoted 37 lines · Junio C Hamano
Quoted 4 lines · Phillip Wood

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

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

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

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

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

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

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

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

Thanks Phillip, Ben and Junio for pointing this out!

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

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

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

Harald

#30 lore raw
Quoted 16 lines · Harald Nordgren
Quoted 8 lines · Junio C Hamano
Quoted 4 lines · Phillip Wood

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

Quoted 6 lines

[snip]

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

Quoted 6 lines

Thanks Phillip, Ben and Junio for pointing this out!

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

Well. I wrote in this thread:

Quoted 3 lines

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

But now I wonder if that was just an illusion:

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

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

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

[1] https://lore.kernel.org/git/[email protected]/

Quoted 5 lines

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

[snip]

#31 lore raw
In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Phillip Wood

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

Quoted 7 lines

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

'git rebase' doing its work on a detached HEAD was likely the first, and we may want to convert more multi-step commands to do so, including the multiple 'git cherry-pick' you mention.

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

Quoted 4 lines

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

Oh, I hadn't realized that - thanks for the tip

Phillip

#32 lore raw
Quoted 7 lines

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

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

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

Harald

#33 lore raw
Quoted 8 lines · Harald Nordgren
Quoted 7 lines

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

Quoted 5 lines

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

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

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

#34 lore raw
Quoted 12 lines · Junio C Hamano
Quoted 7 lines · Harald Nordgren
Quoted 7 lines

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

Quoted 6 lines
Quoted 4 lines

I think that the fact that all of us have our tricks to work around things is indicative of a real problem.
We should attack the root cause and simplify git so that even us would prefer vanilla git.

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

But you also workaround limitations in am by detaching ;)
which I think was Harald’s bigger point: let’s eliminate the need for such.

#35 lore raw
Quoted 3 lines · Ben Knoble
Quoted 1 line

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

But you also workaround limitations in am by detaching ;)

Not at all. That is not a limitation. It is a flexibility. I do apply patches directly on my branches when that is the behaviou I want.

#36 lore raw
In reply to Ben Knoble
Quoted 18 lines · Ben Knoble
Quoted 18 lines · Junio C Hamano
Quoted 11 lines · Harald Nordgren
Quoted 7 lines

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

I think that the fact that all of us have our tricks to work around things is indicative of a real problem.
We should attack the root cause and simplify git so that even us would prefer vanilla git.

Quoted 4 lines
Quoted 1 line

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

But you also workaround limitations in am by detaching ;)
which I think was Harald’s bigger point: let’s eliminate the need for such.

Yes, on the bigger picture.

The specific example I had in mind was using 'git reflog @{0}', which I never knew about and will use from now on. I'm looking into maybe making this the default as well.

Harald

#37 lore raw
In reply to Phillip Wood
Quoted 9 lines · Phillip Wood
Quoted 5 lines · Junio C Hamano
Quoted 4 lines · Phillip Wood

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

Quoted 8 lines
Quoted 6 lines

I like the direction to make the default 'git reflog' output on a single branch easier to understand without needing any extra filtering in this series.
'git rebase' doing its work on a detached HEAD was likely the first, and we may want to convert more multi-step commands to do so, including the multiple 'git cherry-pick' you mention.

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

I sometimes forget that Git detaches HEAD while rebasing! The prompt script shows the branch info, and git status (I think?) doesn’t indicate it.

All this to say, I think rebase’s presentation of a detached HEAD that is somewhat « hidden » might make such a change more invisible to cherry-pickers?

#38 lore raw
In reply to Phillip Wood
Quoted 9 lines
Quoted 2 lines

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

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

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

Quoted 11 lines

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

Nice, looking forward to it.

Harald

#39 lore raw

v3 cover reflog: add --operations and --historical-branch

In reply to Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

The reflog of HEAD records the work done on every branch, so "git reflog" mixes them and does not say which entry was made on which branch. This adds a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

$ git log -g --format='%h %gd: (%gb) %gs'
9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
1a2b3c4 HEAD@{3}: (main) commit: fix typo

It follows branch switches, branch renames and rebases, including "git rebase ", rebases of a detached HEAD and rebases of a branch that was already up to date. When HEAD was detached, the entry shows "detached at ".

Changes in v3:

* Dropped --operations.
* Replaced --historical-branch with a %gb placeholder for --format, e.g. git log -g --format='%h %gd: (%gb) %gs'.
* %gb names the right branch after git rebase <upstream> <branch>, a rebase of a detached HEAD and an up-to-date rebase.
* Named the returning to and checkout reflog strings in reflog.h too.

Changes in v2:

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

Harald Nordgren (2):
reflog: name the strings that mark switches, renames and rebase steps
pretty: add %gb to name the branch of each reflog entry

 Documentation/pretty-formats.adoc |   3 +
 builtin/branch.c                  |   3 +-
 builtin/checkout.c                |   3 +-
 builtin/rebase.c                  |  19 ++--
 object-name.c                     |   3 +-
 pretty.c                          |   4 +
 reflog-walk.c                     | 156 ++++++++++++++++++++++++++++--
 reflog-walk.h                     |   2 +
 reflog.h                          |   8 ++
 sequencer.c                       |  13 +--
 t/t1414-reflog-walk.sh            | 122 +++++++++++++++++++++++
 wt-status.c                       |   3 +-
 12 files changed, 314 insertions(+), 25 deletions(-)

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

Range-diff vs v2:

1: 745929bd80 ! 1: 4d779e8da3 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

- 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.
+ The next commit adds a "%gb" placeholder that shows which branch HEAD
+ was on for each reflog entry. It finds the branch from the messages
+ that branch switches, branch renames and rebases leave in the reflog.
+ The commands that write these messages spell out the strings by hand,
+ and "@{-<n>}" and "git status" each keep their own copy of the switch
+ prefix to read it back. Reading them in one more place would add yet
+ another copy that has to stay in sync.

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by
- "git branch -m", and the "start", "finish" and "abort" steps written
- by "git rebase".
+ "git branch -m", and the "start", "finish" and "abort" steps, the
+ "returning to " before the branch a rebase goes back to and the
+ "checkout " before what it checks out, all written by "git rebase".

No change in behavior.

@@ builtin/rebase.c: static int move_to_original_branch(struct rebase_options *opts
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",
++ strbuf_addf(&head_reflog, "%s (%s): " REFLOG_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;
+@@ builtin/rebase.c: static int checkout_up_to_date(struct rebase_options *options)
+ struct reset_working_tree_options ropts = { 0 };
+ int ret = 0;
+
+- strbuf_addf(&buf, "%s: checkout %s",
++ strbuf_addf(&buf, "%s: " REFLOG_CHECKOUT "%s",
+ options->reflog_action, options->switch_to);
+ ropts.oid = &options->orig_head->object.oid;
+ ropts.branch = options->head_name;
@@ builtin/rebase.c: 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",
++ strbuf_addf(&head_msg, "%s (%s): " REFLOG_RETURNING_TO "%s",
+ options.reflog_action, REFLOG_STEP_ABORT,
options.head_name ? options.head_name
: oid_to_hex(&options.orig_head->object.oid));
@@ builtin/rebase.c: int cmd_rebase(int argc,

- 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);
++ strbuf_addf(&msg, "%s (%s): " REFLOG_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 |
@@ reflog.h
+#define REFLOG_STEP_START "start"
+#define REFLOG_STEP_FINISH "finish"
+#define REFLOG_STEP_ABORT "abort"
++#define REFLOG_RETURNING_TO "returning to "
++#define REFLOG_CHECKOUT "checkout "
+
#define REFLOG_EXPIRE_TOTAL (1 << 0)
#define REFLOG_EXPIRE_UNREACH (1 << 1)
@@ sequencer.c: static int checkout_onto(struct repository *r, struct replay_opts *
- .head_msg = reflog_message(opts, "start", "checkout %s",
- onto_name),
+ .head_msg = reflog_message(opts, REFLOG_STEP_START,
-+ "checkout %s", onto_name),
++ REFLOG_CHECKOUT "%s", onto_name),
.default_reflog_action = sequencer_reflog_action(opts)
};
if (reset_working_tree(r, &ropts)) {
@@ sequencer.c: 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);
++ REFLOG_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);

 2:  1c47ba225e < -:  ---------- reflog: add --operations to show one entry per command
 3:  aa5a98b80d ! 2:  dee548f9b1 reflog: add --historical-branch to name the branch of each entry
     @@ Metadata
      Author: Harald Nordgren <[email protected]>
      
       ## Commit message ##
     -    reflog: add --historical-branch to name the branch of each entry
     +    pretty: add %gb to name the branch of each reflog entry
      
     -    "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.
     +    The reflog of HEAD records the work done on every branch, so the
     +    output of "git reflog" mixes them and does not say which entry was
     +    made on which branch. Working that out by hand means looking back for
     +    the branch switch before each entry, and a rebase can move HEAD to
     +    another branch without writing one, as "git rebase <upstream>
     +    <branch>" does.
      
     -    Teach "git reflog" and "git log -g" a "--historical-branch" option
     -    that shows the branch each entry was made on before its message, in
     -    place of the refs that point at the commit now. The hashes and
     -    selectors stay lined up in their columns, and the name is colored like
     -    a branch decoration:
     +    Add a "%gb" placeholder that names the branch HEAD was on when each
     +    entry was recorded:
      
     -      $ git reflog --historical-branch
     -      4e5f6a7 HEAD@{0}: (topic) commit: add tests
     -      1a2b3c4 HEAD@{1}: (topic) checkout: moving from main to topic
     -      8c9d0e1 HEAD@{2}: (main) commit: fix typo
     +        $ git log -g --format='%h %gd: (%gb) %gs'
     +        9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
     +        9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
     +        1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
     +        1a2b3c4 HEAD@{3}: (main) commit: fix typo
     +        5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main
      
     -    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.
     +    The branch is read from the entries that branch switches, branch
     +    renames and rebases leave behind. The entries of a rebase are named
     +    after the branch being rebased. When HEAD was detached, the entry
     +    shows "detached at <commit>" with the commit HEAD was at when it
     +    moved on. When nothing older in the reflog says which branch a rebase
     +    started on, the rebase is taken to have started on the branch it
     +    rebased. For the reflog of any other ref, "%gb" is the name of that
     +    ref.
      
          Signed-off-by: Harald Nordgren <[email protected]>
      
     - ## Documentation/git-reflog.adoc ##
     -@@ Documentation/git-reflog.adoc: actions, and in addition the `HEAD` reflog records branch switching.
     - --pretty=oneline`; see linkgit:git-log[1] for more information.
     - With `--operations`, branch switches and other entries that change no
     - branch are left out, and a rebase is shown as a single entry instead
     --of one entry for each commit it replayed.
     -+of one entry for each commit it replayed. With `--historical-branch`,
     -+each entry shows the branch it was made on before its message.
     - 
     - The "list" subcommand lists all refs which have a corresponding reflog.
     - 
     + ## Documentation/pretty-formats.adoc ##
     +@@ Documentation/pretty-formats.adoc: endif::git-rev-list[]
     + +%gE+:: reflog identity email (respecting .mailmap, see
     + 	linkgit:git-shortlog[1] or linkgit:git-blame[1])
     + +%gs+:: reflog subject
     +++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
     ++	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
     ++	of any other ref, this is the shortened refname.
     + ++%(trailers++`[:<option>,...]`++)++::
     + display the trailers of the body as interpreted by
     + linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
      
     - ## Documentation/rev-list-options.adoc ##
     -@@ Documentation/rev-list-options.adoc: Under `--pretty=reference`, this information will not be shown at all.
     - 	rebase started. It is an error to use this option unless
     - 	`--walk-reflogs` is in use.
     - 
     -+ifndef::git-rev-list[]
     -+`--historical-branch`::
     -+	Show the branch `HEAD` was on when each reflog entry was
     -+	recorded, such as `(topic)`, before the reflog message, in
     -+	place of the refs that point at the commit now. When `HEAD` was
     -+	detached, the entry shows `(detached at <commit>)`. For the
     -+	reflog of any other ref, the name of that ref is shown. It is
     -+	an error to use this option unless `--walk-reflogs` is in use.
     -+endif::git-rev-list[]
     -+
     - `--merge`::
     - 	Show commits touching conflicted paths in the range `HEAD...<other>`,
     - 	where `<other>` is the first existing pseudoref in `MERGE_HEAD`,
     -
     - ## log-tree.c ##
     -@@ log-tree.c: void format_decorations(struct strbuf *sb,
     - 	}
     - }
     - 
     -+static void format_historical_branch(struct strbuf *sb,
     -+				     struct rev_info *opt)
     -+{
     -+	const char *branch = get_reflog_branch(opt->reflog_info);
     -+	enum git_colorbool use_color = opt->diffopt.use_color;
     -+	const char *color_commit, *color_reset;
     -+
     -+	if (!branch)
     -+		return;
     -+
     -+	color_commit = diff_get_color(use_color, DIFF_COMMIT);
     -+	color_reset = decorate_get_color(use_color, DECORATION_NONE);
     -+	strbuf_addf(sb, "%s(%s%s%s%s%s)%s ",
     -+		    color_commit, color_reset,
     -+		    decorate_get_color(use_color, DECORATION_REF_LOCAL),
     -+		    branch, color_reset, color_commit, color_reset);
     -+}
     -+
     - void show_decorations(struct rev_info *opt, struct commit *commit)
     - {
     - 	struct strbuf sb = STRBUF_INIT;
     -@@ 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->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;
     - 		}
     + ## pretty.c ##
     +@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
     + 			if (c->pretty_ctx->reflog_info)
     + 				get_reflog_message(sb, c->pretty_ctx->reflog_info);
     + 			return 2;
     ++		case 'b':	/* reflog branch */
     ++			if (c->pretty_ctx->reflog_info)
     ++				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
     ++			return 2;
     + 		case 'n':
     + 		case 'N':
     + 		case 'e':
      
       ## reflog-walk.c ##
      @@
     + #include "git-compat-util.h"
     + #include "commit.h"
       #include "refs.h"
     - #include "reflog.h"
     ++#include "reflog.h"
       #include "diff.h"
      +#include "hex.h"
      +#include "object-name.h"
     @@ reflog-walk.c: struct complete_reflogs {
       		int tz;
       		char *message;
      +		char *branch;
     - 		bool user_operation;
       	} *items;
       	int nr, alloc;
     ++	bool branches_marked;
     + };
     + 
     + static int read_one_reflog(const char *refname UNUSED,
      @@ reflog-walk.c: 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;
     + }
      @@ reflog-walk.c: static void free_complete_reflog(struct complete_reflogs *array)
       	for (i = 0; i < array->nr; i++) {
       		free(array->items[i].email);
     @@ reflog-walk.c: static void free_complete_reflog(struct complete_reflogs *array)
       	}
       	free(array->items);
       	free(array->ref);
     -@@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
     - 	}
     +@@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UNUSED)
     + 	free_complete_reflog(array);
       }
       
     -+static char *branch_label(const char *name, size_t len)
     ++static char *branch_label(const char *name, size_t len,
     ++			  const struct object_id *oid)
      +{
     -+	struct object_id oid;
     -+	const char *short_name;
     ++	if (name) {
     ++		struct object_id hex_oid;
     ++		char *branch;
      +
     -+	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);
     ++		if (skip_prefix(name, "refs/heads/", &name))
     ++			len -= strlen("refs/heads/");
     ++		branch = xmemdupz(name, len);
     ++		if (strcmp(branch, "HEAD") && get_oid_hex(branch, &hex_oid) &&
     ++		    !check_refname_format(branch, REFNAME_ALLOW_ONELEVEL))
     ++			return branch;
     ++		free(branch);
     ++	}
     ++	return xstrfmt("detached at %s",
     ++		       repo_find_unique_abbrev(the_repository, oid,
     ++					       DEFAULT_ABBREV));
     ++}
     ++
     ++static const char *skip_rebase_step(const char *message, const char *step)
     ++{
     ++	const char *p = strstr(message, " (");
     ++	const char *colon = strstr(message, ": ");
     ++
     ++	if (!p || (colon && colon < p) ||
     ++	    !skip_prefix(p + 2, step, &p) || !skip_prefix(p, "): ", &p))
     ++		return NULL;
     ++	return p;
      +}
      +
      +struct branch_move {
     @@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
      +{
      +	const char *to;
      +
     -+	if (!skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) &&
     -+	    !skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from))
     ++	move->from = move->to = NULL;
     ++	move->from_len = move->to_len = 0;
     ++	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) ||
     ++	    skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from)) {
     ++		to = strstr(move->from, " to ");
     ++		if (!to)
     ++			return false;
     ++		move->from_len = to - move->from;
     ++		move->to = to + strlen(" to ");
     ++	} else if (((to = skip_rebase_step(message, REFLOG_STEP_FINISH)) ||
     ++		    (to = skip_rebase_step(message, REFLOG_STEP_ABORT))) &&
     ++		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
     ++		move->from = move->to = to;
     ++		move->from_len = strcspn(to, "\n");
     ++	} else if (skip_prefix(message, "rebase: " REFLOG_CHECKOUT, &to)) {
     ++		move->to = to;
     ++	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
      +		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");
     ++	}
     ++	if (move->to)
     ++		move->to_len = strcspn(move->to, "\n");
      +	return true;
      +}
      +
      +static void mark_branches(struct complete_reflogs *array)
      +{
     ++	struct ref_store *refs = get_main_ref_store(the_repository);
     ++	struct object_id oid;
     ++	const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0, &oid, NULL);
      +	char *branch = NULL;
     ++	int unknown = array->nr;
      +
     -+	if (strcmp(array->ref, "HEAD"))
     -+		return;
     ++	if (head)
     ++		branch = branch_label(head, strlen(head), &oid);
      +
      +	for (int i = array->nr - 1; i >= 0; i--) {
      +		struct reflog_info *item = &array->items[i];
     @@ reflog-walk.c: static void mark_operations(struct complete_reflogs *array)
      +			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) {
     ++			branch = branch_label(move.to, move.to_len,
     ++					      &array->items[unknown - 1].noid);
     ++			for (int j = i + 1; j < unknown; j++)
     ++				array->items[j].branch = xstrdup(branch);
     ++		}
     ++		item->branch = branch;
     ++		if (move.from) {
     ++			branch = branch_label(move.from, move.from_len,
     ++					      &item->ooid);
     ++		} else {
     ++			branch = NULL;
     ++			unknown = i;
     ++		}
      +	}
      +
     -+	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);
     -+	}
     ++	if (!branch && unknown < array->nr)
     ++		for (int i = 0; i < unknown; i++)
     ++			array->items[i].branch =
     ++				xstrdup(array->items[unknown].branch);
      +	free(branch);
      +}
      +
       static struct complete_reflogs *read_complete_reflog(const char *ref)
       {
       	struct complete_reflogs *reflogs =
     -@@ reflog-walk.c: 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);
     - 	}
     - }
     - 
      @@ reflog-walk.c: int add_reflog_for_walk(struct reflog_walk_info *info,
       	return 0;
       }
     @@ reflog-walk.c: void get_reflog_message(struct strbuf *sb,
       	strbuf_add(sb, info->message, len);
       }
       
     -+const char *get_reflog_branch(struct reflog_walk_info *reflog_info)
     ++void get_reflog_branch(struct strbuf *sb,
     ++		       struct reflog_walk_info *reflog_info)
      +{
      +	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
     ++	struct complete_reflogs *reflogs;
     ++	const char *branch;
      +
      +	if (!commit_reflog)
     -+		return NULL;
     ++		return;
      +
     -+	if (strcmp(commit_reflog->reflogs->ref, "HEAD"))
     -+		return short_ref(commit_reflog->reflogs);
     -+	return commit_reflog->reflogs->items[commit_reflog->recno+1].branch;
     ++	reflogs = commit_reflog->reflogs;
     ++	if (strcmp(reflogs->ref, "HEAD")) {
     ++		strbuf_addstr(sb, short_ref(reflogs));
     ++		return;
     ++	}
     ++
     ++	if (!reflogs->branches_marked) {
     ++		mark_branches(reflogs);
     ++		reflogs->branches_marked = true;
     ++	}
     ++	branch = reflogs->items[commit_reflog->recno+1].branch;
     ++	if (branch)
     ++		strbuf_addstr(sb, branch);
      +}
      +
       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;
     - 
     - void init_reflog_walk(struct reflog_walk_info **info);
     - void reflog_walk_info_release(struct reflog_walk_info *info);
     --void reflog_walk_mark(struct reflog_walk_info *info, bool operations);
     -+void reflog_walk_mark(struct reflog_walk_info *info, bool operations,
     -+		      bool historical_branch);
     - int add_reflog_for_walk(struct reflog_walk_info *info,
     - 			struct commit *commit, const char *name);
     - void show_reflog_message(struct reflog_walk_info *info, int,
     --			 struct date_mode, int force_date);
     -+			 struct date_mode, int force_date,
     -+			 const char *branch);
     +@@ reflog-walk.h: 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);
     ++void get_reflog_branch(struct strbuf *sb,
     ++		       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,
      
     - ## revision.c ##
     -@@ revision.c: 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;
     -@@ revision.c: 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 ||
     -
     - ## revision.h ##
     -@@ revision.h: 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,
     -
       ## t/t1414-reflog-walk.sh ##
     -@@ t/t1414-reflog-walk.sh: test_expect_success '--operations shows a rebase as one entry' '
     - 	)
     +@@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
     + 	test_cmp expect actual
       '
       
     -+test_expect_success '--historical-branch names the branch of each entry' '
     ++test_expect_success '%gb names the branch HEAD was on' '
     ++	git init branches &&
      +	(
     -+		cd operations &&
     ++		cd branches &&
     ++		test_commit base &&
     ++		git switch -c topic &&
     ++		test_commit A &&
     ++		test_commit B &&
     ++		git switch main &&
     ++		test_commit upstream &&
     ++		git switch topic &&
     ++		git rebase main &&
      +
      +		cat >expect <<-\EOF &&
     -+		HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
     -+		HEAD@{1}: (topic) rebase (pick): C
     -+		HEAD@{2}: (topic) rebase (pick): B
     -+		HEAD@{3}: (topic) rebase (pick): A
     -+		HEAD@{4}: (topic) rebase (start): checkout main
     -+		HEAD@{5}: (topic) checkout: moving from main to topic
     -+		HEAD@{6}: (main) commit: upstream
     -+		HEAD@{7}: (main) checkout: moving from topic to main
     -+		HEAD@{8}: (topic) commit: C
     -+		HEAD@{9}: (topic) commit: B
     -+		HEAD@{10}: (topic) commit: A
     -+		HEAD@{11}: (topic) checkout: moving from main to topic
     -+		HEAD@{12}: (main) commit (initial): base
     ++		(topic) rebase (finish): returning to refs/heads/topic
     ++		(topic) rebase (pick): B
     ++		(topic) rebase (pick): A
     ++		(topic) rebase (start): checkout main
     ++		(topic) checkout: moving from main to topic
     ++		(main) commit: upstream
     ++		(main) checkout: moving from topic to main
     ++		(topic) commit: B
     ++		(topic) commit: A
     ++		(topic) checkout: moving from main to topic
     ++		(main) commit (initial): base
      +		EOF
     -+		git reflog --historical-branch >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		git log -g --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch takes the place of decorations' '
     ++test_expect_success '%gb names the ref of other reflogs' '
      +	(
     -+		cd operations &&
     -+		hash=$(git rev-parse --short HEAD) &&
     -+		echo "<YELLOW>$hash<RESET> HEAD@{0}: <YELLOW>(<RESET><BOLD;GREEN>topic<RESET><YELLOW>)<RESET> rebase (finish): returning to refs/heads/topic" >expect &&
     -+		git reflog --historical-branch --decorate --color -1 >out &&
     -+		test_decode_color <out >actual &&
     ++		cd branches &&
     ++		echo main >expect &&
     ++		git log -g -1 --format=%gb main >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch names the ref of other reflogs' '
     ++test_expect_success '%gb follows "git rebase <upstream> <branch>"' '
      +	(
     -+		cd operations &&
     -+		echo "main@{0}: (main) commit: upstream" >expect &&
     -+		git reflog -1 main --historical-branch >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		cd branches &&
     ++		git switch main &&
     ++		test_commit main-change &&
     ++		git rebase main topic &&
     ++		git switch main &&
     ++		git rebase main topic &&
     ++
     ++		cat >expect <<-\EOF &&
     ++		(topic) rebase: checkout topic
     ++		(main) checkout: moving from topic to main
     ++		(topic) rebase (finish): returning to refs/heads/topic
     ++		(topic) rebase (pick): B
     ++		(topic) rebase (pick): A
     ++		(topic) rebase (start): checkout main
     ++		(main) commit: main-change
     ++		(main) checkout: moving from topic to main
     ++		EOF
     ++		git log -g -8 --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     - test_expect_success '--operations shows a stopped rebase as one entry once it finishes' '
     - 	set_fake_editor &&
     - 	(
     -@@ t/t1414-reflog-walk.sh: 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' '
     ++test_expect_success '%gb names a detached HEAD by the commit it was left at' '
      +	(
     -+		cd downstream &&
     -+		echo "(topic)" >expect &&
     -+		git reflog --historical-branch >out &&
     -+		cut -d " " -f 3 out | sort -u >actual &&
     ++		cd branches &&
     ++		git switch --detach topic~1 &&
     ++		test_commit detached &&
     ++		git rebase topic &&
     ++		rebased=$(git rev-parse HEAD) &&
     ++		git switch topic &&
     ++
     ++		left_before=$(git rev-parse --short detached) &&
     ++		left_after=$(git rev-parse --short $rebased) &&
     ++		cat >expect <<-EOF &&
     ++		(topic) checkout: moving from $rebased to topic
     ++		(detached at $left_after) rebase (pick): detached
     ++		(detached at $left_after) rebase (start): checkout topic
     ++		(detached at $left_before) commit: detached
     ++		(detached at $left_before) checkout: moving from topic to topic~1
     ++		EOF
     ++		git log -g -5 --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch follows a renamed branch' '
     ++test_expect_success '%gb names the branch of a "git pull --rebase" in a fresh clone' '
     ++	git clone branches downstream &&
      +	(
     -+		cd operations &&
     -+		test_commit D &&
     -+		git branch -m topic renamed &&
     -+		test_commit E &&
     ++		cd branches &&
     ++		test_commit upstream-change
     ++	) &&
     ++	(
     ++		cd downstream &&
     ++		test_commit local-change &&
     ++		git pull --rebase &&
      +
     -+		cat >expect <<-\EOF &&
     -+		HEAD@{0}: (renamed) commit: E
     -+		HEAD@{1}: (renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
     -+		HEAD@{3}: (topic) commit: D
     -+		EOF
     -+		git reflog --historical-branch -3 >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		echo "(topic)" >expect &&
     ++		git log -g --format="(%gb)" >out &&
     ++		sort -u out >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '--historical-branch shows where HEAD was detached' '
     ++test_expect_success '%gb follows a renamed branch' '
      +	(
     -+		cd operations &&
     -+		git checkout --detach &&
     -+		test_commit F &&
     -+		git checkout renamed &&
     ++		cd branches &&
     ++		test_commit before-rename &&
     ++		git branch -m topic renamed &&
     ++		test_commit after-rename &&
      +
     -+		cat >expect <<-EOF &&
     -+		HEAD@{0}: (renamed) checkout: moving from $(git rev-parse F) to renamed
     -+		HEAD@{1}: (detached at $(git rev-parse --short F)) commit: F
     ++		cat >expect <<-\EOF &&
     ++		(renamed) commit: after-rename
     ++		(renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
     ++		(topic) commit: before-rename
      +		EOF
     -+		git reflog --historical-branch -2 >out &&
     -+		cut -d " " -f 2- out >actual &&
     ++		git log -g -3 --format="(%gb) %gs" >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
     + test_expect_success 'rev-list -g complains when there are no reflogs' '
     + 	test_must_fail git rev-list -g
       '
     - 
Signature · 1 line
gitgitgadget
#40 lore raw

PATCH v3 1/2 reflog: name the strings that mark switches, renames and rebase steps

From: Harald Nordgren <[email protected]>

The next commit adds a "%gb" placeholder that shows which branch HEAD was on for each reflog entry. It finds the branch from the messages that branch switches, branch renames and rebases leave in the reflog. The commands that write these messages spell out the strings by hand, and "@{-<n>}" and "git status" each keep their own copy of the switch prefix to read it back. Reading them in one more place would add yet another copy that has to stay in sync.

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps, the "returning to " before the branch a rebase goes back to and the "checkout " before what it checks out, all written by "git rebase".

No change in behavior.

---
 builtin/branch.c   |  3 ++-
 builtin/checkout.c |  3 ++-
 builtin/rebase.c   | 19 ++++++++++---------
 object-name.c      |  3 ++-
 reflog.h           |  8 ++++++++
 sequencer.c        | 13 +++++++------
 wt-status.c        |  3 ++-
 7 files changed, 33 insertions(+), 19 deletions(-)
builtin/branch.c +2 -1
index a613148fc7..30062ceb7c 100644
@@ -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) &&
builtin/checkout.c +2 -1
index 91e0332ec2..1e06586e67 100644
@@ -26,6 +26,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"
@@ -1037,7 +1038,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);
builtin/rebase.c +10 -9
index 535db60181..47a7b2fd3c 100644
@@ -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): " REFLOG_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;
@@ -867,7 +868,7 @@ static int checkout_up_to_date(struct rebase_options *options)
 	struct reset_working_tree_options ropts = { 0 };
 	int ret = 0;
 
-	strbuf_addf(&buf, "%s: checkout %s",
+	strbuf_addf(&buf, "%s: " REFLOG_CHECKOUT "%s",
 		    options->reflog_action, options->switch_to);
 	ropts.oid = &options->orig_head->object.oid;
 	ropts.branch = options->head_name;
@@ -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): " REFLOG_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): " REFLOG_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 |
object-name.c +2 -1
index 6546685760..5b6ce8e0e0 100644
@@ -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)
reflog.h +8 -0
index 3bdd1ca8a5..f5586242bf 100644
@@ -2,6 +2,14 @@
 #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_RETURNING_TO "returning to "
+#define REFLOG_CHECKOUT "checkout "
+
 #define REFLOG_EXPIRE_TOTAL   (1 << 0)
 #define REFLOG_EXPIRE_UNREACH (1 << 1)
 
sequencer.c +7 -6
index 2ac78e15b8..b2c6d8072a 100644
@@ -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,
+					   REFLOG_CHECKOUT "%s", onto_name),
 		.default_reflog_action = sequencer_reflog_action(opts)
 	};
 	if (reset_working_tree(r, &ropts)) {
@@ -5311,16 +5312,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,
+				REFLOG_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);
wt-status.c +2 -1
index 238bb48643..3516432338 100644
@@ -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)
Signature · 1 line
gitgitgadget
#41 lore raw

PATCH v3 2/2 pretty: add %gb to name the branch of each reflog entry

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

The reflog of HEAD records the work done on every branch, so the output of "git reflog" mixes them and does not say which entry was made on which branch. Working that out by hand means looking back for the branch switch before each entry, and a rebase can move HEAD to another branch without writing one, as "git rebase <upstream> <branch>" does.

Add a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

    $ git log -g --format='%h %gd: (%gb) %gs'
    9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
    9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
    1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
    1a2b3c4 HEAD@{3}: (main) commit: fix typo
    5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main

The branch is read from the entries that branch switches, branch renames and rebases leave behind. The entries of a rebase are named after the branch being rebased. When HEAD was detached, the entry shows "detached at <commit>" with the commit HEAD was at when it moved on. When nothing older in the reflog says which branch a rebase started on, the rebase is taken to have started on the branch it rebased. For the reflog of any other ref, "%gb" is the name of that ref.

---
 Documentation/pretty-formats.adoc |   3 +
 pretty.c                          |   4 +
 reflog-walk.c                     | 156 ++++++++++++++++++++++++++++--
 reflog-walk.h                     |   2 +
 t/t1414-reflog-walk.sh            | 122 +++++++++++++++++++++++
 5 files changed, 281 insertions(+), 6 deletions(-)
Documentation/pretty-formats.adoc +3 -0
index 2ae0eb11a9..a8d00397c4 100644
@@ -334,6 +334,9 @@ endif::git-rev-list[]
 +%gE+:: reflog identity email (respecting .mailmap, see
 	linkgit:git-shortlog[1] or linkgit:git-blame[1])
 +%gs+:: reflog subject
++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
+	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
+	of any other ref, this is the shortened refname.
 ++%(trailers++`[:<option>,...]`++)++::
 display the trailers of the body as interpreted by
 linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
pretty.c +4 -0
index d8a9f370f6..1b3f7e2dc3 100644
@@ -1637,6 +1637,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 			if (c->pretty_ctx->reflog_info)
 				get_reflog_message(sb, c->pretty_ctx->reflog_info);
 			return 2;
+		case 'b':	/* reflog branch */
+			if (c->pretty_ctx->reflog_info)
+				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
+			return 2;
 		case 'n':
 		case 'N':
 		case 'e':
reflog-walk.c +150 -6

207 lines not on this page · raw message

reflog-walk.h +2 -0
index 989583dc55..fc1f344ce6 100644
@@ -13,6 +13,8 @@ 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);
+void get_reflog_branch(struct strbuf *sb,
+		       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,
t/t1414-reflog-walk.sh +122 -0

128 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#42 lore raw

Re: [PATCH v3 2/2] pretty: add %gb to name the branch of each reflog entry

Hi Harald

Quoted 11 lines · Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

Add a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

     $ git log -g --format='%h %gd: (%gb) %gs'
     9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
     9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
     1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
     1a2b3c4 HEAD@{3}: (main) commit: fix typo
     5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main

Looks good

Quoted 2 lines

The branch is read from the entries that branch switches, branch renames and rebases leave behind.

"the entries created by ..."?

Quoted 2 lines

The entries of a rebase are named
after the branch being rebased.

This sounds very LLM, "labelled with the branch being rebased"?

Quoted 3 lines

When HEAD was detached, the entry
shows "detached at <commit>" with the commit HEAD was at when it moved on.

I wondered if that was a bit verbose, but having tried it, it is no longer than some of my branch names.

Quoted 4 lines

When nothing older in the reflog says which branch a rebase started on, the rebase is taken to have started on the branch it rebased. For the reflog of any other ref, "%gb" is the name of that ref.

I'm seeing some strange behavior when GIT_REFLOG_ACTION is set, below is the output of "git reflog --format='%h %gd %gs (%gb)'" for my local HEAD. I've added comments above the lines where the branch name is incorrect. The "sync switch" entries come from running "git rebase" with GIT_REFLOG_ACTION set.

be8e0d55ec HEAD@{0} reset: moving to HEAD (sequencer-delete-REBASE_HEAD)
be8e0d55ec HEAD@{1} sync switch: checkout sequencer-delete-REBASE_HEAD (sequencer-delete-REBASE_HEAD)

# should be fix-nth-prior-checkout
64822b4a3b HEAD@{2} sync switch: checkout fix-nth-prior-checkout (sequencer-delete-REBASE_HEAD)
# should be detached at 64882b4a3b
64822b4a3b HEAD@{3} rebase: fast-forward (sequencer-delete-REBASE_HEAD)
# should be detached at 64822b4a3b
bf19b60d89 HEAD@{4} rebase (start): checkout @^ (sequencer-delete-REBASE_HEAD)
64822b4a3b HEAD@{5} rebase: fast-forward (detached at 64822b4a3b)
bf19b60d89 HEAD@{6} rebase (start): checkout fix-nth-prior-checkout^ (detached at 64822b4a3b)
be8e0d55ec HEAD@{7} rebase: checkout sequencer-delete-REBASE_HEAD (sequencer-delete-REBASE_HEAD)
be8e0d55ec HEAD@{8} rebase (start): checkout sequencer-delete-REBASE_HEAD (detached at be8e0d55ec)
64822b4a3b HEAD@{9} checkout: moving from 64822b4a3ba71e5a823002cd5ebe0fa5fb4b3a0c to fix-nth-prior-checkout (fix-nth-prior-checkout)

Thanks

Phillip

Quoted 403 lines
---
  Documentation/pretty-formats.adoc |   3 +
  pretty.c                          |   4 +
  reflog-walk.c                     | 156 ++++++++++++++++++++++++++++--
  reflog-walk.h                     |   2 +
  t/t1414-reflog-walk.sh            | 122 +++++++++++++++++++++++
  5 files changed, 281 insertions(+), 6 deletions(-)
Documentation/pretty-formats.adoc +3 -0
index 2ae0eb11a9..a8d00397c4 100644
@@ -334,6 +334,9 @@ endif::git-rev-list[]
  +%gE+:: reflog identity email (respecting .mailmap, see
  	linkgit:git-shortlog[1] or linkgit:git-blame[1])
  +%gs+:: reflog subject
++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
+	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
+	of any other ref, this is the shortened refname.
  ++%(trailers++`[:<option>,...]`++)++::
  display the trailers of the body as interpreted by
  linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
pretty.c +4 -0
index d8a9f370f6..1b3f7e2dc3 100644
@@ -1637,6 +1637,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
  			if (c->pretty_ctx->reflog_info)
  				get_reflog_message(sb, c->pretty_ctx->reflog_info);
  			return 2;
+		case 'b':	/* reflog branch */
+			if (c->pretty_ctx->reflog_info)
+				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
+			return 2;
  		case 'n':
  		case 'N':
  		case 'e':
reflog-walk.c +150 -6
index 4dbeaa93a7..45b5dc09b5 100644
@@ -3,7 +3,10 @@
  #include "git-compat-util.h"
  #include "commit.h"
  #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"
@@ -18,8 +21,10 @@ struct complete_reflogs {
  		timestamp_t timestamp;
  		int tz;
  		char *message;
+		char *branch;
  	} *items;
  	int nr, alloc;
+	bool branches_marked;
  };
 
  static int read_one_reflog(const char *refname UNUSED,
@@ -38,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;
  	array->nr++;
  	return 0;
  }
@@ -52,6 +58,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);
@@ -65,6 +72,113 @@ static void complete_reflogs_clear(void *util, const char *str UNUSED)
  	free_complete_reflog(array);
  }
 
+static char *branch_label(const char *name, size_t len,
+			  const struct object_id *oid)
+{
+	if (name) {
+		struct object_id hex_oid;
+		char *branch;
+
+		if (skip_prefix(name, "refs/heads/", &name))
+			len -= strlen("refs/heads/");
+		branch = xmemdupz(name, len);
+		if (strcmp(branch, "HEAD") && get_oid_hex(branch, &hex_oid) &&
+		    !check_refname_format(branch, REFNAME_ALLOW_ONELEVEL))
+			return branch;
+		free(branch);
+	}
+	return xstrfmt("detached at %s",
+		       repo_find_unique_abbrev(the_repository, oid,
+					       DEFAULT_ABBREV));
+}
+
+static const char *skip_rebase_step(const char *message, const char *step)
+{
+	const char *p = strstr(message, " (");
+	const char *colon = strstr(message, ": ");
+
+	if (!p || (colon && colon < p) ||
+	    !skip_prefix(p + 2, step, &p) || !skip_prefix(p, "): ", &p))
+		return NULL;
+	return p;
+}
+
+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;
+
+	move->from = move->to = NULL;
+	move->from_len = move->to_len = 0;
+	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) ||
+	    skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from)) {
+		to = strstr(move->from, " to ");
+		if (!to)
+			return false;
+		move->from_len = to - move->from;
+		move->to = to + strlen(" to ");
+	} else if (((to = skip_rebase_step(message, REFLOG_STEP_FINISH)) ||
+		    (to = skip_rebase_step(message, REFLOG_STEP_ABORT))) &&
+		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
+		move->from = move->to = to;
+		move->from_len = strcspn(to, "\n");
+	} else if (skip_prefix(message, "rebase: " REFLOG_CHECKOUT, &to)) {
+		move->to = to;
+	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
+		return false;
+	}
+	if (move->to)
+		move->to_len = strcspn(move->to, "\n");
+	return true;
+}
+
+static void mark_branches(struct complete_reflogs *array)
+{
+	struct ref_store *refs = get_main_ref_store(the_repository);
+	struct object_id oid;
+	const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0, &oid, NULL);
+	char *branch = NULL;
+	int unknown = array->nr;
+
+	if (head)
+		branch = branch_label(head, strlen(head), &oid);
+
+	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) {
+			branch = branch_label(move.to, move.to_len,
+					      &array->items[unknown - 1].noid);
+			for (int j = i + 1; j < unknown; j++)
+				array->items[j].branch = xstrdup(branch);
+		}
+		item->branch = branch;
+		if (move.from) {
+			branch = branch_label(move.from, move.from_len,
+					      &item->ooid);
+		} else {
+			branch = NULL;
+			unknown = i;
+		}
+	}
+
+	if (!branch && unknown < array->nr)
+		for (int i = 0; i < unknown; i++)
+			array->items[i].branch =
+				xstrdup(array->items[unknown].branch);
+	free(branch);
+}
+
  static struct complete_reflogs *read_complete_reflog(const char *ref)
  {
  	struct complete_reflogs *reflogs =
@@ -234,6 +348,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,
@@ -247,12 +371,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;
  	}
@@ -287,6 +406,31 @@ void get_reflog_message(struct strbuf *sb,
  	strbuf_add(sb, info->message, len);
  }
 
+void get_reflog_branch(struct strbuf *sb,
+		       struct reflog_walk_info *reflog_info)
+{
+	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
+	struct complete_reflogs *reflogs;
+	const char *branch;
+
+	if (!commit_reflog)
+		return;
+
+	reflogs = commit_reflog->reflogs;
+	if (strcmp(reflogs->ref, "HEAD")) {
+		strbuf_addstr(sb, short_ref(reflogs));
+		return;
+	}
+
+	if (!reflogs->branches_marked) {
+		mark_branches(reflogs);
+		reflogs->branches_marked = true;
+	}
+	branch = reflogs->items[commit_reflog->recno+1].branch;
+	if (branch)
+		strbuf_addstr(sb, branch);
+}
+
  const char *get_reflog_ident(struct reflog_walk_info *reflog_info)
  {
  	struct commit_reflog *commit_reflog = reflog_info->last_commit_reflog;
reflog-walk.h +2 -0
index 989583dc55..fc1f344ce6 100644
@@ -13,6 +13,8 @@ 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);
+void get_reflog_branch(struct strbuf *sb,
+		       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,
t/t1414-reflog-walk.sh +122 -0
index be6c3f472c..0e821922ca 100755
@@ -131,6 +131,128 @@ test_expect_success 'walk prefers reflog to ref tip' '
  	test_cmp expect actual
  '
 
+test_expect_success '%gb names the branch HEAD was on' '
+	git init branches &&
+	(
+		cd branches &&
+		test_commit base &&
+		git switch -c topic &&
+		test_commit A &&
+		test_commit B &&
+		git switch main &&
+		test_commit upstream &&
+		git switch topic &&
+		git rebase main &&
+
+		cat >expect <<-\EOF &&
+		(topic) rebase (finish): returning to refs/heads/topic
+		(topic) rebase (pick): B
+		(topic) rebase (pick): A
+		(topic) rebase (start): checkout main
+		(topic) checkout: moving from main to topic
+		(main) commit: upstream
+		(main) checkout: moving from topic to main
+		(topic) commit: B
+		(topic) commit: A
+		(topic) checkout: moving from main to topic
+		(main) commit (initial): base
+		EOF
+		git log -g --format="(%gb) %gs" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '%gb names the ref of other reflogs' '
+	(
+		cd branches &&
+		echo main >expect &&
+		git log -g -1 --format=%gb main >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '%gb follows "git rebase <upstream> <branch>"' '
+	(
+		cd branches &&
+		git switch main &&
+		test_commit main-change &&
+		git rebase main topic &&
+		git switch main &&
+		git rebase main topic &&
+
+		cat >expect <<-\EOF &&
+		(topic) rebase: checkout topic
+		(main) checkout: moving from topic to main
+		(topic) rebase (finish): returning to refs/heads/topic
+		(topic) rebase (pick): B
+		(topic) rebase (pick): A
+		(topic) rebase (start): checkout main
+		(main) commit: main-change
+		(main) checkout: moving from topic to main
+		EOF
+		git log -g -8 --format="(%gb) %gs" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '%gb names a detached HEAD by the commit it was left at' '
+	(
+		cd branches &&
+		git switch --detach topic~1 &&
+		test_commit detached &&
+		git rebase topic &&
+		rebased=$(git rev-parse HEAD) &&
+		git switch topic &&
+
+		left_before=$(git rev-parse --short detached) &&
+		left_after=$(git rev-parse --short $rebased) &&
+		cat >expect <<-EOF &&
+		(topic) checkout: moving from $rebased to topic
+		(detached at $left_after) rebase (pick): detached
+		(detached at $left_after) rebase (start): checkout topic
+		(detached at $left_before) commit: detached
+		(detached at $left_before) checkout: moving from topic to topic~1
+		EOF
+		git log -g -5 --format="(%gb) %gs" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '%gb names the branch of a "git pull --rebase" in a fresh clone' '
+	git clone branches downstream &&
+	(
+		cd branches &&
+		test_commit upstream-change
+	) &&
+	(
+		cd downstream &&
+		test_commit local-change &&
+		git pull --rebase &&
+
+		echo "(topic)" >expect &&
+		git log -g --format="(%gb)" >out &&
+		sort -u out >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success '%gb follows a renamed branch' '
+	(
+		cd branches &&
+		test_commit before-rename &&
+		git branch -m topic renamed &&
+		test_commit after-rename &&
+
+		cat >expect <<-\EOF &&
+		(renamed) commit: after-rename
+		(renamed) Branch: renamed refs/heads/topic to refs/heads/renamed
+		(topic) commit: before-rename
+		EOF
+		git log -g -3 --format="(%gb) %gs" >actual &&
+		test_cmp expect actual
+	)
+'
+
  test_expect_success 'rev-list -g complains when there are no reflogs' '
  	test_must_fail git rev-list -g
  '
#43 lore raw

v4 cover reflog: add format %gb

In reply to Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

The reflog of HEAD records the work done on every branch, so "git reflog" mixes them and does not say which entry was made on which branch. This adds a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

$ git log -g --format='%h %gd: (%gb) %gs'
9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
1a2b3c4 HEAD@{3}: (main) commit: fix typo

It follows branch switches, branch renames and rebases, including "git rebase ", rebases of a detached HEAD and rebases of a branch that was already up to date. When HEAD was detached, the entry shows "detached at ".

Changes in v4:

* %gb now names the right branch when git rebase <upstream> <branch> runs with GIT_REFLOG_ACTION set.
* Reworded the %gb commit message.

Changes in v3:

* Dropped --operations.
* Replaced --historical-branch with a %gb placeholder for --format, e.g. git log -g --format='%h %gd: (%gb) %gs'.
* %gb names the right branch after git rebase <upstream> <branch>, a rebase of a detached HEAD and an up-to-date rebase.
* Named the returning to and checkout reflog strings in reflog.h too.

Changes in v2:

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

Harald Nordgren (2):
reflog: name the strings that mark switches, renames and rebase steps
pretty: add %gb to name the branch of each reflog entry

 Documentation/pretty-formats.adoc |   3 +
 builtin/branch.c                  |   3 +-
 builtin/checkout.c                |   3 +-
 builtin/rebase.c                  |  19 ++--
 object-name.c                     |   3 +-
 pretty.c                          |   4 +
 reflog-walk.c                     | 167 ++++++++++++++++++++++++++++--
 reflog-walk.h                     |   2 +
 reflog.h                          |   8 ++
 sequencer.c                       |  13 +--
 t/t1414-reflog-walk.sh            | 137 ++++++++++++++++++++++++
 wt-status.c                       |   3 +-
 12 files changed, 340 insertions(+), 25 deletions(-)

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

Range-diff vs v3:

 1:  4d779e8da3 = 1:  f960a24b8b reflog: name the strings that mark switches, renames and rebase steps
 2:  dee548f9b1 ! 2:  99a1047702 pretty: add %gb to name the branch of each reflog entry
     @@ Commit message
              1a2b3c4 HEAD@{3}: (main) commit: fix typo
              5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main
      
     -    The branch is read from the entries that branch switches, branch
     -    renames and rebases leave behind. The entries of a rebase are named
     -    after the branch being rebased. When HEAD was detached, the entry
     -    shows "detached at <commit>" with the commit HEAD was at when it
     -    moved on. When nothing older in the reflog says which branch a rebase
     -    started on, the rebase is taken to have started on the branch it
     -    rebased. For the reflog of any other ref, "%gb" is the name of that
     -    ref.
     +    The branch is read from the entries created by branch switches,
     +    branch renames and rebases. The entries of a rebase are labeled with
     +    the branch being rebased. When HEAD was detached, the entry shows
     +    "detached at <commit>" with the commit HEAD was at when it moved on.
     +    When nothing older in the reflog says which branch a rebase started
     +    on, the rebase is taken to have started on the branch it rebased. For
     +    the reflog of any other ref, "%gb" is the name of that ref.
      
          Signed-off-by: Harald Nordgren <[email protected]>
      
     @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
      +	return p;
      +}
      +
     ++static const char *skip_rebase_checkout(const char *message)
     ++{
     ++	const char *p = strstr(message, " (");
     ++	const char *colon = strstr(message, ": ");
     ++
     ++	if (!colon || (p && p < colon) ||
     ++	    !skip_prefix(colon + 2, REFLOG_CHECKOUT, &p))
     ++		return NULL;
     ++	return p;
     ++}
     ++
      +struct branch_move {
      +	const char *from, *to;
      +	size_t from_len, to_len;
     @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
      +		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
      +		move->from = move->to = to;
      +		move->from_len = strcspn(to, "\n");
     -+	} else if (skip_prefix(message, "rebase: " REFLOG_CHECKOUT, &to)) {
     ++	} else if ((to = skip_rebase_checkout(message))) {
      +		move->to = to;
      +	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
      +		return false;
     @@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
      +	)
      +'
      +
     ++test_expect_success '%gb follows "git rebase <upstream> <branch>" with GIT_REFLOG_ACTION' '
     ++	(
     ++		cd branches &&
     ++		git switch main &&
     ++		GIT_REFLOG_ACTION="sync switch" git rebase main topic &&
     ++
     ++		cat >expect <<-\EOF &&
     ++		(topic) sync switch: checkout topic
     ++		(main) checkout: moving from topic to main
     ++		EOF
     ++		git log -g -2 --format="(%gb) %gs" >actual &&
     ++		test_cmp expect actual
     ++	)
     ++'
     ++
      +test_expect_success '%gb names a detached HEAD by the commit it was left at' '
      +	(
      +		cd branches &&
Signature · 1 line
gitgitgadget
#44 lore raw

PATCH v4 1/2 reflog: name the strings that mark switches, renames and rebase steps

From: Harald Nordgren <[email protected]>

The next commit adds a "%gb" placeholder that shows which branch HEAD was on for each reflog entry. It finds the branch from the messages that branch switches, branch renames and rebases leave in the reflog. The commands that write these messages spell out the strings by hand, and "@{-<n>}" and "git status" each keep their own copy of the switch prefix to read it back. Reading them in one more place would add yet another copy that has to stay in sync.

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps, the "returning to " before the branch a rebase goes back to and the "checkout " before what it checks out, all written by "git rebase".

No change in behavior.

---
 builtin/branch.c   |  3 ++-
 builtin/checkout.c |  3 ++-
 builtin/rebase.c   | 19 ++++++++++---------
 object-name.c      |  3 ++-
 reflog.h           |  8 ++++++++
 sequencer.c        | 13 +++++++------
 wt-status.c        |  3 ++-
 7 files changed, 33 insertions(+), 19 deletions(-)
builtin/branch.c +2 -1
index b536b619bb..f7acb0f349 100644
@@ -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) &&
builtin/checkout.c +2 -1
index f28467f6df..cbdd78dfc8 100644
@@ -26,6 +26,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"
@@ -1043,7 +1044,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);
builtin/rebase.c +10 -9
index 535db60181..47a7b2fd3c 100644
@@ -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): " REFLOG_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;
@@ -867,7 +868,7 @@ static int checkout_up_to_date(struct rebase_options *options)
 	struct reset_working_tree_options ropts = { 0 };
 	int ret = 0;
 
-	strbuf_addf(&buf, "%s: checkout %s",
+	strbuf_addf(&buf, "%s: " REFLOG_CHECKOUT "%s",
 		    options->reflog_action, options->switch_to);
 	ropts.oid = &options->orig_head->object.oid;
 	ropts.branch = options->head_name;
@@ -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): " REFLOG_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): " REFLOG_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 |
object-name.c +2 -1
index 6546685760..5b6ce8e0e0 100644
@@ -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)
reflog.h +8 -0
index 3bdd1ca8a5..f5586242bf 100644
@@ -2,6 +2,14 @@
 #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_RETURNING_TO "returning to "
+#define REFLOG_CHECKOUT "checkout "
+
 #define REFLOG_EXPIRE_TOTAL   (1 << 0)
 #define REFLOG_EXPIRE_UNREACH (1 << 1)
 
sequencer.c +7 -6
index 508ec71792..da6f61a0e6 100644
@@ -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,
+					   REFLOG_CHECKOUT "%s", onto_name),
 		.default_reflog_action = sequencer_reflog_action(opts)
 	};
 	if (reset_working_tree(r, &ropts)) {
@@ -5311,16 +5312,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,
+				REFLOG_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);
wt-status.c +2 -1
index 238bb48643..3516432338 100644
@@ -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)
Signature · 1 line
gitgitgadget
#45 lore raw

PATCH v4 2/2 pretty: add %gb to name the branch of each reflog entry

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

The reflog of HEAD records the work done on every branch, so the output of "git reflog" mixes them and does not say which entry was made on which branch. Working that out by hand means looking back for the branch switch before each entry, and a rebase can move HEAD to another branch without writing one, as "git rebase <upstream> <branch>" does.

Add a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

    $ git log -g --format='%h %gd: (%gb) %gs'
    9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
    9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
    1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
    1a2b3c4 HEAD@{3}: (main) commit: fix typo
    5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main

The branch is read from the entries created by branch switches, branch renames and rebases. The entries of a rebase are labeled with the branch being rebased. When HEAD was detached, the entry shows "detached at <commit>" with the commit HEAD was at when it moved on. When nothing older in the reflog says which branch a rebase started on, the rebase is taken to have started on the branch it rebased. For the reflog of any other ref, "%gb" is the name of that ref.

---
 Documentation/pretty-formats.adoc |   3 +
 pretty.c                          |   4 +
 reflog-walk.c                     | 167 ++++++++++++++++++++++++++++--
 reflog-walk.h                     |   2 +
 t/t1414-reflog-walk.sh            | 137 ++++++++++++++++++++++++
 5 files changed, 307 insertions(+), 6 deletions(-)
Documentation/pretty-formats.adoc +3 -0
index 2ae0eb11a9..a8d00397c4 100644
@@ -334,6 +334,9 @@ endif::git-rev-list[]
 +%gE+:: reflog identity email (respecting .mailmap, see
 	linkgit:git-shortlog[1] or linkgit:git-blame[1])
 +%gs+:: reflog subject
++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
+	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
+	of any other ref, this is the shortened refname.
 ++%(trailers++`[:<option>,...]`++)++::
 display the trailers of the body as interpreted by
 linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
pretty.c +4 -0
index d8a9f370f6..1b3f7e2dc3 100644
@@ -1637,6 +1637,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 			if (c->pretty_ctx->reflog_info)
 				get_reflog_message(sb, c->pretty_ctx->reflog_info);
 			return 2;
+		case 'b':	/* reflog branch */
+			if (c->pretty_ctx->reflog_info)
+				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
+			return 2;
 		case 'n':
 		case 'N':
 		case 'e':
reflog-walk.c +161 -6

218 lines not on this page · raw message

reflog-walk.h +2 -0
index 989583dc55..fc1f344ce6 100644
@@ -13,6 +13,8 @@ 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);
+void get_reflog_branch(struct strbuf *sb,
+		       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,
t/t1414-reflog-walk.sh +137 -0

143 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#46 lore raw

Re: [PATCH v4 0/2] reflog: add format %gb

In reply to Harald Nordgren via GitGitGadget

Hi Harald

Quoted 4 lines · Harald Nordgren via GitGitGadget

It follows branch switches, branch renames and rebases, including "git rebase ", rebases of a detached HEAD and rebases of a branch that was already up to date. When HEAD was detached, the entry shows "detached at ".

The "detached at " labels show the commit before we switch away from a detached HEAD, not the commit where HEAD was detached. The parsing of "rebase: checkout <revision>" is still not quite right - it treats "origin/main" as a branch name and is confused by commits where the subject starts with "checkout " (I think the way to avoid that is to check that the reflog message does not match the commit subject [1]). Here is a failing test for those issues which also checks we handle ':' in a url in $GIT_REFLOG_ACTION correctly.

test_expect_success '%gb labels branches and detached HEADs' '

	test_create_repo origin &&
	(
		cd origin &&
		test_commit O1 &&
		test_commit O2
	) &&
	git clone origin downstream &&
	(
		cd origin &&
		test_commit O3
	) &&
	(
		cd downstream &&
		test_commit L1 &&
		git branch side &&
		test_commit L2 &&
		git pull -r "file://$TRASH_DIRECTORY/origin" &&
		test_commit L3 &&
		GIT_REFLOG_ACTION="sync switch" git rebase -i FETCH_HEAD side^0 &&
		H1=$(git rev-parse --short HEAD) &&
		test_commit "checkout main" L4.t L4 L4 &&
		git rebase FETCH_HEAD side &&
		test_commit L5 &&
		git rebase origin/main origin/main &&
		H2=$(git rev-parse --short HEAD) &&
		test_commit L6 &&
		git rebase main refs/heads/main &&
		H3=$(git rev-parse --short HEAD) &&
		test_commit L7 &&
		git switch side &&
		test_commit L8 &&
		git checkout origin/main &&
		H4=$(git rev-parse --short HEAD) &&
		test_commit L9 &&

		cat >expect <<-EOF &&
		commit: l9 (detached at $H4) &&
		checkout: moving from side to origin/main (detached at $H4)
		commit: L8 (side)
		checkout: moving from $(git rev-parse L7) to side (side)
		commit: L7 (detached at $H3)
		rebase: checkout refs/heads/main (detached at $H3)
		commit: L6 (detached at $H2)
		rebase: checkout origin/main (detached at $H2)
		commit: L5 (side)
		rebase (finish): returning to refs/heads/side (side)
		rebase (pick): L1 (side)
		rebase (start): checkout FETCH_HEAD (side)
		commit: checkout main (detached at $H1)
		sync switch (pick): L1 (detached at $H1)
		sync switch (start): checkout FETCH_HEAD (detached at $H1)
		commit: L3 (main)
		pull -r file://$TRASH_DIRECTORY/origin (finish): returning to refs/heads/main (main)
		pull -r file://$TRASH_DIRECTORY/origin (pick): L2 (main)
		pull -r file://$TRASH_DIRECTORY/origin (pick): L1 (main)
		pull -r file://$TRASH_DIRECTORY/origin (start): checkout $(git rev-parse FETCH_HEAD) (main)
		commit: L2 (main)
		commit: L1 (main)
		clone: from $TRASH_DIRECTORY/origin (main)
		EOF

		git log -g --format="%gs (%gb)" >actual &&
		test_cmp expect actual
	)

'

Thanks

Phillip

[1] https://github.com/phillipwood/git/blob/c11b0dcb38d4dbe26c41053569d4fae27fdf0de6/object-name.c#L1301

Quoted 122 lines

Changes in v4:

* %gb now names the right branch when git rebase <upstream> <branch> runs with GIT_REFLOG_ACTION set.
* Reworded the %gb commit message.

Changes in v3:

* Dropped --operations.
* Replaced --historical-branch with a %gb placeholder for --format, e.g.

    git log -g --format='%h %gd: (%gb) %gs'.

* %gb names the right branch after git rebase <upstream> <branch>, a rebase of a detached HEAD and an up-to-date rebase.
* Named the returning to and checkout reflog strings in reflog.h too.

Changes in v2:

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

Harald Nordgren (2):
reflog: name the strings that mark switches, renames and rebase steps
pretty: add %gb to name the branch of each reflog entry

  Documentation/pretty-formats.adoc |   3 +
  builtin/branch.c                  |   3 +-
  builtin/checkout.c                |   3 +-
  builtin/rebase.c                  |  19 ++--
  object-name.c                     |   3 +-
  pretty.c                          |   4 +
  reflog-walk.c                     | 167 ++++++++++++++++++++++++++++--
  reflog-walk.h                     |   2 +
  reflog.h                          |   8 ++
  sequencer.c                       |  13 +--
  t/t1414-reflog-walk.sh            | 137 ++++++++++++++++++++++++
  wt-status.c                       |   3 +-
  12 files changed, 340 insertions(+), 25 deletions(-)

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

Range-diff vs v3:

  1:  4d779e8da3 = 1:  f960a24b8b reflog: name the strings that mark switches, renames and rebase steps
  2:  dee548f9b1 ! 2:  99a1047702 pretty: add %gb to name the branch of each reflog entry
      @@ Commit message
               1a2b3c4 HEAD@{3}: (main) commit: fix typo
               5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main
       
      -    The branch is read from the entries that branch switches, branch
      -    renames and rebases leave behind. The entries of a rebase are named
      -    after the branch being rebased. When HEAD was detached, the entry
      -    shows "detached at <commit>" with the commit HEAD was at when it
      -    moved on. When nothing older in the reflog says which branch a rebase
      -    started on, the rebase is taken to have started on the branch it
      -    rebased. For the reflog of any other ref, "%gb" is the name of that
      -    ref.
      +    The branch is read from the entries created by branch switches,
      +    branch renames and rebases. The entries of a rebase are labeled with
      +    the branch being rebased. When HEAD was detached, the entry shows
      +    "detached at <commit>" with the commit HEAD was at when it moved on.
      +    When nothing older in the reflog says which branch a rebase started
      +    on, the rebase is taken to have started on the branch it rebased. For
      +    the reflog of any other ref, "%gb" is the name of that ref.
       
           Signed-off-by: Harald Nordgren <[email protected]>
       
      @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
       +	return p;
       +}
       +
      ++static const char *skip_rebase_checkout(const char *message)
      ++{
      ++	const char *p = strstr(message, " (");
      ++	const char *colon = strstr(message, ": ");
      ++
      ++	if (!colon || (p && p < colon) ||
      ++	    !skip_prefix(colon + 2, REFLOG_CHECKOUT, &p))
      ++		return NULL;
      ++	return p;
      ++}
      ++
       +struct branch_move {
       +	const char *from, *to;
       +	size_t from_len, to_len;
      @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
       +		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
       +		move->from = move->to = to;
       +		move->from_len = strcspn(to, "\n");
      -+	} else if (skip_prefix(message, "rebase: " REFLOG_CHECKOUT, &to)) {
      ++	} else if ((to = skip_rebase_checkout(message))) {
       +		move->to = to;
       +	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
       +		return false;
      @@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
       +	)
       +'
       +
      ++test_expect_success '%gb follows "git rebase <upstream> <branch>" with GIT_REFLOG_ACTION' '
      ++	(
      ++		cd branches &&
      ++		git switch main &&
      ++		GIT_REFLOG_ACTION="sync switch" git rebase main topic &&
      ++
      ++		cat >expect <<-\EOF &&
      ++		(topic) sync switch: checkout topic
      ++		(main) checkout: moving from topic to main
      ++		EOF
      ++		git log -g -2 --format="(%gb) %gs" >actual &&
      ++		test_cmp expect actual
      ++	)
      ++'
      ++
       +test_expect_success '%gb names a detached HEAD by the commit it was left at' '
       +	(
       +		cd branches &&
#47 lore raw

Re: [PATCH v4 0/2] reflog: add format %gb

Good point, and thanks a lot for the failing test!

Harald

#48 lore raw

v5 cover reflog: add format %gb

In reply to Harald Nordgren via GitGitGadget

Create a better reflog. Follows the discussion in
https://lore.kernel.org/git/[email protected]/.

The reflog of HEAD records the work done on every branch, so "git reflog" mixes them and does not say which entry was made on which branch. This adds a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

$ git log -g --format='%h %gd: (%gb) %gs'
9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
1a2b3c4 HEAD@{3}: (main) commit: fix typo

It follows branch switches, branch renames and rebases, including "git rebase ", rebases of a detached HEAD and rebases of a branch that was already up to date. When HEAD was detached, the entry shows "detached at ".

Changes in v5:

* detached at <commit> now names the commit HEAD was detached at, or for a detached rebase, the commit it ended on.
* git rebase <upstream> <rev> counts <rev> as a branch only if refs/heads/<rev> exists, so origin/main and refs/heads/main show as detached.
* A commit whose subject starts with checkout is no longer taken for a rebase checkout.
* Added Phillip's test, and tests for a detached rebase that fast-forwards and one that is aborted.

Changes in v4:

* %gb now names the right branch when git rebase <upstream> <branch> runs with GIT_REFLOG_ACTION set.
* Reworded the %gb commit message.

Changes in v3:

* Dropped --operations.
* Replaced --historical-branch with a %gb placeholder for --format, e.g. git log -g --format='%h %gd: (%gb) %gs'.
* %gb names the right branch after git rebase <upstream> <branch>, a rebase of a detached HEAD and an up-to-date rebase.
* Named the returning to and checkout reflog strings in reflog.h too.

Changes in v2:

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

Harald Nordgren (2):
reflog: name the strings that mark switches, renames and rebase steps
pretty: add %gb to name the branch of each reflog entry

 Documentation/pretty-formats.adoc |   3 +
 builtin/branch.c                  |   3 +-
 builtin/checkout.c                |   3 +-
 builtin/rebase.c                  |  19 +--
 object-name.c                     |   3 +-
 pretty.c                          |   4 +
 reflog-walk.c                     | 217 +++++++++++++++++++++++++++-
 reflog-walk.h                     |   2 +
 reflog.h                          |   9 ++
 sequencer.c                       |  15 +-
 t/t1414-reflog-walk.sh            | 229 ++++++++++++++++++++++++++++++
 wt-status.c                       |   3 +-
 12 files changed, 484 insertions(+), 26 deletions(-)

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

Range-diff vs v4:

1: f960a24b8b ! 1: 115b5a81b7 reflog: name the strings that mark switches, renames and rebase steps
@@ Commit message
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, the
- "returning to " before the branch a rebase goes back to and the
- "checkout " before what it checks out, all written by "git rebase".
+ "returning to " before the branch a rebase goes back to, the
+ "checkout " before what it checks out and the "fast-forward" it
+ records when a commit can be reused, all written by "git rebase".

No change in behavior.

@@ reflog.h
+#define REFLOG_STEP_ABORT "abort"
+#define REFLOG_RETURNING_TO "returning to "
+#define REFLOG_CHECKOUT "checkout "
++#define REFLOG_FAST_FORWARD "fast-forward"
+
#define REFLOG_EXPIRE_TOTAL (1 << 0)
#define REFLOG_EXPIRE_UNREACH (1 << 1)
@@ sequencer.c
#include "sparse-index.h"
#include "strvec.h"
#include "quote.h"
+@@ sequencer.c: static int fast_forward_to(struct repository *r,
+ if (checkout_fast_forward(r, from, to, 1))
+ return -1; /* the callee should have complained already */
+
+- strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
++ strbuf_addf(&sb, "%s: " REFLOG_FAST_FORWARD, action_name(opts));
+
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ 0, &err);
@@ sequencer.c: static int checkout_onto(struct repository *r, struct replay_opts *opts,
RESET_WORKING_TREE_UPDATE_HEAD | RESET_WORKING_TREE_UPDATE_ORIG_HEAD |
2: 99a1047702 ! 2: 34bb930908 pretty: add %gb to name the branch of each reflog entry

     @@ Commit message
          The branch is read from the entries created by branch switches,
          branch renames and rebases. The entries of a rebase are labeled with
          the branch being rebased. When HEAD was detached, the entry shows
     -    "detached at <commit>" with the commit HEAD was at when it moved on.
     +    "detached at <commit>" with the commit HEAD was detached at, or for a
     +    rebase of a detached HEAD the commit the rebase ended on. These
     +    entries only record what was typed, so when no newer entry says
     +    otherwise, a name counts as a branch if a branch of that name exists.
          When nothing older in the reflog says which branch a rebase started
          on, the rebase is taken to have started on the branch it rebased. For
          the reflog of any other ref, "%gb" is the name of that ref.
     @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
       	free_complete_reflog(array);
       }
       
     -+static char *branch_label(const char *name, size_t len,
     -+			  const struct object_id *oid)
     -+{
     -+	if (name) {
     -+		struct object_id hex_oid;
     -+		char *branch;
     -+
     -+		if (skip_prefix(name, "refs/heads/", &name))
     -+			len -= strlen("refs/heads/");
     -+		branch = xmemdupz(name, len);
     -+		if (strcmp(branch, "HEAD") && get_oid_hex(branch, &hex_oid) &&
     -+		    !check_refname_format(branch, REFNAME_ALLOW_ONELEVEL))
     -+			return branch;
     -+		free(branch);
     -+	}
     -+	return xstrfmt("detached at %s",
     -+		       repo_find_unique_abbrev(the_repository, oid,
     -+					       DEFAULT_ABBREV));
     -+}
     -+
      +static const char *skip_rebase_step(const char *message, const char *step)
      +{
      +	const char *p = strstr(message, " (");
     @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
      +	return p;
      +}
      +
     -+static const char *skip_rebase_checkout(const char *message)
     ++static bool is_commit_subject(const char *text, const struct object_id *oid)
      +{
     -+	const char *p = strstr(message, " (");
     -+	const char *colon = strstr(message, ": ");
     ++	struct commit *commit = lookup_commit_reference_gently(the_repository,
     ++							       oid, 1);
     ++	const char *buffer, *subject;
     ++	size_t len;
     ++	bool ret;
     ++
     ++	if (!commit)
     ++		return false;
     ++	buffer = repo_get_commit_buffer(the_repository, commit, NULL);
     ++	len = find_commit_subject(buffer, &subject);
     ++	ret = strcspn(text, "\n") == len && !memcmp(text, subject, len);
     ++	repo_unuse_commit_buffer(the_repository, commit, buffer);
     ++	return ret;
     ++}
      +
     -+	if (!colon || (p && p < colon) ||
     -+	    !skip_prefix(colon + 2, REFLOG_CHECKOUT, &p))
     ++static const char *skip_rebase_checkout(const struct reflog_info *item)
     ++{
     ++	const char *colon = strstr(item->message, ": ");
     ++	const char *rev;
     ++
     ++	if (!colon || !skip_prefix(colon + 2, REFLOG_CHECKOUT, &rev) ||
     ++	    is_commit_subject(colon + 2, &item->noid))
      +		return NULL;
     -+	return p;
     ++	return rev;
     ++}
     ++
     ++static const char *existing_branch(const char *name)
     ++{
     ++	char *ref = xstrfmt("refs/heads/%.*s", (int)strcspn(name, "\n"), name);
     ++	bool exists = refs_ref_exists(get_main_ref_store(the_repository), ref);
     ++
     ++	free(ref);
     ++	return exists ? name : NULL;
      +}
      +
      +struct branch_move {
      +	const char *from, *to;
      +	size_t from_len, to_len;
     ++	bool from_detached;
      +};
      +
     -+static bool parse_branch_move(const char *message, struct branch_move *move)
     ++static bool parse_branch_move(const struct reflog_info *item,
     ++			      struct branch_move *move)
      +{
     -+	const char *to;
     ++	const char *message = item->message;
     ++	const char *from, *to, *end;
     ++	struct object_id oid;
      +
     -+	move->from = move->to = NULL;
     -+	move->from_len = move->to_len = 0;
     -+	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &move->from) ||
     -+	    skip_prefix(message, REFLOG_RENAME_PREFIX, &move->from)) {
     -+		to = strstr(move->from, " to ");
     ++	memset(move, 0, sizeof(*move));
     ++	if (skip_prefix(message, REFLOG_SWITCH_PREFIX, &from)) {
     ++		to = strstr(from, " to ");
      +		if (!to)
      +			return false;
     -+		move->from_len = to - move->from;
     -+		move->to = to + strlen(" to ");
     ++		if (!parse_oid_hex(from, &oid, &end) && end == to) {
     ++			move->from_detached = true;
     ++		} else {
     ++			move->from = from;
     ++			move->from_len = to - from;
     ++		}
     ++		move->to = existing_branch(to + strlen(" to "));
     ++	} else if (skip_prefix(message, REFLOG_RENAME_PREFIX "refs/heads/",
     ++			       &from)) {
     ++		to = strstr(from, " to refs/heads/");
     ++		if (!to)
     ++			return false;
     ++		move->from = from;
     ++		move->from_len = to - from;
     ++		move->to = to + strlen(" to refs/heads/");
      +	} else if (((to = skip_rebase_step(message, REFLOG_STEP_FINISH)) ||
      +		    (to = skip_rebase_step(message, REFLOG_STEP_ABORT))) &&
     -+		   skip_prefix(to, REFLOG_RETURNING_TO, &to)) {
     ++		   skip_prefix(to, REFLOG_RETURNING_TO "refs/heads/", &to)) {
      +		move->from = move->to = to;
      +		move->from_len = strcspn(to, "\n");
     -+	} else if ((to = skip_rebase_checkout(message))) {
     -+		move->to = to;
     -+	} else if (!skip_rebase_step(message, REFLOG_STEP_START)) {
     ++	} else if (skip_rebase_step(message, REFLOG_STEP_START)) {
     ++		return true;
     ++	} else if ((to = skip_rebase_checkout(item))) {
     ++		move->to = existing_branch(to);
     ++	} else {
      +		return false;
      +	}
      +	if (move->to)
     @@ reflog-walk.c: static void complete_reflogs_clear(void *util, const char *str UN
      +	return true;
      +}
      +
     ++static char *detached_label(const struct complete_reflogs *array,
     ++			    int i, int end)
     ++{
     ++	const struct reflog_info *item = &array->items[i];
     ++
     ++	if (skip_rebase_step(item->message, REFLOG_STEP_START)) {
     ++		size_t len = strstr(item->message, " (") + 2 - item->message;
     ++
     ++		for (int j = end - 1; j > i; j--) {
     ++			const char *message = array->items[j].message;
     ++
     ++			if (!strncmp(message, item->message, len) ||
     ++			    starts_with(message,
     ++					"rebase: " REFLOG_FAST_FORWARD)) {
     ++				item = &array->items[j];
     ++				break;
     ++			}
     ++		}
     ++	}
     ++	return xstrfmt("detached at %s",
     ++		       repo_find_unique_abbrev(the_repository, &item->noid,
     ++					       DEFAULT_ABBREV));
     ++}
     ++
      +static void mark_branches(struct complete_reflogs *array)
      +{
      +	struct ref_store *refs = get_main_ref_store(the_repository);
     -+	struct object_id oid;
     -+	const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0, &oid, NULL);
     ++	const char *head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
      +	char *branch = NULL;
     -+	int unknown = array->nr;
     ++	bool detached = false;
     ++	int unlabeled = array->nr;
      +
     -+	if (head)
     -+		branch = branch_label(head, strlen(head), &oid);
     ++	if (head && skip_prefix(head, "refs/heads/", &head))
     ++		branch = xstrdup(head);
     ++	else if (head)
     ++		detached = true;
      +
      +	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)) {
     ++		if (!parse_branch_move(item, &move)) {
      +			item->branch = xstrdup_or_null(branch);
      +			continue;
      +		}
      +
      +		if (!branch) {
     -+			branch = branch_label(move.to, move.to_len,
     -+					      &array->items[unknown - 1].noid);
     -+			for (int j = i + 1; j < unknown; j++)
     ++			branch = move.to && !detached ?
     ++				xmemdupz(move.to, move.to_len) :
     ++				detached_label(array, i, unlabeled);
     ++			for (int j = i + 1; j < unlabeled; j++)
      +				array->items[j].branch = xstrdup(branch);
      +		}
      +		item->branch = branch;
     -+		if (move.from) {
     -+			branch = branch_label(move.from, move.from_len,
     -+					      &item->ooid);
     -+		} else {
     -+			branch = NULL;
     -+			unknown = i;
     -+		}
     ++		branch = move.from ? xmemdupz(move.from, move.from_len) : NULL;
     ++		detached = move.from_detached;
     ++		unlabeled = i;
      +	}
      +
     -+	if (!branch && unknown < array->nr)
     -+		for (int i = 0; i < unknown; i++)
     -+			array->items[i].branch =
     -+				xstrdup(array->items[unknown].branch);
     ++	if (!branch) {
     ++		if (detached)
     ++			branch = detached_label(array, 0, unlabeled);
     ++		else if (unlabeled < array->nr)
     ++			branch = xstrdup(array->items[unlabeled].branch);
     ++		for (int i = 0; branch && i < unlabeled; i++)
     ++			array->items[i].branch = xstrdup(branch);
     ++	}
      +	free(branch);
      +}
      +
     @@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
      +	)
      +'
      +
     -+test_expect_success '%gb names a detached HEAD by the commit it was left at' '
     ++test_expect_success '%gb names a detached HEAD by the commit it was detached at' '
      +	(
      +		cd branches &&
      +		git switch --detach topic~1 &&
     ++		detached_at=$(git rev-parse --short HEAD) &&
      +		test_commit detached &&
      +		git rebase topic &&
      +		rebased=$(git rev-parse HEAD) &&
      +		git switch topic &&
      +
     -+		left_before=$(git rev-parse --short detached) &&
     -+		left_after=$(git rev-parse --short $rebased) &&
     ++		rebased_at=$(git rev-parse --short $rebased) &&
      +		cat >expect <<-EOF &&
      +		(topic) checkout: moving from $rebased to topic
     -+		(detached at $left_after) rebase (pick): detached
     -+		(detached at $left_after) rebase (start): checkout topic
     -+		(detached at $left_before) commit: detached
     -+		(detached at $left_before) checkout: moving from topic to topic~1
     ++		(detached at $rebased_at) rebase (pick): detached
     ++		(detached at $rebased_at) rebase (start): checkout topic
     ++		(detached at $detached_at) commit: detached
     ++		(detached at $detached_at) checkout: moving from topic to topic~1
      +		EOF
      +		git log -g -5 --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
      +
     -+test_expect_success '%gb names the branch of a "git pull --rebase" in a fresh clone' '
     -+	git clone branches downstream &&
     ++test_expect_success '%gb names a detached rebase by the commit it ended on' '
     ++	write_script add-exec <<-\EOF &&
     ++	{ echo "exec true" && cat "$1"; } >"$1.new" &&
     ++	mv "$1.new" "$1"
     ++	EOF
      +	(
      +		cd branches &&
     -+		test_commit upstream-change
     -+	) &&
     ++		git switch --detach topic^0 &&
     ++		test_set_sequence_editor "$TRASH_DIRECTORY/add-exec" &&
     ++		git rebase -i @^ &&
     ++		ended_at=$(git rev-parse --short HEAD) &&
     ++		test_commit after-fast-forward &&
     ++
     ++		cat >expect <<-EOF &&
     ++		(detached at $ended_at) commit: after-fast-forward
     ++		(detached at $ended_at) rebase: fast-forward
     ++		(detached at $ended_at) rebase (start): checkout @^
     ++		EOF
     ++		git log -g -3 --format="(%gb) %gs" >actual &&
     ++		test_cmp expect actual
     ++	)
     ++'
     ++
     ++test_expect_success '%gb names an aborted rebase of a detached HEAD' '
      +	(
     -+		cd downstream &&
     -+		test_commit local-change &&
     -+		git pull --rebase &&
     ++		cd branches &&
     ++		git switch --detach topic^0 &&
     ++		test_must_fail git rebase -f -x false @^ &&
     ++		git rebase --abort &&
     ++		returned_to=$(git rev-parse HEAD) &&
     ++		short=$(git rev-parse --short HEAD) &&
      +
     -+		echo "(topic)" >expect &&
     -+		git log -g --format="(%gb)" >out &&
     -+		sort -u out >actual &&
     ++		cat >expect <<-EOF &&
     ++		(detached at $short) rebase (abort): returning to $returned_to
     ++		(detached at $short) rebase (pick): B
     ++		(detached at $short) rebase (start): checkout @^
     ++		EOF
     ++		git log -g -3 --format="(%gb) %gs" >actual &&
      +		test_cmp expect actual
      +	)
      +'
     @@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
      +test_expect_success '%gb follows a renamed branch' '
      +	(
      +		cd branches &&
     ++		git switch topic &&
      +		test_commit before-rename &&
      +		git branch -m topic renamed &&
      +		test_commit after-rename &&
     @@ t/t1414-reflog-walk.sh: test_expect_success 'walk prefers reflog to ref tip' '
      +		test_cmp expect actual
      +	)
      +'
     ++
     ++test_expect_success '%gb labels branches and detached HEADs' '
     ++	test_create_repo origin &&
     ++	(
     ++		cd origin &&
     ++		test_commit O1 &&
     ++		test_commit O2
     ++	) &&
     ++	git clone origin downstream &&
     ++	(
     ++		cd origin &&
     ++		test_commit O3
     ++	) &&
     ++	(
     ++		cd downstream &&
     ++		test_commit L1 &&
     ++		git branch side &&
     ++		test_commit L2 &&
     ++		git pull -r "file://$TRASH_DIRECTORY/origin" &&
     ++		test_commit L3 &&
     ++		GIT_REFLOG_ACTION="sync switch" git rebase -i FETCH_HEAD side^0 &&
     ++		H1=$(git rev-parse --short HEAD) &&
     ++		test_commit "checkout main" L4.t L4 L4 &&
     ++		git rebase FETCH_HEAD side &&
     ++		test_commit L5 &&
     ++		git rebase origin/main origin/main &&
     ++		H2=$(git rev-parse --short HEAD) &&
     ++		test_commit L6 &&
     ++		git rebase main refs/heads/main &&
     ++		H3=$(git rev-parse --short HEAD) &&
     ++		test_commit L7 &&
     ++		git switch side &&
     ++		test_commit L8 &&
     ++		git checkout origin/main &&
     ++		H4=$(git rev-parse --short HEAD) &&
     ++		test_commit L9 &&
     ++
     ++		cat >expect <<-EOF &&
     ++		commit: L9 (detached at $H4)
     ++		checkout: moving from side to origin/main (detached at $H4)
     ++		commit: L8 (side)
     ++		checkout: moving from $(git rev-parse L7) to side (side)
     ++		commit: L7 (detached at $H3)
     ++		rebase: checkout refs/heads/main (detached at $H3)
     ++		commit: L6 (detached at $H2)
     ++		rebase: checkout origin/main (detached at $H2)
     ++		commit: L5 (side)
     ++		rebase (finish): returning to refs/heads/side (side)
     ++		rebase (pick): L1 (side)
     ++		rebase (start): checkout FETCH_HEAD (side)
     ++		commit: checkout main (detached at $H1)
     ++		sync switch (pick): L1 (detached at $H1)
     ++		sync switch (start): checkout FETCH_HEAD (detached at $H1)
     ++		commit: L3 (main)
     ++		pull -r file://$TRASH_DIRECTORY/origin (finish): returning to refs/heads/main (main)
     ++		pull -r file://$TRASH_DIRECTORY/origin (pick): L2 (main)
     ++		pull -r file://$TRASH_DIRECTORY/origin (pick): L1 (main)
     ++		pull -r file://$TRASH_DIRECTORY/origin (start): checkout $(git rev-parse FETCH_HEAD) (main)
     ++		commit: L2 (main)
     ++		commit: L1 (main)
     ++		clone: from $(cd ../origin && pwd) (main)
     ++		EOF
     ++
     ++		git log -g --format="%gs (%gb)" >actual &&
     ++		test_cmp expect actual
     ++	)
     ++'
      +
       test_expect_success 'rev-list -g complains when there are no reflogs' '
       	test_must_fail git rev-list -g
Signature · 1 line
gitgitgadget
#49 lore raw

PATCH v5 1/2 reflog: name the strings that mark switches, renames and rebase steps

From: Harald Nordgren <[email protected]>

The next commit adds a "%gb" placeholder that shows which branch HEAD was on for each reflog entry. It finds the branch from the messages that branch switches, branch renames and rebases leave in the reflog. The commands that write these messages spell out the strings by hand, and "@{-<n>}" and "git status" each keep their own copy of the switch prefix to read it back. Reading them in one more place would add yet another copy that has to stay in sync.

Name the strings once in reflog.h and use the names wherever the messages are written or read: the "checkout: moving from " prefix written by "git checkout", the "Branch: renamed " prefix written by "git branch -m", and the "start", "finish" and "abort" steps, the "returning to " before the branch a rebase goes back to, the "checkout " before what it checks out and the "fast-forward" it records when a commit can be reused, all written by "git rebase".

No change in behavior.

---
 builtin/branch.c   |  3 ++-
 builtin/checkout.c |  3 ++-
 builtin/rebase.c   | 19 ++++++++++---------
 object-name.c      |  3 ++-
 reflog.h           |  9 +++++++++
 sequencer.c        | 15 ++++++++-------
 wt-status.c        |  3 ++-
 7 files changed, 35 insertions(+), 20 deletions(-)
builtin/branch.c +2 -1
index b536b619bb..f7acb0f349 100644
@@ -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) &&
builtin/checkout.c +2 -1
index f28467f6df..cbdd78dfc8 100644
@@ -26,6 +26,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"
@@ -1043,7 +1044,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);
builtin/rebase.c +10 -9
index 535db60181..47a7b2fd3c 100644
@@ -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): " REFLOG_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;
@@ -867,7 +868,7 @@ static int checkout_up_to_date(struct rebase_options *options)
 	struct reset_working_tree_options ropts = { 0 };
 	int ret = 0;
 
-	strbuf_addf(&buf, "%s: checkout %s",
+	strbuf_addf(&buf, "%s: " REFLOG_CHECKOUT "%s",
 		    options->reflog_action, options->switch_to);
 	ropts.oid = &options->orig_head->object.oid;
 	ropts.branch = options->head_name;
@@ -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): " REFLOG_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): " REFLOG_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 |
object-name.c +2 -1
index 6546685760..5b6ce8e0e0 100644
@@ -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)
reflog.h +9 -0
index 3bdd1ca8a5..d396d1df05 100644
@@ -2,6 +2,15 @@
 #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_RETURNING_TO "returning to "
+#define REFLOG_CHECKOUT "checkout "
+#define REFLOG_FAST_FORWARD "fast-forward"
+
 #define REFLOG_EXPIRE_TOTAL   (1 << 0)
 #define REFLOG_EXPIRE_UNREACH (1 << 1)
 
sequencer.c +8 -7
index 4a1c0e70b8..5aa560315d 100644
@@ -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"
@@ -664,7 +665,7 @@ static int fast_forward_to(struct repository *r,
 	if (checkout_fast_forward(r, from, to, 1))
 		return -1; /* the callee should have complained already */
 
-	strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
+	strbuf_addf(&sb, "%s: " REFLOG_FAST_FORWARD, action_name(opts));
 
 	transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
 						  0, &err);
@@ -5014,8 +5015,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,
+					   REFLOG_CHECKOUT "%s", onto_name),
 		.default_reflog_action = sequencer_reflog_action(opts)
 	};
 	if (reset_working_tree(r, &ropts)) {
@@ -5314,16 +5315,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,
+				REFLOG_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);
wt-status.c +2 -1
index 238bb48643..3516432338 100644
@@ -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)
Signature · 1 line
gitgitgadget
#50 lore raw

PATCH v5 2/2 pretty: add %gb to name the branch of each reflog entry

In reply to Harald Nordgren via GitGitGadget

From: Harald Nordgren <[email protected]>

The reflog of HEAD records the work done on every branch, so the output of "git reflog" mixes them and does not say which entry was made on which branch. Working that out by hand means looking back for the branch switch before each entry, and a rebase can move HEAD to another branch without writing one, as "git rebase <upstream> <branch>" does.

Add a "%gb" placeholder that names the branch HEAD was on when each entry was recorded:

    $ git log -g --format='%h %gd: (%gb) %gs'
    9c8d7e6 HEAD@{0}: (topic) rebase (finish): returning to refs/heads/topic
    9c8d7e6 HEAD@{1}: (topic) rebase (pick): add tests
    1a2b3c4 HEAD@{2}: (topic) rebase (start): checkout main
    1a2b3c4 HEAD@{3}: (main) commit: fix typo
    5d6e7f8 HEAD@{4}: (main) checkout: moving from topic to main

The branch is read from the entries created by branch switches, branch renames and rebases. The entries of a rebase are labeled with the branch being rebased. When HEAD was detached, the entry shows "detached at <commit>" with the commit HEAD was detached at, or for a rebase of a detached HEAD the commit the rebase ended on. These entries only record what was typed, so when no newer entry says otherwise, a name counts as a branch if a branch of that name exists. When nothing older in the reflog says which branch a rebase started on, the rebase is taken to have started on the branch it rebased. For the reflog of any other ref, "%gb" is the name of that ref.

---
 Documentation/pretty-formats.adoc |   3 +
 pretty.c                          |   4 +
 reflog-walk.c                     | 217 +++++++++++++++++++++++++++-
 reflog-walk.h                     |   2 +
 t/t1414-reflog-walk.sh            | 229 ++++++++++++++++++++++++++++++
 5 files changed, 449 insertions(+), 6 deletions(-)
Documentation/pretty-formats.adoc +3 -0
index 2ae0eb11a9..a8d00397c4 100644
@@ -334,6 +334,9 @@ endif::git-rev-list[]
 +%gE+:: reflog identity email (respecting .mailmap, see
 	linkgit:git-shortlog[1] or linkgit:git-blame[1])
 +%gs+:: reflog subject
++%gb+:: reflog branch, the branch `HEAD` was on when the entry was
+	recorded, e.g., `topic` or `detached at 1a2b3c4`. For the reflog
+	of any other ref, this is the shortened refname.
 ++%(trailers++`[:<option>,...]`++)++::
 display the trailers of the body as interpreted by
 linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by
pretty.c +4 -0
index d8a9f370f6..1b3f7e2dc3 100644
@@ -1637,6 +1637,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 			if (c->pretty_ctx->reflog_info)
 				get_reflog_message(sb, c->pretty_ctx->reflog_info);
 			return 2;
+		case 'b':	/* reflog branch */
+			if (c->pretty_ctx->reflog_info)
+				get_reflog_branch(sb, c->pretty_ctx->reflog_info);
+			return 2;
 		case 'n':
 		case 'N':
 		case 'e':
reflog-walk.c +211 -6

268 lines not on this page · raw message

reflog-walk.h +2 -0
index 989583dc55..fc1f344ce6 100644
@@ -13,6 +13,8 @@ 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);
+void get_reflog_branch(struct strbuf *sb,
+		       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,
t/t1414-reflog-walk.sh +229 -0

235 lines not on this page · raw message

Signature · 1 line
gitgitgadget