Skip to content

Commit 9fd389b

Browse files
committed
Merge branch 'jn/revert-quit'
* jn/revert-quit: revert: remove --reset compatibility option revert: introduce --abort to cancel a failed cherry-pick revert: write REVERT_HEAD pseudoref during conflicted revert revert: improve error message for cherry-pick during cherry-pick revert: rearrange pick_revisions() for clarity revert: rename --reset option to --quit
2 parents e14d631 + c427b21 commit 9fd389b

File tree

10 files changed

+314
-54
lines changed

10 files changed

+314
-54
lines changed

Documentation/git-cherry-pick.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
12-
'git cherry-pick' --reset
1312
'git cherry-pick' --continue
13+
'git cherry-pick' --quit
14+
'git cherry-pick' --abort
1415

1516
DESCRIPTION
1617
-----------

Documentation/git-revert.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
12-
'git revert' --reset
1312
'git revert' --continue
13+
'git revert' --quit
14+
'git revert' --abort
1415

1516
DESCRIPTION
1617
-----------

Documentation/sequencer.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
--reset::
2-
Forget about the current operation in progress. Can be used
3-
to clear the sequencer state after a failed cherry-pick or
4-
revert.
5-
61
--continue::
72
Continue the operation in progress using the information in
83
'.git/sequencer'. Can be used to continue after resolving
94
conflicts in a failed cherry-pick or revert.
5+
6+
--quit::
7+
Forget about the current operation in progress. Can be used
8+
to clear the sequencer state after a failed cherry-pick or
9+
revert.
10+
11+
--abort::
12+
Cancel the operation and return to the pre-sequence state.

