Skip to content

Commit e928c11

Browse files
newrenchriscool
authored andcommitted
replay: stop assuming replayed branches do not diverge
The replay command is able to replay multiple branches but when some of them are based on other replayed branches, their commit should be replayed onto already replayed commits. For this purpose, let's store the replayed commit and its original commit in a key value store, so that we can easily find and reuse a replayed commit instead of the original one. Co-authored-by: Christian Couder <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Christian Couder <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c461113 commit e928c11

File tree

2 files changed

+86
-10
lines changed

2 files changed

+86
-10
lines changed

builtin/replay.c

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,
223223
strset_clear(&rinfo.positive_refs);
224224
}
225225

226+
static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
227+
struct commit *commit,
228+
struct commit *fallback)
229+
{
230+
khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
231+
if (pos == kh_end(replayed_commits))
232+
return fallback;
233+
return kh_value(replayed_commits, pos);
234+
}
235+
226236
static struct commit *pick_regular_commit(struct commit *pickme,
227-
struct commit *last_commit,
237+
kh_oid_map_t *replayed_commits,
238+
struct commit *onto,
228239
struct merge_options *merge_opt,
229240
struct merge_result *result)
230241
{
231-
struct commit *base;
242+
struct commit *base, *replayed_base;
232243
struct tree *pickme_tree, *base_tree;
233244

234245
base = pickme->parents->item;
246+
replayed_base = mapped_commit(replayed_commits, base, onto);
235247

248+
result->tree = repo_get_commit_tree(the_repository, replayed_base);
236249
pickme_tree = repo_get_commit_tree(the_repository, pickme);
237250
base_tree = repo_get_commit_tree(the_repository, base);
238251

239-
merge_opt->branch1 = short_commit_name(last_commit);
252+
merge_opt->branch1 = short_commit_name(replayed_base);
240253
merge_opt->branch2 = short_commit_name(pickme);
241254
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
242255

@@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,
250263
merge_opt->ancestor = NULL;
251264
if (!result->clean)
252265
return NULL;
253-
return create_commit(result->tree, pickme, last_commit);
266+
return create_commit(result->tree, pickme, replayed_base);
254267
}
255268

256269
int cmd_replay(int argc, const char **argv, const char *prefix)
@@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
266279
struct merge_options merge_opt;
267280
struct merge_result result;
268281
struct strset *update_refs = NULL;
282+
kh_oid_map_t *replayed_commits;
269283
int ret = 0;
270284

271285
const char * const replay_usage[] = {
@@ -363,21 +377,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
363377
init_merge_options(&merge_opt, the_repository);
364378
memset(&result, 0, sizeof(result));
365379
merge_opt.show_rename_progress = 0;
366-
367-
result.tree = repo_get_commit_tree(the_repository, onto);
368380
last_commit = onto;
381+
replayed_commits = kh_init_oid_map();
369382
while ((commit = get_revision(&revs))) {
370383
const struct name_decoration *decoration;
384+
khint_t pos;
385+
int hr;
371386

372387
if (!commit->parents)
373388
die(_("replaying down to root commit is not supported yet!"));
374389
if (commit->parents->next)
375390
die(_("replaying merge commits is not supported yet!"));
376391

377-
last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);
392+
last_commit = pick_regular_commit(commit, replayed_commits, onto,
393+
&merge_opt, &result);
378394
if (!last_commit)
379395
break;
380396

397+
/* Record commit -> last_commit mapping */
398+
pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
399+
if (hr == 0)
400+
BUG("Duplicate rewritten commit: %s\n",
401+
oid_to_hex(&commit->object.oid));
402+
kh_value(replayed_commits, pos) = last_commit;
403+
381404
/* Update any necessary branches */
382405
if (advance_name)
383406
continue;
@@ -406,13 +429,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
406429
}
407430

408431
merge_finalize(&merge_opt, &result);
409-
ret = result.clean;
410-
411-
cleanup:
432+
kh_destroy_oid_map(replayed_commits);
412433
if (update_refs) {
413434
strset_clear(update_refs);
414435
free(update_refs);
415436
}
437+
ret = result.clean;
438+
439+
cleanup:
416440
release_revisions(&revs);
417441

418442
/* Return */

t/t3650-replay-basics.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch
143143
test_cmp expect result-bare
144144
'
145145

146+
test_expect_success 'using replay to rebase multiple divergent branches' '
147+
git replay --onto main ^topic1 topic2 topic4 >result &&
148+
149+
test_line_count = 2 result &&
150+
cut -f 3 -d " " result >new-branch-tips &&
151+
152+
git log --format=%s $(head -n 1 new-branch-tips) >actual &&
153+
test_write_lines E D M L B A >expect &&
154+
test_cmp expect actual &&
155+
156+
git log --format=%s $(tail -n 1 new-branch-tips) >actual &&
157+
test_write_lines J I M L B A >expect &&
158+
test_cmp expect actual &&
159+
160+
printf "update refs/heads/topic2 " >expect &&
161+
printf "%s " $(head -n 1 new-branch-tips) >>expect &&
162+
git rev-parse topic2 >>expect &&
163+
printf "update refs/heads/topic4 " >>expect &&
164+
printf "%s " $(tail -n 1 new-branch-tips) >>expect &&
165+
git rev-parse topic4 >>expect &&
166+
167+
test_cmp expect result
168+
'
169+
170+
test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '
171+
git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&
172+
173+
test_line_count = 4 result &&
174+
cut -f 3 -d " " result >new-branch-tips &&
175+
176+
>expect &&
177+
for i in 2 1 3 4
178+
do
179+
printf "update refs/heads/topic$i " >>expect &&
180+
printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect &&
181+
git -C bare rev-parse topic$i >>expect || return 1
182+
done &&
183+
184+
test_cmp expect result &&
185+
186+
test_write_lines F C M L B A >expect1 &&
187+
test_write_lines E D C M L B A >expect2 &&
188+
test_write_lines H G F C M L B A >expect3 &&
189+
test_write_lines J I M L B A >expect4 &&
190+
191+
for i in 1 2 3 4
192+
do
193+
git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual &&
194+
test_cmp expect$i actual || return 1
195+
done
196+
'
197+
146198
test_done

0 commit comments

Comments
 (0)