Subject: [PATCH v1] replay.c: support replaying root commits

From: Tian Yuchen <[email-masked]>
Date: Sat, 28 Mar 2026 02:09:30 +0800

'git replay' does not support replaying root commits, as indicated by
the FIXME comment. If a user attempts to replay a root commit, the 'die()'
in 'replay_revisions()' will be called.

The reason for this is that the call 'base = pickme->parents->item' in
'pick_regular_commit()' will cause a null pointer dereference error in the
case of a root commit.

Fix this by adding new conditional statements: the difference between the
root commit its 'common ancestor' is equivalent to the difference from
an empty tree object, and it must be placed on the user-specified target
branch 'onto'. Therefore,  We set 'base' to 'NULL', 'replayed_base' to
'onto', and use 'lookup_tree()' to instantiate the OID of the empty tree
in order to pass it to 'base_tree'.

Signed-off-by: Tian Yuchen <[email-masked]>
---

To be honest, I'm not sure whether setting 'base = NULL' is proper or
not. Feel free to give feedback and suggestions!

 replay.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)

diff --git a/replay.c b/replay.c
index a63f6714c4..80497191a8 100644
--- a/replay.c
+++ b/replay.c
@@ -225,12 +225,21 @@ static struct commit *pick_regular_commit(struct repository *repo,
 	struct commit *base, *replayed_base;
 	struct tree *pickme_tree, *base_tree, *replayed_base_tree;
 
-	base = pickme->parents->item;
-	replayed_base = mapped_commit(replayed_commits, base, onto);
-
+	if (pickme->parents) {
+		base = pickme->parents->item;
+		replayed_base = mapped_commit(replayed_commits, base, onto);
+		base_tree = repo_get_commit_tree(repo, base);
+	} else {
+		/* 
+		 * Root commits have no parents. Their base is the empty tree,
+		 * and they should be replayed directly onto the target 'onto' commit.
+		 */
+		base = NULL;
+		replayed_base = onto;
+		base_tree = lookup_tree(repo, repo->hash_algo->empty_tree);
+	}
 	replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
 	pickme_tree = repo_get_commit_tree(repo, pickme);
-	base_tree = repo_get_commit_tree(repo, base);
 
 	merge_opt->branch1 = short_commit_name(repo, replayed_base);
 	merge_opt->branch2 = short_commit_name(repo, pickme);
@@ -293,8 +302,6 @@ int replay_revisions(struct rev_info *revs,
 	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 */
-
 	if (prepare_revision_walk(revs) < 0) {
 		ret = error(_("error preparing revisions"));
 		goto out;
@@ -309,9 +316,7 @@ int replay_revisions(struct rev_info *revs,
 		khint_t pos;
 		int hr;
 
-		if (!commit->parents)
-			die(_("replaying down from root commit is not supported yet!"));
-		if (commit->parents->next)
+		if (commit->parents && commit->parents->next)
 			die(_("replaying merge commits is not supported yet!"));
 
 		last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
-- 
2.43.0