]> git.ipfire.org Git - thirdparty/git.git/commitdiff
replay: drop commits that become empty
authorPhillip Wood <phillip.wood@dunelm.org.uk>
Thu, 18 Dec 2025 16:50:26 +0000 (16:50 +0000)
committerJunio C Hamano <gitster@pobox.com>
Fri, 19 Dec 2025 03:56:37 +0000 (12:56 +0900)
If the changes in a commit being replayed are already in the branch
that the commits are being replayed onto, then "git replay" creates an
empty commit. This is confusing because the commit message no longer
matches the contents of the commit. Drop the commit instead. Commits
that start off empty are not dropped. This matches the behavior of
"git rebase --reapply-cherry-pick --empty=drop" and "git cherry-pick
--empty-drop".

If a branch points to a commit that is dropped it will be updated
to point to the last commit that was not dropped. This can be seen
in the new test where "topic1" is updated to point to the rebased
"C" as "F" is dropped because it is already upstream. While this is
a breaking change, "git replay" is marked as experimental to allow
improvements like this that change the behavior.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-replay.adoc
replay.c
t/t3650-replay-basics.sh

index dcb26e8a8e88cac30fb04293c8b941b80ef2acc2..96a3a557bf3338b4293245f074f0d1e5f58732d6 100644 (file)
@@ -59,7 +59,9 @@ The default mode can be configured via the `replay.refAction` configuration vari
        be passed, but in `--advance <branch>` mode, they should have
        a single tip, so that it's clear where <branch> should point
        to. See "Specifying Ranges" in linkgit:git-rev-parse[1] and the
-       "Commit Limiting" options below.
+       "Commit Limiting" options below. Any commits in the range whose
+       changes are already present in the branch the commits are being
+       replayed onto will be dropped.
 
 include::rev-list-options.adoc[]
 
index 13983dbc56677a6b93af7375aaed12ac433f41bc..2864c2139935e0afb40aea1657d7c55a33537ff0 100644 (file)
--- a/replay.c
+++ b/replay.c
@@ -88,12 +88,12 @@ struct commit *replay_pick_regular_commit(struct repository *repo,
                                          struct merge_result *result)
 {
        struct commit *base, *replayed_base;
-       struct tree *pickme_tree, *base_tree;
+       struct tree *pickme_tree, *base_tree, *replayed_base_tree;
 
        base = pickme->parents->item;
        replayed_base = mapped_commit(replayed_commits, base, onto);
 
-       result->tree = repo_get_commit_tree(repo, replayed_base);
+       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);
 
@@ -103,7 +103,7 @@ struct commit *replay_pick_regular_commit(struct repository *repo,
 
        merge_incore_nonrecursive(merge_opt,
                                  base_tree,
-                                 result->tree,
+                                 replayed_base_tree,
                                  pickme_tree,
                                  result);
 
@@ -111,5 +111,9 @@ struct commit *replay_pick_regular_commit(struct repository *repo,
        merge_opt->ancestor = NULL;
        if (!result->clean)
                return NULL;
+       /* Drop commits that become empty */
+       if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
+           !oideq(&pickme_tree->object.oid, &base_tree->object.oid))
+               return replayed_base;
        return replay_create_commit(repo, result->tree, pickme, replayed_base);
 }
index cf3aacf3551f8ee16803ae2a1ddbb7d9d6935980..b3fb886960053567717fec06144b9d9a896e1fd6 100755 (executable)
@@ -25,6 +25,8 @@ test_expect_success 'setup' '
        git switch -c topic3 &&
        test_commit G &&
        test_commit H &&
+       git switch -c empty &&
+       git commit --allow-empty -m empty &&
        git switch -c topic4 main &&
        test_commit I &&
        test_commit J &&
@@ -106,6 +108,25 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
        test_cmp expect result-bare
 '
 
+test_expect_success 'commits that become empty are dropped' '
+       # Save original branches
+       git for-each-ref --format="update %(refname) %(objectname)" \
+               refs/heads/ >original-branches &&
+       test_when_finished "git update-ref --stdin <original-branches &&
+               rm original-branches" &&
+       # Cherry-pick tip of topic1 ("F"), from the middle of A..empty, to main
+       git replay --advance main topic1^! &&
+
+       # Replay all of A..empty onto main (which includes topic1 & thus F
+       # in the middle)
+       git replay --onto main --branches --ancestry-path=empty ^A \
+               >result &&
+       git log --format="%s%d" L..empty >actual &&
+       test_write_lines >expect \
+               "empty (empty)" "H (topic3)" G "C (topic1)" "F (main)" "M (tag: M)" &&
+       test_cmp expect actual
+'
+
 test_expect_success 'replay on bare repo fails with both --advance and --onto' '
        test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
 '