Skip to content

Commit 41a4aec

Browse files
committed
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. 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 as shown 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. Signed-off-by: Phillip Wood <[email protected]>
1 parent b00f91d commit 41a4aec

File tree

3 files changed

+36
-4
lines changed

3 files changed

+36
-4
lines changed

Documentation/git-replay.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ The default mode can be configured via the `replay.refAction` configuration vari
5959
be passed, but in `--advance <branch>` mode, they should have
6060
a single tip, so that it's clear where <branch> should point
6161
to. See "Specifying Ranges" in linkgit:git-rev-parse[1] and the
62-
"Commit Limiting" options below.
62+
"Commit Limiting" options below. Any commits in the range whose
63+
changes are already present in the branch the commits are being
64+
replayed onto will be dropped.
6365

6466
include::rev-list-options.adoc[]
6567

replay.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "commit.h"
55
#include "environment.h"
66
#include "gettext.h"
7+
#include "hex.h"
78
#include "ident.h"
89
#include "object.h"
910
#include "object-name.h"
@@ -88,12 +89,12 @@ struct commit *replay_pick_regular_commit(struct repository *repo,
8889
struct merge_result *result)
8990
{
9091
struct commit *base, *replayed_base;
91-
struct tree *pickme_tree, *base_tree;
92+
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
9293

9394
base = pickme->parents->item;
9495
replayed_base = mapped_commit(replayed_commits, base, onto);
9596

96-
result->tree = repo_get_commit_tree(repo, replayed_base);
97+
replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
9798
pickme_tree = repo_get_commit_tree(repo, pickme);
9899
base_tree = repo_get_commit_tree(repo, base);
99100

@@ -103,13 +104,17 @@ struct commit *replay_pick_regular_commit(struct repository *repo,
103104

104105
merge_incore_nonrecursive(merge_opt,
105106
base_tree,
106-
result->tree,
107+
replayed_base_tree,
107108
pickme_tree,
108109
result);
109110

110111
free((char*)merge_opt->ancestor);
111112
merge_opt->ancestor = NULL;
112113
if (!result->clean)
113114
return NULL;
115+
/* Drop commits that become empty */
116+
if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
117+
!oideq(&pickme_tree->object.oid, &base_tree->object.oid))
118+
return replayed_base;
114119
return replay_create_commit(repo, result->tree, pickme, replayed_base);
115120
}

t/t3650-replay-basics.sh

Lines changed: 25 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 --only -m empty &&
2830
git switch -c topic4 main &&
2931
test_commit I &&
3032
test_commit J &&
@@ -106,6 +108,29 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' '
106108
test_cmp expect result-bare
107109
'
108110

111+
test_expect_success 'commits that become empty are dropped' '
112+
git replay --ref-action=print --advance main topic1^! >result &&
113+
ONTO=$(cut -f 3 -d " " result) &&
114+
git replay --ref-action=print --onto $ONTO \
115+
--branches --ancestry-path=empty ^A >result &&
116+
# Write the new value of refs/heads/empty to "new-empty" and
117+
# generate a sed script that annotates the output of
118+
# `git log --format="%H %s"` with the updated branches
119+
SCRIPT="$(sed -e "
120+
/empty/{
121+
h
122+
s|^.*empty \([^ ]*\) .*|\1|wnew-empty
123+
g
124+
}
125+
s|^.*/\([^/ ]*\) \([^ ]*\).*|/^\2/s/\\\$/ (\1)/|
126+
\$s|\$|;s/^[^ ]* //|" result)" &&
127+
git log --format="%H %s" --stdin <new-empty >actual.raw &&
128+
sed -e "$SCRIPT" actual.raw >actual &&
129+
test_write_lines >expect \
130+
"empty (empty)" "H (topic3)" G "C (topic1)" F M L B A &&
131+
test_cmp expect actual
132+
'
133+
109134
test_expect_success 'replay on bare repo fails with both --advance and --onto' '
110135
test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare
111136
'

0 commit comments

Comments
 (0)