Skip to content

Commit e0613d2

Browse files
committed
Merge branch 'sa/replay-revert'
"git replay" (experimental) learns, in addition to "pick" and "replay", a new operating mode "revert". * sa/replay-revert: replay: add --revert mode to reverse commit changes sequencer: extract revert message formatting into shared function
2 parents 05ddb9e + 2760ee4 commit e0613d2

7 files changed

Lines changed: 361 additions & 101 deletions

File tree

Documentation/git-replay.adoc

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t
99
SYNOPSIS
1010
--------
1111
[verse]
12-
(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) [--ref-action[=<mode>]] <revision-range>
12+
(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range>
1313

1414
DESCRIPTION
1515
-----------
@@ -42,6 +42,25 @@ The history is replayed on top of the <branch> and <branch> is updated to
4242
point at the tip of the resulting history. This is different from `--onto`,
4343
which uses the target only as a starting point without updating it.
4444

45+
--revert <branch>::
46+
Starting point at which to create the reverted commits; must be a
47+
branch name.
48+
+
49+
When `--revert` is specified, the commits in the revision range are reverted
50+
(their changes are undone) and the reverted commits are created on top of
51+
<branch>. The <branch> is then updated to point at the new commits. This is
52+
the same as running `git revert <revision-range>` but does not update the
53+
working tree.
54+
+
55+
The commit messages follow `git revert` conventions: they are prefixed with
56+
"Revert" and include "This reverts commit <hash>." When reverting a commit
57+
whose message starts with "Revert", the new message uses "Reapply" instead.
58+
Unlike cherry-pick which preserves the original author, revert commits use
59+
the current user as the author, matching the behavior of `git revert`.
60+
+
61+
This option is mutually exclusive with `--onto` and `--advance`. It is also
62+
incompatible with `--contained` (which is a modifier for `--onto` only).
63+
4564
--contained::
4665
Update all branches that point at commits in
4766
<revision-range>. Requires `--onto`.
@@ -60,10 +79,11 @@ The default mode can be configured via the `replay.refAction` configuration vari
6079

6180
<revision-range>::
6281
Range of commits to replay; see "Specifying Ranges" in
63-
linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
64-
range should have a single tip, so that it's clear to which tip the
65-
advanced <branch> should point. Any commits in the range whose
66-
changes are already present in the branch the commits are being
82+
linkgit:git-rev-parse[1]. In `--advance <branch>` or
83+
`--revert <branch>` mode, the range should have a single tip,
84+
so that it's clear to which tip the advanced or reverted
85+
<branch> should point. Any commits in the range whose changes
86+
are already present in the branch the commits are being
6787
replayed onto will be dropped.
6888

6989
:git-replay: 1
@@ -84,9 +104,10 @@ When using `--ref-action=print`, the output is usable as input to
84104
update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH}
85105

86106
where the number of refs updated depends on the arguments passed and
87-
the shape of the history being replayed. When using `--advance`, the
88-
number of refs updated is always one, but for `--onto`, it can be one
89-
or more (rebasing multiple branches simultaneously is supported).
107+
the shape of the history being replayed. When using `--advance` or
108+
`--revert`, the number of refs updated is always one, but for `--onto`,
109+
it can be one or more (rebasing multiple branches simultaneously is
110+
supported).
90111

91112
There is no stderr output on conflicts; see the <<exit-status,EXIT
92113
STATUS>> section below.
@@ -152,6 +173,21 @@ all commits they have since `base`, playing them on top of
152173
`origin/main`. These three branches may have commits on top of `base`
153174
that they have in common, but that does not need to be the case.
154175