branch.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ void create_branch(const char *head,
241241
void remove_branch_state(void)
242242
{
243243
unlink(git_path("CHERRY_PICK_HEAD"));
244+
unlink(git_path("REVERT_HEAD"));
244245
unlink(git_path("MERGE_HEAD"));
245246
unlink(git_path("MERGE_RR"));
246247
unlink(git_path("MERGE_MSG"));

builtin/commit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
15141514
}
15151515

15161516
unlink(git_path("CHERRY_PICK_HEAD"));
1517+
unlink(git_path("REVERT_HEAD"));
15171518
unlink(git_path("MERGE_HEAD"));
15181519
unlink(git_path("MERGE_MSG"));
15191520
unlink(git_path("MERGE_MODE"));

builtin/revert.c

Lines changed: 122 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ static const char * const cherry_pick_usage[] = {
4040
};
4141

4242
enum replay_action { REVERT, CHERRY_PICK };
43-
enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
43+
enum replay_subcommand {
44+
REPLAY_NONE,
45+
REPLAY_REMOVE_STATE,
46+
REPLAY_CONTINUE,
47+
REPLAY_ROLLBACK
48+
};
4449

4550
struct replay_opts {
4651
enum replay_action action;
@@ -133,11 +138,13 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
133138
{
134139
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
135140
const char *me = action_name(opts);
136-
int reset = 0;
141+
int remove_state = 0;
137142
int contin = 0;
143+
int rollback = 0;
138144
struct option options[] = {
139-
OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
140-
OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"),
145+
OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"),
146+
OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"),
147+
OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"),
141148
OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
142149
OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
143150
OPT_NOOP_NOARG('r', NULL),
@@ -168,25 +175,32 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
168175

169176
/* Check for incompatible subcommands */
170177
verify_opt_mutually_compatible(me,
171-
"--reset", reset,
178+
"--quit", remove_state,
172179
"--continue", contin,
180+
"--abort", rollback,
173181
NULL);
174182

175183
/* Set the subcommand */
176-
if (reset)
177-
opts->subcommand = REPLAY_RESET;
184+
if (remove_state)
185+
opts->subcommand = REPLAY_REMOVE_STATE;
178186
else if (contin)
179187
opts->subcommand = REPLAY_CONTINUE;
188+
else if (rollback)
189+
opts->subcommand = REPLAY_ROLLBACK;
180190
else
181191
opts->subcommand = REPLAY_NONE;
182192

183193
/* Check for incompatible command line arguments */
184194
if (opts->subcommand != REPLAY_NONE) {
185195
char *this_operation;
186-
if (opts->subcommand == REPLAY_RESET)
187-
this_operation = "--reset";
188-
else
196+
if (opts->subcommand == REPLAY_REMOVE_STATE)
197+
this_operation = "--quit";
198+
else if (opts->subcommand == REPLAY_CONTINUE)
189199
this_operation = "--continue";
200+
else {
201+
assert(opts->subcommand == REPLAY_ROLLBACK);
202+
this_operation = "--abort";
203+
}
190204

191205
verify_opt_compatible(me, this_operation,
192206
"--no-commit", opts->no_commit,
@@ -286,15 +300,15 @@ static char *get_encoding(const char *message)
286300
return NULL;
287301
}
288302

289-
static void write_cherry_pick_head(struct commit *commit)
303+
static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
290304
{
291305
const char *filename;
292306
int fd;
293307
struct strbuf buf = STRBUF_INIT;
294308

295309
strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
296310

297-
filename = git_path("CHERRY_PICK_HEAD");
311+
filename = git_path(pseudoref);
298312
fd = open(filename, O_WRONLY | O_CREAT, 0666);
299313
if (fd < 0)
300314
die_errno(_("Could not open '%s' for writing"), filename);
@@ -594,7 +608,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
594608
* write it at all.
595609
*/
596610
if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1))
597-
write_cherry_pick_head(commit);
611+
write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
612+
if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1))
613+
write_cherry_pick_head(commit, "REVERT_HEAD");
598614

599615
if (res) {
600616
error(opts->action == REVERT
@@ -843,8 +859,11 @@ static int create_seq_dir(void)
843859
{
844860
const char *seq_dir = git_path(SEQ_DIR);
845861

846-
if (file_exists(seq_dir))
847-
return error(_("%s already exists."), seq_dir);
862+
if (file_exists(seq_dir)) {
863+
error(_("a cherry-pick or revert is already in progress"));
864+
advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
865+
return -1;
866+
}
848867
else if (mkdir(seq_dir, 0777) < 0)
849868
die_errno(_("Could not create sequencer directory %s"), seq_dir);
850869
return 0;
@@ -865,6 +884,71 @@ static void save_head(const char *head)
865884
die(_("Error wrapping up %s."), head_file);
866885
}
867886

887+
static int reset_for_rollback(const unsigned char *sha1)
888+
{
889+
const char *argv[4]; /* reset --merge <arg> + NULL */
890+
argv[0] = "reset";
891+
argv[1] = "--merge";
892+
argv[2] = sha1_to_hex(sha1);
893+
argv[3] = NULL;
894+
return run_command_v_opt(argv, RUN_GIT_CMD);
895+
}
896+
897+
static int rollback_single_pick(void)
898+
{
899+
unsigned char head_sha1[20];
900+
901+
if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
902+
!file_exists(git_path("REVERT_HEAD")))
903+
return error(_("no cherry-pick or revert in progress"));
904+
if (!resolve_ref("HEAD", head_sha1, 0, NULL))
905+
return error(_("cannot resolve HEAD"));
906+
if (is_null_sha1(head_sha1))
907+
return error(_("cannot abort from a branch yet to be born"));
908+
return reset_for_rollback(head_sha1);
909+
}
910+
911+
static int sequencer_rollback(struct replay_opts *opts)
912+
{
913+
const char *filename;
914+
FILE *f;
915+
unsigned char sha1[20];
916+
struct strbuf buf = STRBUF_INIT;
917+
918+
filename = git_path(SEQ_HEAD_FILE);
919+
f = fopen(filename, "r");
920+
if (!f && errno == ENOENT) {
921+
/*
922+
* There is no multiple-cherry-pick in progress.
923+
* If CHERRY_PICK_HEAD or REVERT_HEAD indicates
924+
* a single-cherry-pick in progress, abort that.
925+
*/
926+
return rollback_single_pick();
927+
}
928+
if (!f)
929+
return error(_("cannot open %s: %s"), filename,
930+
strerror(errno));
931+
if (strbuf_getline(&buf, f, '\n')) {
932+
error(_("cannot read %s: %s"), filename, ferror(f) ?
933+
strerror(errno) : _("unexpected end of file"));
934+
goto fail;
935+
}
936+
if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
937+
error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
938+
filename);
939+
goto fail;
940+
}
941+
if (reset_for_rollback(sha1))
942+
goto fail;
943+
strbuf_release(&buf);
944+
fclose(f);
945+
return 0;
946+
fail:
947+
strbuf_release(&buf);
948+
fclose(f);
949+
return -1;
950+
}
951+
868952
static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
869953
{
870954
const char *todo_file = git_path(SEQ_TODO_FILE);
@@ -965,43 +1049,41 @@ static int pick_revisions(struct replay_opts *opts)
9651049
* cherry-pick should be handled differently from an existing
9661050
* one that is being continued
9671051
*/
968-
if (opts->subcommand == REPLAY_RESET) {
1052+
if (opts->subcommand == REPLAY_REMOVE_STATE) {
9691053
remove_sequencer_state(1);
9701054
return 0;
971-
} else if (opts->subcommand == REPLAY_CONTINUE) {
1055+
}
1056+
if (opts->subcommand == REPLAY_ROLLBACK)
1057+
return sequencer_rollback(opts);
1058+
if (opts->subcommand == REPLAY_CONTINUE) {
9721059
if (!file_exists(git_path(SEQ_TODO_FILE)))
973-
goto error;
1060+
return error(_("No %s in progress"), action_name(opts));
9741061
read_populate_opts(&opts);
9751062
read_populate_todo(&todo_list, opts);
9761063

9771064
/* Verify that the conflict has been resolved */
9781065
if (!index_differs_from("HEAD", 0))
9791066
todo_list = todo_list->next;
980-
} else {
981-
/*
982-
* Start a new cherry-pick/ revert sequence; but
983-
* first, make sure that an existing one isn't in
984-
* progress
985-
*/
1067+
return pick_commits(todo_list, opts);
1068+
}
9861069

987-
walk_revs_populate_todo(&todo_list, opts);
988-
if (create_seq_dir() < 0) {
989-
error(_("A cherry-pick or revert is in progress."));
990-
advise(_("Use --continue to continue the operation"));
991-
advise(_("or --reset to forget about it"));
992-
return -1;
993-
}
994-
if (get_sha1("HEAD", sha1)) {
995-
if (opts->action == REVERT)
996-
return error(_("Can't revert as initial commit"));
997-
return error(_("Can't cherry-pick into empty head"));
998-
}
999-
save_head(sha1_to_hex(sha1));
1000-
save_opts(opts);
1070+
/*
1071+
* Start a new cherry-pick/ revert sequence; but
1072+
* first, make sure that an existing one isn't in
1073+
* progress
1074+
*/
1075+
1076+
walk_revs_populate_todo(&todo_list, opts);
1077+
if (create_seq_dir() < 0)
1078+
return -1;
1079+
if (get_sha1("HEAD", sha1)) {
1080+
if (opts->action == REVERT)
1081+
return error(_("Can't revert as initial commit"));
1082+
return error(_("Can't cherry-pick into empty head"));
10011083
}
1084+
save_head(sha1_to_hex(sha1));
1085+
save_opts(opts);
10021086
return pick_commits(todo_list, opts);
1003-
error:
1004-
return error(_("No %s in progress"), action_name(opts));
10051087
}
10061088

10071089
int cmd_revert(int argc, const char **argv, const char *prefix)

sequencer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*
1414
* With the aggressive flag, it additionally removes SEQ_OLD_DIR,
1515
* ignoring any errors. Inteded to be used by the sequencer's
16-
* '--reset' subcommand.
16+
* '--quit' subcommand.
1717
*/
1818
void remove_sequencer_state(int aggressive);
1919

t/t3507-cherry-pick-conflict.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,60 @@ test_expect_success 'revert also handles conflicts sanely' '
253253
test_cmp expected actual
254254
'
255255

256+
test_expect_success 'failed revert sets REVERT_HEAD' '
257+
pristine_detach initial &&
258+
test_must_fail git revert picked &&
259+
test_cmp_rev picked REVERT_HEAD
260+
'
261+
262+
test_expect_success 'successful revert does not set REVERT_HEAD' '
263+
pristine_detach base &&
264+
git revert base &&
265+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
266+
test_must_fail git rev-parse --verify REVERT_HEAD
267+
'
268+
269+
test_expect_success 'revert --no-commit sets REVERT_HEAD' '
270+
pristine_detach base &&
271+
git revert --no-commit base &&
272+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
273+
test_cmp_rev base REVERT_HEAD
274+
'
275+
276+
test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' '
277+
pristine_detach base &&
278+
echo foo > foo &&
279+
test_must_fail git revert base &&
280+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
281+
test_must_fail git rev-parse --verify REVERT_HEAD
282+
'
283+
284+
test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' '
285+
pristine_detach initial &&
286+
(
287+
GIT_CHERRY_PICK_HELP="and then do something else" &&
288+
GIT_REVERT_HELP="and then do something else, again" &&
289+
export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP &&
290+
test_must_fail git revert picked
291+
) &&
292+
test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
293+
test_cmp_rev picked REVERT_HEAD
294+
'
295+
296+
test_expect_success 'git reset clears REVERT_HEAD' '
297+
pristine_detach initial &&
298+
test_must_fail git revert picked &&
299+
git reset &&
300+
test_must_fail git rev-parse --verify REVERT_HEAD
301+
'
302+
303+
test_expect_success 'failed commit does not clear REVERT_HEAD' '
304+
pristine_detach initial &&
305+
test_must_fail git revert picked &&
306+
test_must_fail git commit &&
307+
test_cmp_rev picked REVERT_HEAD
308+
'
309+
256310
test_expect_success 'revert conflict, diff3 -m style' '
257311
pristine_detach initial &&
258312
git config merge.conflictstyle diff3 &&

0 commit comments

Comments
 (0)