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
-220
Documentation/git-replay.adoc
+1
-17
index 98e20c1c6e..a32f72aead 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -10,7 +10,7 @@ SYNOPSIS
--------
[verse]
(EXPERIMENTAL!) 'git replay' ([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)
- [--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>
+ [--ref=<ref>] [--ref-action=<mode>] <revision-range>
DESCRIPTION
-----------
@@ -88,23 +88,6 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
+
The default mode can be configured via the `replay.refAction` configuration variable.
---linearize::
- In this mode, each replayed commit is stacked on top of the
- previously replayed one, so all replayed commits are flattened into
- a single linear history.
-+
-When a merge commit is encountered, the behavior of git-rebase(1)'s
-option `--no-rebase-merges` is imitated. All commits in the range
-reachable from the merge commit are replayed into a linear history, and
-the merge commit itself is dropped. A ref that pointed to a merge commit
-is updated to the merge's last replayed ancestor.
-+
-This flattens the `<revision-range>` as a whole. When multiple revision
-ranges are given they are stacked on top of each other into one linear
-history. Each of their refs is updated to point to its position in that
-history. To linearize ranges separately, replay them in separate `git
-replay` invocations.
-
<revision-range>::
Range of commits to replay; see "Specifying Ranges" in
linkgit:git-rev-parse[1]. In `--advance=<branch>` or
builtin/replay.c
+1
-3
index 5e6ff4191a..39e3a86f6c 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -85,7 +85,7 @@ int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
"([--contained] --onto=<newbase> | --advance=<branch> | --revert=<branch>)\n"
- "[--ref=<ref>] [--ref-action=<mode>] [--linearize] <revision-range>"),
+ "[--ref=<ref>] [--ref-action=<mode>] <revision-range>"),
NULL
};
struct option replay_options[] = {
@@ -111,8 +111,6 @@ int cmd_replay(int argc,
N_("mode"),
N_("control ref update behavior (update|print)"),
PARSE_OPT_NONEG),
- OPT_BOOL(0, "linearize", &opts.linearize,
- N_("drop merge commits, replaying only non-merge commits")),
OPT_END()
};
replay.c
+25
-56
index 154fec4a2a..463c900d6c 100644
--- a/replay.c
+++ b/replay.c
@@ -254,9 +254,9 @@ static void set_up_replay_mode(struct repository *repo,
strset_clear(&rinfo.positive_refs);
}
-static struct commit *get_mapped_commit(kh_oid_map_t *replayed_commits,
- struct commit *commit,
- struct commit *fallback)
+static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
+ struct commit *commit,
+ struct commit *fallback)
{
khint_t pos;
if (!commit)
@@ -267,36 +267,27 @@ static struct commit *get_mapped_commit(kh_oid_map_t *replayed_commits,
return kh_value(replayed_commits, pos);
}
-static void put_mapped_commit(kh_oid_map_t *replayed_commits,
- struct commit *commit,
- struct commit *new_commit)
-{
- khint_t pos;
- int ret;
-
- pos = kh_put_oid_map(replayed_commits, commit->object.oid, &ret);
- if (ret == 0)
- BUG("Duplicate rewritten commit: %s",
- oid_to_hex(&commit->object.oid));
-
- kh_value(replayed_commits, pos) = new_commit;
-}
-
static struct commit *pick_regular_commit(struct repository *repo,
struct commit *pickme,
- struct commit *replayed_base,
+ kh_oid_map_t *replayed_commits,
+ struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result,
enum replay_mode mode,
enum replay_empty_commit_action empty)
{
+ struct commit *base, *replayed_base;
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
- if (pickme->parents)
- base_tree = repo_get_commit_tree(repo, pickme->parents->item);
- else
+ if (pickme->parents) {
+ base = pickme->parents->item;
+ base_tree = repo_get_commit_tree(repo, base);
+ } else {
+ base = NULL;
base_tree = lookup_tree(repo, repo->hash_algo->empty_tree);
+ }
+ replayed_base = mapped_commit(replayed_commits, base, onto);
replayed_base_tree = repo_get_commit_tree(repo, replayed_base);
pickme_tree = repo_get_commit_tree(repo, pickme);
@@ -436,46 +427,24 @@ int replay_revisions(struct rev_info *revs,
replayed_commits = kh_init_oid_map();
while ((commit = get_revision(revs))) {
const struct name_decoration *decoration;
+ khint_t pos;
+ int hr;
- if (commit->parents && commit->parents->next) {
- if (!opts->linearize)
- die(_("replaying merge commits is not supported yet!"));
- /*
- * Drop the merge commit: do not pick it, leave
- * `last_commit` unchanged, and fall through to the
- * rest of the loop. As a result:
- * - refs pointing to the merge commit will be updated
- * to `last_commit`.
- * - the next replayed commit uses `last_commit` as its
- * `base`.
- */
- } else {
- /*
- * Decide where to replay this commit onto.
- * If the parent commit was replayed already, the replayed result
- * can be found in `replayed_commits`. Otherwise fall back to `onto`.
- * When reverting, commits are replayed in reverse order and thus
- * its parent isn't replayed yet. Therefore revert commits are
- * always replayed onto `last_commit`.
- * Also when opts->linearize is true, set the base to
- * `last_commit` to create a single linear history.
- */
- struct commit *parent = commit->parents ? commit->parents->item : NULL;
- struct commit *base = get_mapped_commit(replayed_commits, parent, onto);
-
- if (opts->linearize || mode == REPLAY_MODE_REVERT)
- base = last_commit;
-
- last_commit = pick_regular_commit(revs->repo, commit, base,
- &merge_opt, &result,
- mode, opts->empty);
- }
+ if (commit->parents && commit->parents->next)
+ die(_("replaying merge commits is not supported yet!"));
+ last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
+ mode == REPLAY_MODE_REVERT ? last_commit : onto,
+ &merge_opt, &result, mode, opts->empty);
if (!last_commit)
break;
/* Record commit -> last_commit mapping */
- put_mapped_commit(replayed_commits, commit, last_commit);
+ 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 (ref)
replay.h
-5
index 2c71afbfde..491db145e3 100644
--- a/replay.h
+++ b/replay.h
@@ -62,11 +62,6 @@ struct replay_revisions_options {
* Defaults to REPLAY_EMPTY_COMMIT_DROP.
*/
enum replay_empty_commit_action empty;
-
- /*
- * Whether to linearize the commits (i.e. drop merge commits).
- */
- int linearize;
};
/* This struct is used as an out-parameter by `replay_revisions()`. */
t/t3650-replay-basics.sh
+1
-139
index 4d3d442e8a..3353bc4a4d 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -52,19 +52,8 @@ test_expect_success 'setup' '
test_merge P O --no-ff &&
git switch main &&
- git switch --orphan unrelated &&
- test_commit unrelated-root &&
-
git switch -c conflict B &&
- test_commit C.conflict C.t conflict &&
- git branch -D unrelated &&
-
- git switch -c divergent-x main &&
- test_commit X &&
- git switch -c divergent-y main &&
- test_commit Y &&
- git switch divergent-x &&
- test_merge Z divergent-y --no-ff
+ test_commit C.conflict C.t conflict
'
test_expect_success 'setup bare' '
@@ -576,131 +565,4 @@ test_expect_success '--onto with --ref rejects multiple revision ranges' '
test_grep "cannot be used with multiple revision ranges" err
'
-test_expect_success 'replay to rebase merge commit with --linearize' '
- git replay --ref-action=print --linearize \
- --onto main I..topic-with-merge >result &&
-
- test_line_count = 1 result &&
-
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
- test_write_lines O N J M L B A >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'replay to rebase merge commit with --linearize down to the root commit' '
- git replay --ref-action=print --linearize \
- --onto unrelated-root topic-with-merge >result &&
-
- test_line_count = 1 result &&
-
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
- test_write_lines O N J I B A unrelated-root >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'replay to cherry-pick merge commit with --linearize' '
- git replay --ref-action=print --linearize \
- --advance main I..topic-with-merge >result &&
-
- test_line_count = 1 result &&
-
- git log --format=%s $(cut -f 3 -d " " result) >actual &&
- test_write_lines O N J M L B A >expect &&
- test_cmp expect actual &&
-
- printf "update refs/heads/main " >expect &&
- printf "%s " $(cut -f 3 -d " " result) >>expect &&
- git rev-parse main >>expect &&
- test_cmp expect result
-'
-
-test_expect_success 'replay --linearize produces the same patches' '
- git replay --ref-action=print --linearize \
- --onto main I..topic-with-merge >result &&
-
- test_line_count = 1 result &&
- tip=$(cut -f 3 -d " " result) &&
-
- # range-diff does not care about the dropped merge,
- # so the original commits (I..topic-with-merge)
- # and the replayed chain (main..tip) must produce identical patches.
- git range-diff I..topic-with-merge main..$tip >out &&
- test_file_not_empty out &&
- test_grep ! -v "=" out &&
-
- git log --oneline main..$tip >out &&
- test_line_count = 3 out
-'
-
-test_expect_success 'replay with --linearize rebase multiple divergent branches into a single line' '
- git replay --ref-action=print --linearize \
- --onto main ^B topic2 topic3 topic4 >result &&
-
- test_line_count = 3 result &&
- cut -f 3 -d " " result >new-branch-tips &&
-
- >expect &&
- for i in 2 3 4
- do
- printf "update refs/heads/topic$i " >>expect &&
- printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect &&
- git rev-parse topic$i >>expect || return 1
- done &&
-
- test_cmp expect result &&
-
- test_write_lines E D C M L B A >expect2 &&
- test_write_lines H G F E D C M L B A >expect3 &&
- test_write_lines J I H G F E D C M L B A >expect4 &&
-
- for i in 2 3 4
- do
- git log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual &&
- test_cmp expect$i actual || return 1
- done
-'
-
-test_expect_success 'replay with --linearize of a divergent merge keeps both sides' '
- git replay --ref-action=print --linearize \
- --onto main main..divergent-x >result &&
- test_line_count = 1 result &&
- tip=$(cut -f 3 -d " " result) &&
-
- # The merge Z is dropped, but both X and Y are linearized onto main;
- # neither side is lost.
- git log --format=%s main..$tip >actual &&
- test_write_lines Y X >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--linearize with --contained updates contained refs' '
- git replay --ref-action=print --linearize --contained \
- --onto main ^B topic-with-merge >result &&
-
- test_line_count = 2 result &&
-
- git log --format=%s $(head -n 1 result | cut -f 3 -d " ") >actual &&
- test_write_lines J I M L B A >expect &&
- test_cmp expect actual &&
-
- git log --format=%s $(tail -n 1 result | cut -f 3 -d " ") >actual &&
- test_write_lines O N J I M L B A >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'replay --revert with --linearize reverts a range containing a merge' '
- git replay --ref-action=print --revert=divergent-x --linearize \
- main..divergent-x >result &&
- test_line_count = 1 result &&
- tip=$(cut -f 3 -d " " result) &&
-
- git log --format=%s $tip >actual &&
- test_write_lines \
- "Revert \"X\"" "Revert \"Y\"" Z Y X M L B A >expect &&
- test_cmp expect actual &&
-
- test_must_fail git cat-file -e $tip:X.t &&
- test_must_fail git cat-file -e $tip:Y.t
-'
-
test_done