Subject: [PATCH v6 0/2] replay: add --revert mode to reverse commit changes

From: Siddharth Asthana <[email-masked]>
Date: Thu, 26 Mar 2026 01:53:50 +0530

Hi,

git replay currently supports cherry-picking (--advance) and rebasing
(--onto), but not reverting. We need this at GitLab for Gitaly to
reverse commits directly on bare repositories without a checkout.

The approach is the same as sequencer.c -- cherry-pick and revert are
just the same three-way merge with swapped arguments. We swap the base
and pickme trees passed to merge_incore_nonrecursive() to reverse the
diff direction.

Patch 1 extracts the full revert message formatting logic into a new
sequencer_format_revert_message() function that handles everything in
one shared function rather than just the header. refer_to_commit() is
updated to take a struct repository and a bool instead of replay_opts
so it works outside the sequencer.

Patch 2 adds --revert <branch> as a standalone mode. Reverts are
processed newest-first (matching git revert) to reduce conflicts by
peeling off changes from the top.

The series is based on top of d181b9354c (The 13th batch, 2026-03-07).

CI: https://gitlab.com/gitlab-org/git/-/pipelines/2408816732

Changes in v6:
- Moved desired_reverse declaration to the top of cmd_replay() with
  other variable declarations to fix -Wdeclaration-after-statement
- Updated <revision-range> doc to mention --revert alongside --advance
- Link to v5: https://lore.kernel.org/git/[email-masked]/
- Link to v4: https://lore.kernel.org/git/[email-masked]/
- Link to v3: https://public-inbox.org/git/[email-masked]/
- Link to v2: https://public-inbox.org/git/[email-masked]/
- Link to v1: https://public-inbox.org/git/[email-masked]/

Thanks,
Siddharth

---
Siddharth Asthana (2):
  sequencer: extract revert message formatting into shared function
  replay: add --revert mode to reverse commit changes

 Documentation/git-replay.adoc |  52 +++++++++--
 builtin/replay.c              |  36 ++++++--
 replay.c                      | 161 +++++++++++++++++++++++++---------
 replay.h                      |  11 ++-
 sequencer.c                   |  78 +++++++++-------
 sequencer.h                   |  13 +++
 t/t3650-replay-basics.sh      | 111 +++++++++++++++++++++--
 7 files changed, 361 insertions(+), 101 deletions(-)

Range-diff versus v5:

1:  6bd2ce4515 = 1:  6bd2ce4515 sequencer: extract revert message formatting into shared function
2:  9fd92497b9 ! 2:  41fe4861a2 replay: add --revert mode to reverse commit changes
    @@ Documentation/git-replay.adoc: The history is replayed on top of the <branch> an
      --contained::
      	Update all branches that point at commits in
      	<revision-range>. Requires `--onto`.
    +@@ Documentation/git-replay.adoc: The default mode can be configured via the `replay.refAction` configuration vari
    + 
    + <revision-range>::
    + 	Range of commits to replay; see "Specifying Ranges" in
    +-	linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
    +-	range should have a single tip, so that it's clear to which tip the
    +-	advanced <branch> 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 <branch>` or
    ++	`--revert <branch>` mode, the range should have a single tip,
    ++	so that it's clear to which tip the advanced or reverted
    ++	<branch> 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
     @@ Documentation/git-replay.adoc: When using `--ref-action=print`, the output is usable as input to
      	update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
      
    @@ Documentation/git-replay.adoc: all commits they have since `base`, playing them
     
      ## builtin/replay.c ##
     @@ builtin/replay.c: int cmd_replay(int argc,
    + 	struct ref_transaction *transaction = NULL;
    + 	struct strbuf transaction_err = STRBUF_INIT;
    + 	struct strbuf reflog_msg = STRBUF_INIT;
    ++	int desired_reverse;
    + 	int ret = 0;
      
      	const char *const replay_usage[] = {
      		N_("(EXPERIMENTAL!) git replay "
    @@ builtin/replay.c: int cmd_replay(int argc,
     +	 * Revert needs newest-first ordering (like git revert) to
     +	 * reduce conflicts by peeling off changes from the top.
     +	 */
    -+	int desired_reverse = !opts.revert;
    ++	desired_reverse = !opts.revert;
     +
      	repo_init_revisions(repo, &revs, prefix);

base-commit: d181b9354cf85b44455ce3ca9e6af0b9559e0ae2
-- 
2.51.0