176+
To revert commits on a branch:
177+
178+
------------
179+
$ git replay --revert main topic~2..topic
180+
------------
181+
182+
This reverts the last two commits from `topic`, creating revert commits on
183+
top of `main`, and updates `main` to point at the result. This is useful when
184+
commits from `topic` were previously merged or cherry-picked into `main` and
185+
need to be undone.
186+
187+
NOTE: For reverting an entire merge request as a single commit (rather than
188+
commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE`
189+
which can avoid unnecessary merge conflicts.
190+
155191
GIT
156192
---
157193
Part of the linkgit:git[1] suite

builtin/replay.c

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,12 @@ int cmd_replay(int argc,
7979
struct ref_transaction *transaction = NULL;
8080
struct strbuf transaction_err = STRBUF_INIT;
8181
struct strbuf reflog_msg = STRBUF_INIT;
82+
int desired_reverse;
8283
int ret = 0;
8384

8485
const char *const replay_usage[] = {
8586
N_("(EXPERIMENTAL!) git replay "
86-
"([--contained] --onto <newbase> | --advance <branch>) "
87+
"([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) "
8788
"[--ref-action[=<mode>]] <revision-range>"),
8889
NULL
8990
};
@@ -96,6 +97,9 @@ int cmd_replay(int argc,
9697
N_("replay onto given commit")),
9798
OPT_BOOL(0, "contained", &opts.contained,
9899
N_("update all branches that point at commits in <revision-range>")),
100+
OPT_STRING(0, "revert", &opts.revert,
101+
N_("branch"),
102+
N_("revert commits onto given branch")),
99103
OPT_STRING(0, "ref-action", &ref_action,
100104
N_("mode"),
101105
N_("control ref update behavior (update|print)")),
@@ -105,19 +109,31 @@ int cmd_replay(int argc,
105109
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
106110
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
107111

108-
if (!opts.onto && !opts.advance) {
109-
error(_("option --onto or --advance is mandatory"));
112+
/* Exactly one mode must be specified */
113+
if (!opts.onto && !opts.advance && !opts.revert) {
114+
error(_("exactly one of --onto, --advance, or --revert is required"));
110115
usage_with_options(replay_usage, replay_options);
111116
}
112117

118+
die_for_incompatible_opt3(!!opts.onto, "--onto",
119+
!!opts.advance, "--advance",
120+
!!opts.revert, "--revert");
113121
die_for_incompatible_opt2(!!opts.advance, "--advance",
114122
opts.contained, "--contained");
115-
die_for_incompatible_opt2(!!opts.advance, "--advance",
116-
!!opts.onto, "--onto");
123+
die_for_incompatible_opt2(!!opts.revert, "--revert",
124+
opts.contained, "--contained");
117125

118126
/* Parse ref action mode from command line or config */
119127
ref_mode = get_ref_action_mode(repo, ref_action);
120128

129+
/*
130+
* Cherry-pick/rebase need oldest-first ordering so that each
131+
* replayed commit can build on its already-replayed parent.
132+
* Revert needs newest-first ordering (like git revert) to
133+
* reduce conflicts by peeling off changes from the top.
134+
*/
135+
desired_reverse = !opts.revert;
136+
121137
repo_init_revisions(repo, &revs, prefix);
122138

123139
/*
@@ -129,7 +145,7 @@ int cmd_replay(int argc,
129145
* some options changing these values if we think they could
130146
* be useful.
131147
*/
132-
revs.reverse = 1;
148+
revs.reverse = desired_reverse;
133149
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
134150
revs.topo_order = 1;
135151
revs.simplify_history = 0;
@@ -144,11 +160,11 @@ int cmd_replay(int argc,
144160
* Detect and warn if we override some user specified rev
145161
* walking options.
146162
*/
147-
if (revs.reverse != 1) {
163+
if (revs.reverse != desired_reverse) {
148164
warning(_("some rev walking options will be overridden as "
149165
"'%s' bit in 'struct rev_info' will be forced"),
150166
"reverse");
151-
revs.reverse = 1;
167+
revs.reverse = desired_reverse;
152168
}
153169
if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) {
154170
warning(_("some rev walking options will be overridden as "
@@ -174,7 +190,9 @@ int cmd_replay(int argc,
174190
goto cleanup;
175191

176192
/* Build reflog message */
177-
if (opts.advance) {
193+
if (opts.revert) {
194+
strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert);
195+
} else if (opts.advance) {
178196
strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
179197
} else {
180198
struct object_id oid;

0 commit comments

Comments
 (0)