Revert "Merge branch 'tc/replay-linearize' into next"
This reverts commit 371c2e9c3b9ad9a668ee0f5f47f81d479c1afac1, reversing changes made to fd2b979b73af2a6f37990768e06ca970519d4355, to re-queue the v8 iteration of the topic.
Junio C Hamano committed
Jul 28, 2026 at 11:26 UTC
b3b5e47172ef2180679b8b4a15aff66f98e8697d
5 files changed
+28
-221
Documentation/git-replay.adoc
+1
-18
@@ -10,7 +10,7 @@ SYNOPSIS
10
--------
11
[verse]
12
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
13
- [--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>
13
+ [--ref=<ref>] [--ref-action=<mode>] <revision-range>
14
15
DESCRIPTION
16
-----------
@@ -88,23 +88,6 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
88
+
89
The default mode can be configured via the `replay.refAction` configuration variable.
90
91
---linearize::
92
- In this mode, each replayed commit is stacked on top of the
93
- previously replayed one, so all replayed commits are flattened into
94
- a single linear history.
95
-+
96
-When a merge commit is encountered, the behavior of git-rebase(1)'s
97
-option `--no-rebase-merges` is imitated. All commits in the range
98
-reachable from the merge commit are replayed into a linear history, and
99
-the merge commit itself is dropped. A ref that pointed to a merge commit
100
-is updated to the merge's last replayed ancestor.
101
-+
102
-This flattens the `<revision-range>` as a whole. When multiple revision
103
-ranges are given they are stacked on top of each other into one linear
104
-history. Each of their refs is updated to point to its position in that
105
-history. To linearize ranges separately, replay them in separate `git
106
-replay` invocations.
107
-
91
<revision-range>::
92
Range of commits to replay; see "Specifying Ranges" in
93
linkgit:git-rev-parse[1]. In `--advance=<branch>` or
builtin/replay.c
+1
-3
@@ -85,7 +85,7 @@ int cmd_replay(int argc,
85
const char *const replay_usage[] = {
86
N_("(EXPERIMENTAL!) git replay "
87
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
88
- "[--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>"),
88
+ "[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
89
NULL
90
};
91
struct option replay_options[] = {
@@ -111,8 +111,6 @@ int cmd_replay(int argc,
111
N_("mode"),
112
N_("control ref update behavior (update|print)"),
113
PARSE_OPT_NONEG),
114
- OPT_BOOL(0, "linearize", &opts.linearize,
115
- N_("drop merge commits, replaying only non-merge commits")),
114
OPT_END()
115
};
116
replay.c
+25
-56
@@ -254,9 +254,9 @@ static void set_up_replay_mode(struct repository *repo,
254
strset_clear(&rinfo.positive_refs);
255
}
256
257
-static struct commit *get_mapped_commit(kh_oid_map_t *replayed_commits,
258
- struct commit *commit,
259
- struct commit *fallback)
257
+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
258
+ struct commit *commit,
259
+ struct commit *fallback)
260
{
261
khint_t pos;
262
if (!commit)
@@ -267,36 +267,27 @@ static struct commit *get_mapped_commit(kh_oid_map_t *replayed_commits,
267
return kh_value(replayed_commits, pos);
268
}
269
270
-static void put_mapped_commit(kh_oid_map_t *replayed_commits,
271
- struct commit *commit,
272
- struct commit *new_commit)
273
-{
274
- khint_t pos;
275
- int ret;
276
-
277
- pos = kh_put_oid_map(replayed_commits, commit->object.oid, &ret);
278
- if (ret == 0)
279
- BUG("Duplicate rewritten commit: %s",
280
- oid_to_hex(&commit->object.oid));
281
-
282
- kh_value(replayed_commits, pos) = new_commit;
283
-}
284
-
270
static struct commit *pick_regular_commit(struct repository *repo,
271
struct commit *pickme,
287
- struct commit *replayed_base,
272
+ kh_oid_map_t *replayed_commits,
273
+ struct commit *onto,
274
struct merge_options *merge_opt,
275
struct merge_result *result,
276
enum replay_mode mode,
277
enum replay_empty_commit_action empty)
278
{
279
+ struct commit *base, *replayed_base;
280
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
281
295
- if (pickme->parents)
296
- base_tree = repo_get_commit_tree(repo, pickme->parents->item);
297
- else
282
+ if (pickme->parents) {
283
+ base = pickme->parents->item;
284
+ base_tree = repo_get_commit_tree(repo, base);
285
+ } else {
286
+ base = NULL;
287
base_tree = lookup_tree(repo, repo->hash_algo->empty_tree);
288
+ }
289
290
+ replayed_base = mapped_commit(replayed_commits, base, onto);
291
replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
292
pickme_tree = repo_get_commit_tree(repo, pickme);
293
@@ -436,46 +427,24 @@ int replay_revisions(struct rev_info *revs,
427
replayed_commits = kh_init_oid_map();
428
while ((commit = get_revision(revs))) {
429
const struct name_decoration *decoration;
430
+ khint_t pos;
431
+ int hr;
432
440
- if (commit->parents && commit->parents->next) {
441
- if (!opts->linearize)
442
- die(_("replaying merge commits is not supported yet!"));
443
- /*
444
- * Drop the merge commit: do not pick it, leave
445
- * `last_commit` unchanged, and fall through to the
446
- * rest of the loop. As a result:
447
- * - refs pointing to the merge commit will be updated
448
- * to `last_commit`.
449
- * - the next replayed commit uses `last_commit` as its
450
- * `base`.
451
- */
452
- } else {
453
- /*
454
- * Decide where to replay this commit onto.
455
- * If the parent commit was replayed already, the replayed result
456
- * can be found in `replayed_commits`. Otherwise fall back to `onto`.
457
- * When reverting, commits are replayed in reverse order and thus
458
- * its parent isn't replayed yet. Therefore revert commits are
459
- * always replayed onto `last_commit`.
460
- * Also when opts->linearize is true, set the base to
461
- * `last_commit` to create a single linear history.
462
- */
463
- struct commit *parent = commit->parents ? commit->parents->item : NULL;
464
- struct commit *base = get_mapped_commit(replayed_commits, parent, onto);
465
-
466
- if (opts->linearize || mode == REPLAY_MODE_REVERT)
467
- base = last_commit;
468
-
469
- last_commit = pick_regular_commit(revs->repo, commit, base,
470
- &merge_opt, &result,
471
- mode, opts->empty);
472
- }
433
+ if (commit->parents && commit->parents->next)
434
+ die(_("replaying merge commits is not supported yet!"));
435
436
+ last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
437
+ mode == REPLAY_MODE_REVERT ? last_commit : onto,
438
+ &merge_opt, &result, mode, opts->empty);
439
if (!last_commit)
440
break;
441
442
/* Record commit -> last_commit mapping */
478
- put_mapped_commit(replayed_commits, commit, last_commit);
443
+ pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
444
+ if (hr == 0)
445
+ BUG("Duplicate rewritten commit: %s\n",
446
+ oid_to_hex(&commit->object.oid));
447
+ kh_value(replayed_commits, pos) = last_commit;
448
449
/* Update any necessary branches */
450
if (ref)
replay.h
-5
@@ -62,11 +62,6 @@ struct replay_revisions_options {
62
* Defaults to REPLAY_EMPTY_COMMIT_DROP.
63
*/
64
enum replay_empty_commit_action empty;
65
-
66
- /*
67
- * Whether to linearize the commits (i.e. drop merge commits).
68
- */
69
- int linearize;
65
};
66
67
/* This struct is used as an out-parameter by `replay_revisions()`. */
t/t3650-replay-basics.sh
+1
-139
@@ -52,19 +52,8 @@ test_expect_success 'setup' '
52
test_merge P O --no-ff &&
53
git switch main &&
54
55
- git switch --orphan unrelated &&
56
- test_commit unrelated-root &&
57
-
55
git switch -c conflict B &&
59
- test_commit C.conflict C.t conflict &&
60
- git branch -D unrelated &&
61
-
62
- git switch -c divergent-x main &&
63
- test_commit X &&
64
- git switch -c divergent-y main &&
65
- test_commit Y &&
66
- git switch divergent-x &&
67
- test_merge Z divergent-y --no-ff
56
+ test_commit C.conflict C.t conflict
57
'
58
59
test_expect_success 'setup bare' '
@@ -576,131 +565,4 @@ test_expect_success '--onto with --ref rejects multiple revision ranges' '
565
test_grep "cannot be used with multiple revision ranges" err
566
'
567
579
-test_expect_success 'replay to rebase merge commit with --linearize' '
580
- git replay --ref-action=print --linearize \
581
- --onto main I..topic-with-merge >result &&
582
-
583
- test_line_count = 1 result &&
584
-
585
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
586
- test_write_lines O N J M L B A >expect &&
587
- test_cmp expect actual
588
-'
589
-
590
-test_expect_success 'replay to rebase merge commit with --linearize down to the root commit' '
591
- git replay --ref-action=print --linearize \
592
- --onto unrelated-root topic-with-merge >result &&
593
-
594
- test_line_count = 1 result &&
595
-
596
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
597
- test_write_lines O N J I B A unrelated-root >expect &&
598
- test_cmp expect actual
599
-'
600
-
601
-test_expect_success 'replay to cherry-pick merge commit with --linearize' '
602
- git replay --ref-action=print --linearize \
603
- --advance main I..topic-with-merge >result &&
604
-
605
- test_line_count = 1 result &&
606
-
607
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
608
- test_write_lines O N J M L B A >expect &&
609
- test_cmp expect actual &&
610
-
611
- printf "update refs/heads/main " >expect &&
612
- printf "%s " $(cut -f 3 -d " " result) >>expect &&
613
- git rev-parse main >>expect &&
614
- test_cmp expect result
615
-'
616
-
617
-test_expect_success 'replay --linearize produces the same patches' '
618
- git replay --ref-action=print --linearize \
619
- --onto main I..topic-with-merge >result &&
620
-
621
- test_line_count = 1 result &&
622
- tip=$(cut -f 3 -d " " result) &&
623
-
624
- # range-diff does not care about the dropped merge,
625
- # so the original commits (I..topic-with-merge)
626
- # and the replayed chain (main..tip) must produce identical patches.
627
- git range-diff I..topic-with-merge main..$tip >out &&
628
- test_file_not_empty out &&
629
- test_grep ! -v "=" out &&
630
-
631
- git log --oneline main..$tip >out &&
632
- test_line_count = 3 out
633
-'
634
-
635
-test_expect_success 'replay with --linearize rebase multiple divergent branches into a single line' '
636
- git replay --ref-action=print --linearize \
637
- --onto main ^B topic2 topic3 topic4 >result &&
638
-
639
- test_line_count = 3 result &&
640
- cut -f 3 -d " " result >new-branch-tips &&
641
-
642
- >expect &&
643
- for i in 2 3 4
644
- do
645
- printf "update refs/heads/topic$i " >>expect &&
646
- printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect &&
647
- git rev-parse topic$i >>expect || return 1
648
- done &&
649
-
650
- test_cmp expect result &&
651
-
652
- test_write_lines E D C M L B A >expect2 &&
653
- test_write_lines H G F E D C M L B A >expect3 &&
654
- test_write_lines J I H G F E D C M L B A >expect4 &&
655
-
656
- for i in 2 3 4
657
- do
658
- git log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual &&
659
- test_cmp expect$i actual || return 1
660
- done
661
-'
662
-
663
-test_expect_success 'replay with --linearize of a divergent merge keeps both sides' '
664
- git replay --ref-action=print --linearize \
665
- --onto main main..divergent-x >result &&
666
- test_line_count = 1 result &&
667
- tip=$(cut -f 3 -d " " result) &&
668
-
669
- # The merge Z is dropped, but both X and Y are linearized onto main;
670
- # neither side is lost.
671
- git log --format=%s main..$tip >actual &&
672
- test_write_lines Y X >expect &&
673
- test_cmp expect actual
674
-'
675
-
676
-test_expect_success '--linearize with --contained updates contained refs' '
677
- git replay --ref-action=print --linearize --contained \
678
- --onto main ^B topic-with-merge >result &&
679
-
680
- test_line_count = 2 result &&
681
-
682
- git log --format=%s $(head -n 1 result | cut -f 3 -d " ") >actual &&
683
- test_write_lines J I M L B A >expect &&
684
- test_cmp expect actual &&
685
-
686
- git log --format=%s $(tail -n 1 result | cut -f 3 -d " ") >actual &&
687
- test_write_lines O N J I M L B A >expect &&
688
- test_cmp expect actual
689
-'
690
-
691
-test_expect_success 'replay --revert with --linearize reverts a range containing a merge' '
692
- git replay --ref-action=print --revert=divergent-x --linearize \
693
- main..divergent-x >result &&
694
- test_line_count = 1 result &&
695
- tip=$(cut -f 3 -d " " result) &&
696
-
697
- git log --format=%s $tip >actual &&
698
- test_write_lines \
699
- "Revert \"X\"" "Revert \"Y\"" Z Y X M L B A >expect &&
700
- test_cmp expect actual &&
701
-
702
- test_must_fail git cat-file -e $tip:X.t &&
703
- test_must_fail git cat-file -e $tip:Y.t
704
-'
705
-
568
test_done