Skip to content

Commit dc26bd8

Browse files
committed
Merge branch 'master' into next
* master: Update 1.7.8 draft release notes in preparation for rc4 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 Conflicts: builtin/revert.c
2 parents 1ebd5b5 + 017d1e1 commit dc26bd8

File tree

11 files changed

+316
-56
lines changed

11 files changed

+316
-56
lines changed

Documentation/RelNotes/1.7.8.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Updates since v1.7.7
2929
files from the index, not from the working tree.
3030

3131
* Variants of "git cherry-pick" and "git revert" that take multiple
32-
commits learned to "--continue".
32+
commits learned to "--continue" and "--abort".
3333

3434
* "git daemon" gives more human readble error messages to clients
3535
using ERR packets when appropriate.

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
@@ -272,6 +272,7 @@ void create_branch(const char *head,
272272
void remove_branch_state(void)
273273
{
274274
unlink(git_path("CHERRY_PICK_HEAD"));
275+
unlink(git_path("REVERT_HEAD"));
275276
unlink(git_path("MERGE_HEAD"));
276277
unlink(git_path("MERGE_RR"));
277278
unlink(git_path("MERGE_MSG"));

builtin/commit.c

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

15221522
unlink(git_path("CHERRY_PICK_HEAD"));
1523+
unlink(git_path("REVERT_HEAD"));
15231524
unlink(git_path("MERGE_HEAD"));
15241525
unlink(git_path("MERGE_MSG"));
15251526
unlink(git_path("MERGE_MODE"));

builtin/revert.c

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

42-
enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
42+
enum replay_subcommand {
43+
REPLAY_NONE,
44+
REPLAY_REMOVE_STATE,
45+
REPLAY_CONTINUE,
46+
REPLAY_ROLLBACK
47+
};
4348

4449
struct replay_opts {
4550
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,
@@ -296,15 +310,15 @@ static char *get_encoding(const char *message)
296310
return NULL;
297311
}
298312

299-
static void write_cherry_pick_head(struct commit *commit)
313+
static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
300314
{
301315
const char *filename;
302316
int fd;
303317
struct strbuf buf = STRBUF_INIT;
304318

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

307-
filename = git_path("CHERRY_PICK_HEAD");
321+
filename = git_path(pseudoref);
308322
fd = open(filename, O_WRONLY | O_CREAT, 0666);
309323
if (fd < 0)
310324
die_errno(_("Could not open '%s' for writing"), filename);
@@ -604,8 +618,10 @@ static int do_pick_commit(struct commit *commit, enum replay_action action,
604618
* However, if the merge did not even start, then we don't want to
605619
* write it at all.
606620
*/
607-
if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
608-
write_cherry_pick_head(commit);
621+
if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1))
622+
write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
623+
if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1))
624+
write_cherry_pick_head(commit, "REVERT_HEAD");
609625

610626
if (res) {
611627
error(action == REPLAY_REVERT
@@ -836,8 +852,11 @@ static int create_seq_dir(void)
836852
{
837853
const char *seq_dir = git_path(SEQ_DIR);
838854

839-
if (file_exists(seq_dir))
840-
return error(_("%s already exists."), seq_dir);
855+
if (file_exists(seq_dir)) {
856+
error(_("a cherry-pick or revert is already in progress"));
857+
advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
858+
return -1;
859+
}
841860
else if (mkdir(seq_dir, 0777) < 0)
842861
die_errno(_("Could not create sequencer directory %s"), seq_dir);
843862
return 0;
@@ -858,6 +877,71 @@ static void save_head(const char *head)
858877
die(_("Error wrapping up %s."), head_file);
859878
}
860879

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

9741061
/* Verify that the conflict has been resolved */
9751062
if (!index_differs_from("HEAD", 0))
9761063
todo_list = todo_list->next;
977-
} else {
978-
/*
979-
* Start a new cherry-pick/ revert sequence; but
980-
* first, make sure that an existing one isn't in
981-
* progress
982-
*/
1064+
return pick_commits(todo_list, opts);
1065+
}
9831066

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

10041086
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
@@ -21,7 +21,7 @@ struct replay_insn_list {
2121
*
2222
* With the aggressive flag, it additionally removes SEQ_OLD_DIR,
2323
* ignoring any errors. Inteded to be used by the sequencer's
24-
* '--reset' subcommand.
24+
* '--quit' subcommand.
2525
*/
2626
void remove_sequencer_state(int aggressive);
2727

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)