[PATCH v2 0/3] Teach git-replay(1) to linearize merge commits
From: Toon Claes <hidden>
Date: 2026-06-10 14:49:26
As an alternative to dscho's patch series to replay merges[1], add option to git-replay(1) to linearize merges. This mimics wath git-rebase(1) does too with --no-rebase-merges (the default). The first two patches do some refactoring. The third patch implements the actual change. I was kindly helped by dscho to implement this change. The --linearize option is only added to git-replay(1) and not to git-history(1) because in my opinion doesn't make much sense to do so, but I'm happy to hear if anyone disagrees. This series might conflict with Kristoffer's series to make documentation changes[2], but should be trivial to resolve. And I don't think there's a conflict with Patrick's series on adding "drop" to git-history(1)[3]. dscho's series to replay merges[1] need a bit of rework to fit on top of this, but I'm happy to help figuring that out. We've been discussing to either name the option --flatten or --linearize, but I've decided on "linearize" because the documentation of git-rebase(1) also mentions "linearize". [1]: [ref] [2]: [ref] [3]: [ref] Signed-off-by: Toon Claes <redacted> --- Changes in v2: - Restructured the conditions to detect merge commits and added a line of comment why the loop continues. - Rewrote tests to use the history from the setup step and added a few test cases. - Re-added Johannes's Signed-off-by trailer. Johannes gave me the patches with this trailer, and if I understand correctly, I can keep it. Please let me know if that wrong. - Link to v1: https://patch.msgid.link/20260608-toon-git-replay-drop-merges-v1-0-e3ee71fce7b4@iotcl.com --- Johannes Schindelin (1): replay: offer an option to linearize the commit topology Toon Claes (2): replay: refactor enum replay_mode into a bool replay: add helper to put entry into mapped_commits Documentation/git-replay.adoc | 5 ++ builtin/replay.c | 4 ++ replay.c | 114 ++++++++++++++++++++++++------------------ replay.h | 5 ++ t/t3650-replay-basics.sh | 26 ++++++++++ 5 files changed, 105 insertions(+), 49 deletions(-) Range-diff versus v1: 1: 7f3bc6f425 ! 1: 0975b142e3 replay: refactor enum replay_mode into a bool @@ Commit message - The value `REPLAY_MODE_REVERT` is used when option `--revert` is passed to git-replay(1). When using this value the commits are - possible in reverse order and the inverse of the changes are applied. + processed in reverse order and the inverse of the changes are + applied. - The value `REPLAY_MODE_PICK` is used when either option `--onto` or - `--advance` is used. In both cases the commits are pocessed in normal - order, and the changes are applied as-is. + `--advance` is used. In both cases the commits are processed in + normal order, and the changes are applied as-is. Since there are only two possible values of this enum, simplify the code - by converting the enum into a bool. This avoid adding code paths that - check for invalid vaues of the enum, and shortens code where the value + by converting the enum into a bool. This avoids adding code paths that + check for invalid values of the enum, and shortens code where the value is checked with a ternary operator. Signed-off-by: Toon Claes [off-list ref] 2: 0868871c78 ! 2: db88193624 replay: add helper to put entry into mapped_commits @@ Commit message replay: add helper to put entry into mapped_commits The function replay_revisions() in replay.c is rather lengthy. Extract - the logic to put commit entry into mapped_commits into a helper - function. + the logic to put a commit entry into mapped_commits into a helper + function put_mapped_commit(). + + While at it, rename mapped_commit() to get_mapped_commit() to pair with + this new function. Signed-off-by: Toon Claes [off-list ref] 3: a432ae753b ! 3: d0c220ec8e replay: offer an option to linearize the commit topology @@ Commit message 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 - linearized the commit history into a sequence of the + linearizes the commit history into a sequence of the regular (single-parent) commits. - Add option `--linearize` to git-replay(1) do the same. + Add option `--linearize` to git-replay(1) to do the same. Co-authored-by: Toon Claes [off-list ref] + Signed-off-by: Johannes Schindelin [off-list ref] + Signed-off-by: Toon Claes [off-list ref] ## Documentation/git-replay.adoc ## @@ Documentation/git-replay.adoc: incompatible with `--contained` (which is a modifier for `--onto` only). @@ replay.c: int replay_revisions(struct rev_info *revs, const struct name_decoration *decoration; - if (commit->parents && commit->parents->next) -+ if (opts->linearize && (!commit->parents || commit->parents->next)) -+ ; /* map current commit to the same as the previous commit */ -+ else if (commit->parents && commit->parents->next) - die(_("replaying merge commits is not supported yet!")); -+ else { +- die(_("replaying merge commits is not supported yet!")); ++ if (commit->parents && commit->parents->next) { ++ if (!opts->linearize) ++ die(_("replaying merge commits is not supported yet!")); ++ /* ++ * When linearizing, a merge commit itself is not picked, ++ * but refs that point to it might need updating. ++ */ ++ } else { + struct commit *to_pick = reverse ? last_commit : onto; + last_commit = + pick_regular_commit(revs->repo, commit, @@ t/t3650-replay-basics.sh: test_expect_success '--onto with --ref rejects multipl test_grep "cannot be used with multiple revision ranges" err ' -+test_expect_success 'linearize the commit topology' ' -+ test_tick && -+ N=$(git commit-tree -m N -p L -p I L:) && -+ N=$(git commit-tree -m N-child -p $N L:) && -+ git update-ref refs/heads/N $N && ++test_expect_success 'replay merge commit fails' ' ++ echo "fatal: replaying merge commits is not supported yet!" >expect && ++ test_must_fail git replay --ref-action=print --onto main I..P 2>actual && ++ test_cmp expect actual ++' ++ ++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 ++' + -+ git replay --ref-action=print --linearize \ -+ --onto A B..refs/heads/N >out && ++test_expect_success 'replay to rebase merge commit with --linearize down to root commit' ' ++ git replay --ref-action=print --linearize --onto main A..topic-with-merge >result && + -+ test_line_count = 1 out && -+ read N1 N2 N3 N4 <out && ++ test_line_count = 1 result && + -+ cat >expect <<-EOF && -+ * N-child -+ * I -+ * L -+ o A -+ EOF -+ git log --format=%s --graph --boundary A...$N3 >actual && ++ git log --format=%s $(cut -f 3 -d " " result) >actual && ++ test_write_lines O N J I M L B A >expect && + test_cmp expect actual +' + --- base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0 change-id: 20260604-toon-git-replay-drop-merges-807fa008d395