]> git.ipfire.org Git - thirdparty/git.git/commitdiff
replay: support replaying down from root commit
authorToon Claes <toon@iotcl.com>
Tue, 24 Mar 2026 19:35:41 +0000 (20:35 +0100)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Mar 2026 19:41:13 +0000 (12:41 -0700)
git-replay(1) doesn't allow replaying commits all the way down to the
root commit. Fix that.

Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
replay.c
t/t3650-replay-basics.sh

index a63f6714c4c2ec641bee226983e3d892be854b44..92f2279156d69a3156cb4ac28b082077cbe93b07 100644 (file)
--- a/replay.c
+++ b/replay.c
@@ -209,7 +209,10 @@ static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
                                    struct commit *commit,
                                    struct commit *fallback)
 {
-       khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
+       khint_t pos;
+       if (!commit)
+               return fallback;
+       pos = kh_get_oid_map(replayed_commits, commit->object.oid);
        if (pos == kh_end(replayed_commits))
                return fallback;
        return kh_value(replayed_commits, pos);
@@ -225,16 +228,24 @@ 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;
+               base_tree = repo_get_commit_tree(repo, base);
+       } else {
+               base = NULL;
+               base_tree = lookup_tree(repo, repo->hash_algo->empty_tree);
+       }
 
+       replayed_base = mapped_commit(replayed_commits, base, onto);
        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);
-       merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
+       if (pickme->parents)
+               merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
+       else
+               merge_opt->ancestor = xstrdup("empty tree");
 
        merge_incore_nonrecursive(merge_opt,
                                  base_tree,
@@ -293,8 +304,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 +318,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,
index a03f8f9293eb12853e6ba6608ccf543359f3aa13..9c55b62757625b1473f8f9c07e3154e0e9d2199b 100755 (executable)
@@ -81,9 +81,13 @@ test_expect_success 'option --onto or --advance is mandatory' '
        test_cmp expect actual
 '
 
-test_expect_success 'no base or negative ref gives no-replaying down to root error' '
-       echo "fatal: replaying down from root commit is not supported yet!" >expect &&
-       test_must_fail git replay --onto=topic1 topic2 2>actual &&
+test_expect_success 'replay down to root onto another branch' '
+       git replay --ref-action=print --onto main topic2 >result &&
+
+       test_line_count = 1 result &&
+
+       git log --format=%s $(cut -f 3 -d " " result) >actual &&
+       test_write_lines E D C M L B A >expect &&
        test_cmp expect actual
 '