Skip to content

Commit ae260e7

Browse files
dschoTo1ne
authored andcommitted
replay: offer an option to linearize the commit topology
One of the stated goals of git-replay(1) is to allow implementing the git-rebase(1) functionality on the server side. The default mode of git-rebase(1) is to act as if `--no-rebase-merges` was given. This mode drops merge commits instead of replaying them, and linearizes the commit history into a sequence of the regular (single-parent) commits. Add option `--linearize` to git-replay(1) to do the same. Co-authored-by: Toon Claes <toon@iotcl.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Toon Claes <toon@iotcl.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent dbec23a commit ae260e7

5 files changed

Lines changed: 63 additions & 7 deletions

File tree

Documentation/git-replay.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
8888
+
8989
The default mode can be configured via the `replay.refAction` configuration variable.
9090

91+
--linearize::
92+
In this mode, `git replay` imitates `git rebase --no-rebase-merges`,
93+
i.e. it cherry-picks only non-merge commits, each one on top of the
94+
previous one.
95+
9196
<revision-range>::
9297
Range of commits to replay; see "Specifying Ranges" in
9398
linkgit:git-rev-parse[1]. In `--advance=<branch>` or

builtin/replay.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ int cmd_replay(int argc,
111111
N_("mode"),
112112
N_("control ref update behavior (update|print)"),
113113
PARSE_OPT_NONEG),
114+
OPT_BOOL(0, "linearize", &opts.linearize,
115+
N_("ignore merge commits instead of replaying them")),
114116
OPT_END()
115117
};
116118

@@ -132,6 +134,8 @@ int cmd_replay(int argc,
132134
opts.contained, "--contained");
133135
die_for_incompatible_opt2(!!opts.ref, "--ref",
134136
!!opts.contained, "--contained");
137+
die_for_incompatible_opt2(!!opts.revert, "--revert",
138+
opts.linearize, "--linearize");
135139

136140
/* Parse ref action mode from command line or config */
137141
ref_mode = get_ref_action_mode(repo, ref_action);

replay.c

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,16 @@ static struct commit *pick_regular_commit(struct repository *repo,
277277
struct commit *onto,
278278
struct merge_options *merge_opt,
279279
struct merge_result *result,
280+
struct commit *replayed_base,
280281
bool reverse,
281282
enum replay_empty_commit_action empty)
282283
{
283-
struct commit *base, *replayed_base;
284+
struct commit *base;
284285
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
285286

287+
if (replayed_base && reverse)
288+
BUG("Linearizing commits is not supported when replaying in reverse");
289+
286290
if (pickme->parents) {
287291
base = pickme->parents->item;
288292
base_tree = repo_get_commit_tree(repo, base);
@@ -291,7 +295,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
291295
base_tree = lookup_tree(repo, repo->hash_algo->empty_tree);
292296
}
293297

294-
replayed_base = get_mapped_commit(replayed_commits, base, onto);
298+
if (!replayed_base)
299+
replayed_base = get_mapped_commit(replayed_commits, base, onto);
295300
replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
296301
pickme_tree = repo_get_commit_tree(repo, pickme);
297302

@@ -430,12 +435,23 @@ int replay_revisions(struct rev_info *revs,
430435
while ((commit = get_revision(revs))) {
431436
const struct name_decoration *decoration;
432437

433-
if (commit->parents && commit->parents->next)
434-
die(_("replaying merge commits is not supported yet!"));
438+
if (commit->parents && commit->parents->next) {
439+
if (!opts->linearize)
440+
die(_("replaying merge commits is not supported yet!"));
441+
/*
442+
* When linearizing, a merge commit itself is not picked,
443+
* but refs that point to it might need updating.
444+
*/
445+
} else {
446+
struct commit *to_pick = reverse ? last_commit : onto;
447+
last_commit =
448+
pick_regular_commit(revs->repo, commit,
449+
replayed_commits, to_pick,
450+
&merge_opt, &result,
451+
opts->linearize ? last_commit : NULL,
452+
reverse, opts->empty);
453+
}
435454

436-
last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
437-
reverse ? last_commit : onto,
438-
&merge_opt, &result, reverse, opts->empty);
439455
if (!last_commit)
440456
break;
441457

replay.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ struct replay_revisions_options {
6262
* Defaults to REPLAY_EMPTY_COMMIT_DROP.
6363
*/
6464
enum replay_empty_commit_action empty;
65+
66+
/*
67+
* Whether to linearize the commits (i.e. drop merge commits).
68+
*/
69+
int linearize;
6570
};
6671

6772
/* This struct is used as an out-parameter by `replay_revisions()`. */

t/t3650-replay-basics.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,4 +565,30 @@ test_expect_success '--onto with --ref rejects multiple revision ranges' '
565565
test_grep "cannot be used with multiple revision ranges" err
566566
'
567567

568+
test_expect_success 'replay merge commit fails' '
569+
echo "fatal: replaying merge commits is not supported yet!" >expect &&
570+
test_must_fail git replay --ref-action=print --onto main I..P 2>actual &&
571+
test_cmp expect actual
572+
'
573+
574+
test_expect_success 'replay to rebase merge commit with --linearize' '
575+
git replay --ref-action=print --linearize --onto main I..topic-with-merge >result &&
576+
577+
test_line_count = 1 result &&
578+
579+
git log --format=%s $(cut -f 3 -d " " result) >actual &&
580+
test_write_lines O N J M L B A >expect &&
581+
test_cmp expect actual
582+
'
583+
584+
test_expect_success 'replay to rebase merge commit with --linearize down to root commit' '
585+
git replay --ref-action=print --linearize --onto main A..topic-with-merge >result &&
586+
587+
test_line_count = 1 result &&
588+
589+
git log --format=%s $(cut -f 3 -d " " result) >actual &&
590+
test_write_lines O N J I M L B A >expect &&
591+
test_cmp expect actual
592+
'
593+
568594
test_done

0 commit comments

Comments
 (0)