Skip to content

Commit 2f8b312

Browse files
phillipwoodgitster
authored andcommitted
replay: drop commits that become empty
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 <[email protected]> Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 176e463 commit 2f8b312

File tree

3 files changed

+31
-4
lines changed

3 files changed

+31
-4
lines changed

Documentation/git-replay.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ The default mode can be configured via the `replay.refAction` configuration vari
6262
Range of commits to replay; see "Specifying Ranges" in
6363
linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
6464
range should have a single tip, so that it's clear to which tip the
65-
advanced <branch> should point.
65+
advanced <branch> should point. Any commits in the range whose
66+
changes are already present in the branch the commits are being
67+
replayed onto will be dropped.
6668

6769
include::rev-list-options.adoc[]
6870

replay.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,12 @@ static struct commit *pick_regular_commit(struct repository *repo,
211211
struct merge_result *result)
212212
{
213213
struct commit *base, *replayed_base;
214-
struct tree *pickme_tree, *base_tree;
214+
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
215215

216216
base = pickme->parents->item;
217217
replayed_base = mapped_commit(replayed_commits, base, onto);
218218

219-
result->tree = repo_get_commit_tree(repo, replayed_base);
219+
replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
220220
pickme_tree = repo_get_commit_tree(repo, pickme);
221221
base_tree = repo_get_commit_tree(repo, base);
222222

@@ -226,14 +226,18 @@ static struct commit *pick_regular_commit(struct repository *repo,
226226

227227
merge_incore_nonrecursive(merge_opt,
228228
base_tree,
229-
result->tree,
229+
replayed_base_tree,
230230
pickme_tree,
231231
result);
232232

233233
free((char*)merge_opt->ancestor);
234234
merge_opt->ancestor = NULL;
235235
if (!result->clean)
236236
return NULL;
237+
/* Drop commits that become empty */
238+
if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
239+
!oideq(&pickme_tree->object.oid, &base_tree->object.oid))
240+
return replayed_base;
237241
return create_commit(repo, result->tree, pickme, replayed_base);
238242
}
239243

t/t3650-replay-basics.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ test_expect_success 'setup' '
2525
git switch -c topic3 &&
2626
test_commit G &&
2727
test_commit H &&
28+
git switch -c empty &&
29+
git commit --allow-empty -m empty &&
2830
git switch -c topic4 main &&
2931
test_commit I &&
3032
test_commit J &&
@@ -160,6 +162,25 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
160162
test_cmp expect result-bare
161163
'
162164

165+
test_expect_success 'commits that become empty are dropped' '
166+
# Save original branches
167+
git for-each-ref --format="update %(refname) %(objectname)" \
168+
refs/heads/ >original-branches &&
169+
test_when_finished "git update-ref --stdin <original-branches &&
170+
rm original-branches" &&
171+
# Cherry-pick tip of topic1 ("F"), from the middle of A..empty, to main
172+
git replay --advance main topic1^! &&
173+
174+
# Replay all of A..empty onto main (which includes topic1 & thus F
175+
# in the middle)
176+
git replay --onto main --branches --ancestry-path=empty ^A \
177+
>result &&
178+
git log --format="%s%d" L..empty >actual &&
179+
test_write_lines >expect \
180+
"empty (empty)" "H (topic3)" G "C (topic1)" "F (main)" "M (tag: M)" &&
181+
test_cmp expect actual
182+
'
183+
163184
test_expect_success 'replay on bare repo fails with both --advance and --onto' '
164185
test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
165186
'

0 commit comments

Comments
 (0)