From 2760ee49834953c0860fa5d7983a6af4d27cb6a9 Mon Sep 17 00:00:00 2001 From: Siddharth Asthana Date: Thu, 26 Mar 2026 01:53:52 +0530 Subject: replay: add --revert mode to reverse commit changes Add a `--revert ` mode to git replay that undoes the changes introduced by the specified commits. Like --onto and --advance, --revert is a standalone mode: it takes a branch argument and updates that branch with the newly created revert commits. At GitLab, we need this in Gitaly for reverting commits directly on bare repositories without requiring a working tree checkout. The approach is the same as sequencer.c's do_pick_commit() -- cherry-pick and revert are just the same three-way merge with swapped arguments: - Cherry-pick: merge(ancestor=parent, ours=current, theirs=commit) - Revert: merge(ancestor=commit, ours=current, theirs=parent) We swap the base and pickme trees passed to merge_incore_nonrecursive() to reverse the diff direction. Reverts are processed newest-first (matching git revert behavior) to reduce conflicts by peeling off changes from the top. Each revert builds on the result of the previous one via the last_commit fallback in the main replay loop, rather than relying on the parent-mapping used for cherry-pick. Revert commit messages follow the usual git revert conventions: prefixed with "Revert" (or "Reapply" when reverting a revert), and including "This reverts commit .". The author is set to the current user rather than preserving the original author, matching git revert behavior. Helped-by: Christian Couder Helped-by: Patrick Steinhardt Helped-by: Elijah Newren Helped-by: Phillip Wood Helped-by: Johannes Schindelin Helped-by: Junio C Hamano Helped-by: Toon Claes Signed-off-by: Siddharth Asthana Signed-off-by: Junio C Hamano --- Documentation/git-replay.adoc | 52 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) (limited to 'Documentation') diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index 8d696ce3ab..997097e420 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t SYNOPSIS -------- [verse] -(EXPERIMENTAL!) 'git replay' ([--contained] --onto | --advance ) [--ref-action[=]] +(EXPERIMENTAL!) 'git replay' ([--contained] --onto | --advance | --revert ) [--ref-action[=]] DESCRIPTION ----------- @@ -42,6 +42,25 @@ The history is replayed on top of the and is updated to point at the tip of the resulting history. This is different from `--onto`, which uses the target only as a starting point without updating it. +--revert :: + Starting point at which to create the reverted commits; must be a + branch name. ++ +When `--revert` is specified, the commits in the revision range are reverted +(their changes are undone) and the reverted commits are created on top of +. The is then updated to point at the new commits. This is +the same as running `git revert ` but does not update the +working tree. ++ +The commit messages follow `git revert` conventions: they are prefixed with +"Revert" and include "This reverts commit ." When reverting a commit +whose message starts with "Revert", the new message uses "Reapply" instead. +Unlike cherry-pick which preserves the original author, revert commits use +the current user as the author, matching the behavior of `git revert`. ++ +This option is mutually exclusive with `--onto` and `--advance`. It is also +incompatible with `--contained` (which is a modifier for `--onto` only). + --contained:: Update all branches that point at commits in . Requires `--onto`. @@ -60,10 +79,11 @@ The default mode can be configured via the `replay.refAction` configuration vari :: Range of commits to replay; see "Specifying Ranges" in - linkgit:git-rev-parse[1]. In `--advance ` mode, the - range should have a single tip, so that it's clear to which tip the - advanced should point. Any commits in the range whose - changes are already present in the branch the commits are being + linkgit:git-rev-parse[1]. In `--advance ` or + `--revert ` mode, the range should have a single tip, + so that it's clear to which tip the advanced or reverted + should point. Any commits in the range whose changes + are already present in the branch the commits are being replayed onto will be dropped. :git-replay: 1 @@ -84,9 +104,10 @@ When using `--ref-action=print`, the output is usable as input to update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH} where the number of refs updated depends on the arguments passed and -the shape of the history being replayed. When using `--advance`, the -number of refs updated is always one, but for `--onto`, it can be one -or more (rebasing multiple branches simultaneously is supported). +the shape of the history being replayed. When using `--advance` or +`--revert`, the number of refs updated is always one, but for `--onto`, +it can be one or more (rebasing multiple branches simultaneously is +supported). There is no stderr output on conflicts; see the <> section below. @@ -152,6 +173,21 @@ all commits they have since `base`, playing them on top of `origin/main`. These three branches may have commits on top of `base` that they have in common, but that does not need to be the case. +To revert commits on a branch: + +------------ +$ git replay --revert main topic~2..topic +------------ + +This reverts the last two commits from `topic`, creating revert commits on +top of `main`, and updates `main` to point at the result. This is useful when +commits from `topic` were previously merged or cherry-picked into `main` and +need to be undone. + +NOTE: For reverting an entire merge request as a single commit (rather than +commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE` +which can avoid unnecessary merge conflicts. + GIT --- Part of the linkgit:git[1] suite -- cgit v1.3-5-g45d5