[PATCH v5 0/2] replay: add --revert mode to reverse commit changes
- From
Siddharth Asthana <siddharthasthana31@gmail.com>
- Date
- Mar 24, 2026, 22:03 UTC
- Message-ID
- <20260324220401.47040-1-siddharthasthana31@gmail.com>
- In-Reply-To
- <20260313054035.26605-1-siddharthasthana31@gmail.com>
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).
Changes in v5:
- Made sequencer_format_revert_message() header comment more concise,
using single quotes to avoid nested escaped double quotes
- Moved desired_reverse declaration up and removed the bare braces, so
the same named variable is used when setting and checking
revs.reverse
- Used die_for_incompatible_opt2() for --advance/--contained and
--revert/--contained instead of die("--contained requires --onto")
- Dropped the erroneously re-added ellipsis from <revision-range>...
in the SYNOPSIS
- Restored #define the_repository DO_NOT_USE_THE_REPOSITORY guard in
replay.c
- Used ${SQ} instead of '"'"' for single-quote escaping in tests
- Link to v4: https://lore.kernel.org/git/20260313054035.26605-1-siddharthasthana31@gmail.com/
- Link to v3: https://public-inbox.org/git/20260218234215.89326-1-siddharthasthana31@gmail.com/
- Link to v2: https://public-inbox.org/git/20251202201611.22137-1-siddharthasthana31@gmail.com/
- Link to v1: https://public-inbox.org/git/20251125170056.34489-1-siddharthasthana31@gmail.com/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 | 43 ++++++++- builtin/replay.c | 35 ++++++-- replay.c | 161 +++++++++++++++++++++++++--------- replay.h | 11 ++- sequencer.c | 78 +++++++++------- sequencer.h | 13 +++ t/t3650-replay-basics.sh | 111 +++++++++++++++++++++-- 7 files changed, 355 insertions(+), 97 deletions(-)
Range-diff versus v4:
1: bdc710b265 ! 1: 6bd2ce4515 sequencer: extract revert message formatting into shared function
@@ sequencer.h: int sequencer_determine_whence(struct repository *r, enum commit_wh
int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs);
+/*
-+ * Formats a complete revert commit message following standard Git conventions.
-+ * Handles regular reverts ("Revert \"<subject>\""), revert of revert cases
-+ * ("Reapply \"<subject>\""), and the --reference style. Appends "This reverts
-+ * commit <ref>." using either the abbreviated or full commit reference
-+ * depending on use_commit_reference. Also handles merge-parent references.
++ * Format a revert commit message with appropriate 'Revert "<subject>"' or
++ * 'Reapply "<subject>"' prefix and 'This reverts commit <ref>.' body.
++ * When use_commit_reference is set, <ref> is an abbreviated hash with
++ * subject and date; otherwise the full hex hash is used.
+ */
+void sequencer_format_revert_message(struct repository *r,
+ const char *subject,
2: bea6229575 ! 2: 9fd92497b9 replay: add --revert mode to reverse commit changes
@@ Documentation/git-replay.adoc: git-replay - EXPERIMENTAL: Replay commits on a ne
--------
[verse]
-(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) [--ref-action[=<mode>]] <revision-range>
-+(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>...
++(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>
DESCRIPTION
-----------
@@ builtin/replay.c: int cmd_replay(int argc,
const char *const replay_usage[] = {
N_("(EXPERIMENTAL!) git replay "
- "([--contained] --onto <newbase> | --advance <branch>) "
-- "[--ref-action[=<mode>]] <revision-range>"),
+ "([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
-+ "[--ref-action[=<mode>]] <revision-range>..."),
+ "[--ref-action[=<mode>]] <revision-range>"),
NULL
};
- struct option replay_options[] = {
@@ builtin/replay.c: int cmd_replay(int argc,
N_("replay onto given commit")),
OPT_BOOL(0, "contained", &opts.contained,
@@ builtin/replay.c: int cmd_replay(int argc,
usage_with_options(replay_usage, replay_options);
}
-- die_for_incompatible_opt2(!!opts.advance, "--advance",
-- opts.contained, "--contained");
-- die_for_incompatible_opt2(!!opts.advance, "--advance",
-- !!opts.onto, "--onto");
+ die_for_incompatible_opt3(!!opts.onto, "--onto",
+ !!opts.advance, "--advance",
+ !!opts.revert, "--revert");
-+ if (opts.contained && !opts.onto)
-+ die(_("--contained requires --onto"));
+ die_for_incompatible_opt2(!!opts.advance, "--advance",
+ opts.contained, "--contained");
+- die_for_incompatible_opt2(!!opts.advance, "--advance",
+- !!opts.onto, "--onto");
++ die_for_incompatible_opt2(!!opts.revert, "--revert",
++ opts.contained, "--contained");
/* Parse ref action mode from command line or config */
ref_mode = get_ref_action_mode(repo, ref_action);
-@@ builtin/replay.c: int cmd_replay(int argc,
- * some options changing these values if we think they could
- * be useful.
- */
-- revs.reverse = 1;
+
+ /*
+ * Cherry-pick/rebase need oldest-first ordering so that each
+ * replayed commit can build on its already-replayed parent.
+ * Revert needs newest-first ordering (like git revert) to
+ * reduce conflicts by peeling off changes from the top.
+ */
-+ revs.reverse = opts.revert ? 0 : 1;
++ int desired_reverse = !opts.revert;
++
+ repo_init_revisions(repo, &revs, prefix);
+
+ /*
+@@ builtin/replay.c: int cmd_replay(int argc,
+ * some options changing these values if we think they could
+ * be useful.
+ */
+- revs.reverse = 1;
++ revs.reverse = desired_reverse;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.topo_order = 1;
revs.simplify_history = 0;
@@ builtin/replay.c: int cmd_replay(int argc,
* walking options.
*/
- if (revs.reverse != 1) {
-- warning(_("some rev walking options will be overridden as "
-- "'%s' bit in 'struct rev_info' will be forced"),
-- "reverse");
++ if (revs.reverse != desired_reverse) {
+ warning(_("some rev walking options will be overridden as "
+ "'%s' bit in 'struct rev_info' will be forced"),
+ "reverse");
- revs.reverse = 1;
-+ {
-+ int desired_reverse = opts.revert ? 0 : 1;
-+ if (revs.reverse != desired_reverse) {
-+ warning(_("some rev walking options will be overridden as "
-+ "'%s' bit in 'struct rev_info' will be forced"),
-+ "reverse");
-+ revs.reverse = desired_reverse;
-+ }
++ revs.reverse = desired_reverse;
}
if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
warning(_("some rev walking options will be overridden as "
@@ replay.c
#include "strmap.h"
#include "tree.h"
--/*
-- * We technically need USE_THE_REPOSITORY_VARIABLE for DEFAULT_ABBREV, but
-- * do not want to use the_repository.
-- */
--#define the_repository DO_NOT_USE_THE_REPOSITORY
+@@
+ */
+ #define the_repository DO_NOT_USE_THE_REPOSITORY
+
+enum replay_mode {
+ REPLAY_MODE_PICK,
+ REPLAY_MODE_REVERT,
+};
-
++
static const char *short_commit_name(struct repository *repo,
struct commit *commit)
+ {
@@ replay.c: static char *get_author(const char *message)
return NULL;
}
@@ t/t3650-replay-basics.sh: test_expect_success 'no base or negative ref gives no-
-test_expect_success 'options --advance and --contained cannot be used together' '
- printf "fatal: options ${SQ}--advance${SQ} " >expect &&
- printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect &&
-+test_expect_success '--contained requires --onto' '
-+ echo "fatal: --contained requires --onto" >expect &&
++test_expect_success '--advance and --contained cannot be used together' '
test_must_fail git replay --advance=main --contained \
topic1..topic2 2>actual &&
- test_cmp expect actual
+- test_cmp expect actual
++ test_grep "cannot be used together" actual
'
test_expect_success 'cannot advance target ... ordering would be ill-defined' '
- echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect &&
-+ cat >expect <<-\EOF &&
-+ fatal: '"'"'--advance'"'"' cannot be used with multiple revision ranges because the ordering would be ill-defined
-+ EOF
++ echo "fatal: ${SQ}--advance${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect &&
test_must_fail git replay --advance=main main topic1 topic2 2>actual &&
test_cmp expect actual
'
@@ t/t3650-replay-basics.sh: test_expect_success 'invalid replay.refAction value' '
+'
+
+test_expect_success 'cannot revert with multiple sources' '
-+ cat >expect <<-\EOF &&
-+ fatal: '"'"'--revert'"'"' cannot be used with multiple revision ranges because the ordering would be ill-defined
-+ EOF
++ echo "fatal: ${SQ}--revert${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect &&
+ test_must_fail git replay --revert main main topic1 topic2 2>actual &&
+ test_cmp expect actual
+'
@@ t/t3650-replay-basics.sh: test_expect_success 'invalid replay.refAction value' '
+
+test_expect_success 'git replay --revert incompatible with --contained' '
+ test_must_fail git replay --revert topic4 --contained topic4~1..topic4 2>error &&
-+ test_grep "requires --onto" error
++ test_grep "cannot be used together" error
+'
+
+test_expect_success 'git replay --revert incompatible with --onto' 'base-commit: d181b9354cf85b44455ce3ca9e6af0b9559e0ae2
-- 2.51.0