Skip to content

Commit

Permalink
replay: stop assuming replayed branches do not diverge
Browse files Browse the repository at this point in the history
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 <chriscool@tuxfamily.org>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
  • Loading branch information
newren and chriscool committed Oct 10, 2023
1 parent 8e7480a commit 663cbc8
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 10 deletions.
44 changes: 34 additions & 10 deletions builtin/replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,33 @@ static void determine_replay_mode(struct rev_cmdline_info *cmd_info,
strset_clear(&rinfo.positive_refs);
}

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);
if (pos == kh_end(replayed_commits))
return fallback;
return kh_value(replayed_commits, pos);
}

static struct commit *pick_regular_commit(struct commit *pickme,
struct commit *last_commit,
kh_oid_map_t *replayed_commits,
struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result)
{
struct commit *base;
struct commit *base, *replayed_base;
struct tree *pickme_tree, *base_tree;

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

result->tree = repo_get_commit_tree(the_repository, replayed_base);
pickme_tree = repo_get_commit_tree(the_repository, pickme);
base_tree = repo_get_commit_tree(the_repository, base);

merge_opt->branch1 = short_commit_name(last_commit);
merge_opt->branch1 = short_commit_name(replayed_base);
merge_opt->branch2 = short_commit_name(pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);

Expand All @@ -250,7 +263,7 @@ static struct commit *pick_regular_commit(struct commit *pickme,
merge_opt->ancestor = NULL;
if (!result->clean)
return NULL;
return create_commit(result->tree, pickme, last_commit);
return create_commit(result->tree, pickme, replayed_base);
}

int cmd_replay(int argc, const char **argv, const char *prefix)
Expand All @@ -266,6 +279,7 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
struct merge_options merge_opt;
struct merge_result result;
struct strset *update_refs = NULL;
kh_oid_map_t *replayed_commits;
int ret = 0;

const char * const replay_usage[] = {
Expand Down Expand Up @@ -324,21 +338,30 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
init_merge_options(&merge_opt, the_repository);
memset(&result, 0, sizeof(result));
merge_opt.show_rename_progress = 0;

result.tree = repo_get_commit_tree(the_repository, onto);
last_commit = onto;
replayed_commits = kh_init_oid_map();
while ((commit = get_revision(&revs))) {
const struct name_decoration *decoration;
khint_t pos;
int hr;

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

last_commit = pick_regular_commit(commit, last_commit, &merge_opt, &result);
last_commit = pick_regular_commit(commit, replayed_commits, onto,
&merge_opt, &result);
if (!last_commit)
break;

/* Record commit -> last_commit mapping */
pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
if (hr == 0)
BUG("Duplicate rewritten commit: %s\n",
oid_to_hex(&commit->object.oid));
kh_value(replayed_commits, pos) = last_commit;

/* Update any necessary branches */
if (advance_name)
continue;
Expand Down Expand Up @@ -367,13 +390,14 @@ int cmd_replay(int argc, const char **argv, const char *prefix)
}

merge_finalize(&merge_opt, &result);
ret = result.clean;

cleanup:
kh_destroy_oid_map(replayed_commits);
if (update_refs) {
strset_clear(update_refs);
free(update_refs);
}
ret = result.clean;

cleanup:
release_revisions(&revs);

/* Return */
Expand Down
52 changes: 52 additions & 0 deletions t/t3650-replay-basics.sh
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,56 @@ test_expect_success 'using replay on bare repo to also rebase a contained branch
test_cmp expect result-bare
'

test_expect_success 'using replay to rebase multiple divergent branches' '
git replay --onto main ^topic1 topic2 topic4 >result &&
test_line_count = 2 result &&
cut -f 3 -d " " result >new-branch-tips &&
git log --format=%s $(head -n 1 new-branch-tips) >actual &&
test_write_lines E D M L B A >expect &&
test_cmp expect actual &&
git log --format=%s $(tail -n 1 new-branch-tips) >actual &&
test_write_lines J I M L B A >expect &&
test_cmp expect actual &&
printf "update refs/heads/topic2 " >expect &&
printf "%s " $(head -n 1 new-branch-tips) >>expect &&
git rev-parse topic2 >>expect &&
printf "update refs/heads/topic4 " >>expect &&
printf "%s " $(tail -n 1 new-branch-tips) >>expect &&
git rev-parse topic4 >>expect &&
test_cmp expect result
'

test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' '
git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result &&
test_line_count = 4 result &&
cut -f 3 -d " " result >new-branch-tips &&
>expect &&
for i in 2 1 3 4
do
printf "update refs/heads/topic$i " >>expect &&
printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect &&
git -C bare rev-parse topic$i >>expect || return 1
done &&
test_cmp expect result &&
test_write_lines F C M L B A >expect1 &&
test_write_lines E D C M L B A >expect2 &&
test_write_lines H G F C M L B A >expect3 &&
test_write_lines J I M L B A >expect4 &&
for i in 1 2 3 4
do
git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual &&
test_cmp expect$i actual || return 1
done
'

test_done

0 comments on commit 663cbc8

Please sign in to comment.