git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v10 5/8] replay: support updating detached HEAD

From
Patrick Steinhardt <ps@pks.im>
Date
Jan 12, 2026, 14:15 UTC
Message-ID
<20260112-b4-pks-history-builtin-v10-5-e3c6aa5b4cec@pks.im>
In-Reply-To
<20260112-b4-pks-history-builtin-v10-0-e3c6aa5b4cec@pks.im>

In a subsequent commit we're about to introduce a new git-history(1) command, which will by default work on all local branches and HEAD. This is already well-supported by the replay machinery for most of the part: updating branches is one of its prime use cases, and the HEAD ref is also updated in case it points to any of the branches.

However, what's not supported yet is to update HEAD in case it is not a symbolic ref. We determine the refs that need to be updated by iterating through the decorations of the original commit, but we only update those refs that are `DECORATION_REF_LOCAL`, which covers local branches.

Address this gap by also handling `DECORATION_REF_HEAD`. Note though that this needs to only happen in case we're working on a detached HEAD. If HEAD is pointing to a branch, then we'd already update that branch via `DECORATION_REF_LOCAL`.

Refactor the loop that iterates through the decorations a bit to make the individual conditions easier to understand.

Based-on-patch-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 replay.c                 | 47 +++++++++++++++++++++++++++++++++--------------
 t/t3650-replay-basics.sh |  9 +++++++++
 2 files changed, 42 insertions(+), 14 deletions(-)
diff --git a/replay.c b/replay.c
index a8e6d5b30b..6a8cd2f72d 100644
--- a/replay.c
+++ b/replay.c
@@ -150,11 +150,17 @@ static void get_ref_information(struct repository *repo,
 static void set_up_replay_mode(struct repository *repo,
 			       struct rev_cmdline_info *cmd_info,
 			       const char *onto_name,
+			       bool *detached_head,
 			       char **advance_name,
 			       struct commit **onto,
 			       struct strset **update_refs)
 {
 	struct ref_info rinfo;
+	int head_flags = 0;
+
+	refs_read_ref_full(get_main_ref_store(repo), "HEAD",
+			   RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
+	*detached_head = !(head_flags & REF_ISSYMREF);
 
 	get_ref_information(repo, cmd_info, &rinfo);
 	if (!rinfo.positive_refexprs)
@@ -269,12 +275,13 @@ int replay_revisions(struct rev_info *revs,
 	struct merge_result result = {
 		.clean = 1,
 	};
+	bool detached_head;
 	char *advance;
 	int ret;
 
 	advance = xstrdup_or_null(opts->advance);
-	set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto, &advance,
-			   &onto, &update_refs);
+	set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto,
+			   &detached_head, &advance, &onto, &update_refs);
 
 	/* FIXME: Should allow replaying commits with the first as a root commit */
 
@@ -312,18 +319,30 @@ int replay_revisions(struct rev_info *revs,
 		/* Update any necessary branches */
 		if (advance)
 			continue;
-		decoration = get_name_decoration(&commit->object);
-		if (!decoration)
-			continue;
-		while (decoration) {
-			if (decoration->type == DECORATION_REF_LOCAL &&
-			    (opts->contained || strset_contains(update_refs,
-								decoration->name))) {
-				replay_result_queue_update(out, decoration->name,
-							   &commit->object.oid,
-							   &last_commit->object.oid);
-			}
-			decoration = decoration->next;
+
+		for (decoration = get_name_decoration(&commit->object);
+		     decoration;
+		     decoration = decoration->next)
+		{
+			if (decoration->type != DECORATION_REF_LOCAL &&
+			    decoration->type != DECORATION_REF_HEAD)
+				continue;
+
+			/*
+			 * We only need to update HEAD separately in case it's
+			 * detached. If it's not we'd already update the branch
+			 * it is pointing to.
+			 */
+			if (decoration->type == DECORATION_REF_HEAD && !detached_head)
+				continue;
+
+			if (!opts->contained &&
+			    !strset_contains(update_refs, decoration->name))
+				continue;
+
+			replay_result_queue_update(out, decoration->name,
+						   &commit->object.oid,
+						   &last_commit->object.oid);
 		}
 	}
 
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 307101eeb9..c862aa39f3 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -249,6 +249,15 @@ test_expect_success 'using replay on bare repo to rebase multiple divergent bran
 	done
 '
 
+test_expect_success 'using replay to update detached HEAD' '
+	current_head=$(git branch --show-current) &&
+	test_when_finished git switch "$current_head" &&
+	git switch --detach &&
+	test_commit something &&
+	git replay --ref-action=print --onto HEAD~2 --ref-action=print HEAD~..HEAD >updates &&
+	test_grep "update HEAD " updates
+'
+
 test_expect_success 'merge.directoryRenames=false' '
 	# create a test case that stress-tests the rename caching
 	git switch -c rename-onto &&
-- 
2.52.0.590.g1f87b77810.dirty
Previous: Patrick SteinhardtNext: Elijah Newren
Message 12 of 19 in “Introduce git-history(1) command for easy history editing”
  1. 0/8 Introduce git-history(1) command for easy history editingPatrick Steinhardt, Jan 12, 2026
  2. 1/8 builtin/replay: extract core logic to replay revisionsPatrick Steinhardt, Jan 12, 2026
  3. Junio C HamanoJan 12, 2026
  4. Patrick SteinhardtJan 12, 2026
  5. Elijah NewrenJan 13, 2026
  6. Patrick SteinhardtJan 13, 2026
  7. 2/8 builtin/replay: move core logic into "libgit.a"Patrick Steinhardt, Jan 12, 2026
  8. 3/8 replay: small set of cleanupsPatrick Steinhardt, Jan 12, 2026
  9. 4/8 replay: support empty commit rangesPatrick Steinhardt, Jan 12, 2026
  10. Elijah NewrenJan 13, 2026
  11. Patrick SteinhardtJan 13, 2026
  12. 5/8 replay: support updating detached HEADPatrick Steinhardt, Jan 12, 2026
  13. Elijah NewrenJan 13, 2026
  14. Patrick SteinhardtJan 13, 2026
  15. 6/8 wt-status: provide function to expose status for treesPatrick Steinhardt, Jan 12, 2026
  16. 7/8 builtin: add new "history" commandPatrick Steinhardt, Jan 12, 2026
  17. 8/8 builtin/history: implement "reword" subcommandPatrick Steinhardt, Jan 12, 2026
  18. Elijah NewrenJan 13, 2026
  19. Elijah NewrenJan 13, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.