From f80a87262aa4468b0941d38deffdb97a757307c5 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 05:14:29 -0600 Subject: revert: rename --reset option to --quit The option to "git cherry-pick" and "git revert" to discard the sequencer state introduced by v1.7.8-rc0~141^2~6 (revert: Introduce --reset to remove sequencer state, 2011-08-04) has a confusing name. Change it now, while we still have the time. The new name for "cherry-pick, please get out of my way, since I've long forgotten about the sequence of commits I was cherry-picking when you wrote that old .git/sequencer directory" is --quit. Mnemonic: this is analagous to quiting a program the user is no longer using --- we just want to get out of the multiple-command cherry-pick procedure and not to reset HEAD or rewind any other old state. The "--reset" option is kept as a synonym to minimize the impact. We might consider dropping it for simplicity in a separate patch, though. Adjust documentation and tests to use the newly preferred name (--quit) instead of --reset. While at it, let's clarify the short descriptions of these operations in "-h" output. Before: --reset forget the current operation --continue continue the current operation After: --quit end revert or cherry-pick sequence --continue resume revert or cherry-pick sequence Noticed-by: Phil Hord Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-cherry-pick.txt | 2 +- Documentation/git-revert.txt | 2 +- Documentation/sequencer.txt | 10 +++++----- builtin/revert.c | 25 ++++++++++++++----------- sequencer.h | 2 +- t/t3510-cherry-pick-sequence.sh | 31 ++++++++++++++++++++++++++----- t/t7106-reset-sequence.sh | 2 +- 7 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 2660a842fc..21998b820e 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] ... -'git cherry-pick' --reset 'git cherry-pick' --continue +'git cherry-pick' --quit DESCRIPTION ----------- diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index f3519413e7..b0fcabc8b6 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] ... -'git revert' --reset 'git revert' --continue +'git revert' --quit DESCRIPTION ----------- diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt index 3e6df338be..75f8e869e8 100644 --- a/Documentation/sequencer.txt +++ b/Documentation/sequencer.txt @@ -1,9 +1,9 @@ ---reset:: - Forget about the current operation in progress. Can be used - to clear the sequencer state after a failed cherry-pick or - revert. - --continue:: Continue the operation in progress using the information in '.git/sequencer'. Can be used to continue after resolving conflicts in a failed cherry-pick or revert. + +--quit:: + Forget about the current operation in progress. Can be used + to clear the sequencer state after a failed cherry-pick or + revert. diff --git a/builtin/revert.c b/builtin/revert.c index 544e8c3057..e109fb1178 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -40,7 +40,7 @@ static const char * const cherry_pick_usage[] = { }; enum replay_action { REVERT, CHERRY_PICK }; -enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE }; +enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, REPLAY_CONTINUE }; struct replay_opts { enum replay_action action; @@ -133,11 +133,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); - int reset = 0; + int remove_state = 0; int contin = 0; struct option options[] = { - OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"), - OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"), + OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"), OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), OPT_NOOP_NOARG('r', NULL), @@ -147,6 +147,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"), OPT_CALLBACK('X', "strategy-option", &opts, "option", "option for merge strategy", option_parse_x), + { OPTION_BOOLEAN, 0, "reset", &remove_state, NULL, + "alias for --quit (deprecated)", + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, OPT_END(), OPT_END(), OPT_END(), @@ -168,13 +171,13 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) /* Check for incompatible subcommands */ verify_opt_mutually_compatible(me, - "--reset", reset, + "--quit", remove_state, "--continue", contin, NULL); /* Set the subcommand */ - if (reset) - opts->subcommand = REPLAY_RESET; + if (remove_state) + opts->subcommand = REPLAY_REMOVE_STATE; else if (contin) opts->subcommand = REPLAY_CONTINUE; else @@ -183,8 +186,8 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) /* Check for incompatible command line arguments */ if (opts->subcommand != REPLAY_NONE) { char *this_operation; - if (opts->subcommand == REPLAY_RESET) - this_operation = "--reset"; + if (opts->subcommand == REPLAY_REMOVE_STATE) + this_operation = "--quit"; else this_operation = "--continue"; @@ -965,7 +968,7 @@ static int pick_revisions(struct replay_opts *opts) * cherry-pick should be handled differently from an existing * one that is being continued */ - if (opts->subcommand == REPLAY_RESET) { + if (opts->subcommand == REPLAY_REMOVE_STATE) { remove_sequencer_state(1); return 0; } else if (opts->subcommand == REPLAY_CONTINUE) { @@ -988,7 +991,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) { error(_("A cherry-pick or revert is in progress.")); advise(_("Use --continue to continue the operation")); - advise(_("or --reset to forget about it")); + advise(_("or --quit to forget about it")); return -1; } if (get_sha1("HEAD", sha1)) { diff --git a/sequencer.h b/sequencer.h index 905d295012..f435fdb4b1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -13,7 +13,7 @@ * * With the aggressive flag, it additionally removes SEQ_OLD_DIR, * ignoring any errors. Inteded to be used by the sequencer's - * '--reset' subcommand. + * '--quit' subcommand. */ void remove_sequencer_state(int aggressive); diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 3bca2b3dd5..bb67cdcb5d 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -13,7 +13,7 @@ test_description='Test cherry-pick continuation features . ./test-lib.sh pristine_detach () { - git cherry-pick --reset && + git cherry-pick --quit && git checkout -f "$1^0" && git read-tree -u --reset HEAD && git clean -d -f -f -q -x @@ -70,18 +70,39 @@ test_expect_success 'cherry-pick cleans up sequencer state upon success' ' test_path_is_missing .git/sequencer ' -test_expect_success '--reset does not complain when no cherry-pick is in progress' ' +test_expect_success '--quit does not complain when no cherry-pick is in progress' ' pristine_detach initial && - git cherry-pick --reset + git cherry-pick --quit ' -test_expect_success '--reset cleans up sequencer state' ' +test_expect_success '--quit cleans up sequencer state' ' pristine_detach initial && test_must_fail git cherry-pick base..picked && - git cherry-pick --reset && + git cherry-pick --quit && test_path_is_missing .git/sequencer ' +test_expect_success 'cherry-pick --reset (another name for --quit)' ' + pristine_detach initial && + cat >expect <<-\EOF && + OBJID + :100644 100644 OBJID OBJID M unrelated + OBJID + :000000 100644 OBJID OBJID A foo + :000000 100644 OBJID OBJID A unrelated + EOF + test_must_fail git cherry-pick base..picked && + git cherry-pick --reset && + test_path_is_missing .git/sequencer && + test_must_fail git update-index --refresh && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$_x40/OBJID/g" + } >actual && + test_cmp expect actual +' + test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' ' pristine_detach initial && test_must_fail git cherry-pick base..picked && diff --git a/t/t7106-reset-sequence.sh b/t/t7106-reset-sequence.sh index 4956caaf82..3f86e8c5e8 100755 --- a/t/t7106-reset-sequence.sh +++ b/t/t7106-reset-sequence.sh @@ -12,7 +12,7 @@ test_description='Test interaction of reset --hard with sequencer . ./test-lib.sh pristine_detach () { - git cherry-pick --reset && + git cherry-pick --quit && git checkout -f "$1^0" && git read-tree -u --reset HEAD && git clean -d -f -f -q -x -- cgit v1.2.1 From dffc86002862753376e31a16a1c1c0306716fbe6 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 05:15:10 -0600 Subject: revert: rearrange pick_revisions() for clarity Deal completely with "cherry-pick --quit" and --continue at the beginning of pick_revisions(), leaving the rest of the function for the more interesting "git cherry-pick " case. No functional change intended. The impact is just to unindent the code a little. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/revert.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index e109fb1178..2346cafba4 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -971,40 +971,40 @@ static int pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_REMOVE_STATE) { remove_sequencer_state(1); return 0; - } else if (opts->subcommand == REPLAY_CONTINUE) { + } + if (opts->subcommand == REPLAY_CONTINUE) { if (!file_exists(git_path(SEQ_TODO_FILE))) - goto error; + return error(_("No %s in progress"), action_name(opts)); read_populate_opts(&opts); read_populate_todo(&todo_list, opts); /* Verify that the conflict has been resolved */ if (!index_differs_from("HEAD", 0)) todo_list = todo_list->next; - } else { - /* - * Start a new cherry-pick/ revert sequence; but - * first, make sure that an existing one isn't in - * progress - */ + return pick_commits(todo_list, opts); + } - walk_revs_populate_todo(&todo_list, opts); - if (create_seq_dir() < 0) { - error(_("A cherry-pick or revert is in progress.")); - advise(_("Use --continue to continue the operation")); - advise(_("or --quit to forget about it")); - return -1; - } - if (get_sha1("HEAD", sha1)) { - if (opts->action == REVERT) - return error(_("Can't revert as initial commit")); - return error(_("Can't cherry-pick into empty head")); - } - save_head(sha1_to_hex(sha1)); - save_opts(opts); + /* + * Start a new cherry-pick/ revert sequence; but + * first, make sure that an existing one isn't in + * progress + */ + + walk_revs_populate_todo(&todo_list, opts); + if (create_seq_dir() < 0) { + error(_("A cherry-pick or revert is in progress.")); + advise(_("Use --continue to continue the operation")); + advise(_("or --quit to forget about it")); + return -1; + } + if (get_sha1("HEAD", sha1)) { + if (opts->action == REVERT) + return error(_("Can't revert as initial commit")); + return error(_("Can't cherry-pick into empty head")); } + save_head(sha1_to_hex(sha1)); + save_opts(opts); return pick_commits(todo_list, opts); -error: - return error(_("No %s in progress"), action_name(opts)); } int cmd_revert(int argc, const char **argv, const char *prefix) -- cgit v1.2.1 From b8c74690b20b7c4dd405f71d63bab325447356da Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 05:15:47 -0600 Subject: revert: improve error message for cherry-pick during cherry-pick In the spirit of v1.6.3.3~3^2 (refuse to merge during a merge, 2009-07-01), "git cherry-pick" refuses to start a new cherry-pick when in the middle of an existing conflicted cherry-pick in the following sequence: 1. git cherry-pick HEAD..origin 2. resolve conflicts 3. git cherry-pick HEAD..origin (instead of "git cherry-pick --continue", by mistake) Good. However, the error message on attempting step 3 is more convoluted than necessary: $ git cherry-pick HEAD..origin error: .git/sequencer already exists. error: A cherry-pick or revert is in progress. hint: Use --continue to continue the operation hint: or --quit to forget about it fatal: cherry-pick failed Clarify by removing the redundant first "error:" message, simplifying the advice, and using lower-case and no full stops to be consistent with other commands that prefix their messages with "error:", so it becomes error: a cherry-pick or revert is already in progress hint: try "git cherry-pick (--continue | --quit)" fatal: cherry-pick failed The "fatal: cherry-pick failed" line seems unnecessary, too, but that can be fixed some other day. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/revert.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 2346cafba4..1d112e4ce4 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -846,8 +846,11 @@ static int create_seq_dir(void) { const char *seq_dir = git_path(SEQ_DIR); - if (file_exists(seq_dir)) - return error(_("%s already exists."), seq_dir); + if (file_exists(seq_dir)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit)\"")); + return -1; + } else if (mkdir(seq_dir, 0777) < 0) die_errno(_("Could not create sequencer directory %s"), seq_dir); return 0; @@ -991,12 +994,8 @@ static int pick_revisions(struct replay_opts *opts) */ walk_revs_populate_todo(&todo_list, opts); - if (create_seq_dir() < 0) { - error(_("A cherry-pick or revert is in progress.")); - advise(_("Use --continue to continue the operation")); - advise(_("or --quit to forget about it")); + if (create_seq_dir() < 0) return -1; - } if (get_sha1("HEAD", sha1)) { if (opts->action == REVERT) return error(_("Can't revert as initial commit")); -- cgit v1.2.1 From 82433cdf4d888639cecddeca162d619cf370417e Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 05:17:36 -0600 Subject: revert: write REVERT_HEAD pseudoref during conflicted revert When conflicts are encountered while reverting a commit, it can be handy to have the name of that commit easily available. For example, to produce a copy of the patch to refer to while resolving conflicts: $ git revert 2eceb2a8 error: could not revert 2eceb2a8... awesome, buggy feature $ git show -R REVERT_HEAD >the-patch $ edit $(git diff --name-only) Set a REVERT_HEAD pseudoref when "git revert" does not make a commit, for cases like this. This also makes it possible for scripts to distinguish between a revert that encountered conflicts and other sources of an unmerged index. After successfully committing, resetting with "git reset", or moving to another commit with "git checkout" or "git reset", the pseudoref is no longer useful, so remove it. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- branch.c | 1 + builtin/commit.c | 1 + builtin/revert.c | 8 +++--- t/t3507-cherry-pick-conflict.sh | 54 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/branch.c b/branch.c index d8098762f6..025a97be02 100644 --- a/branch.c +++ b/branch.c @@ -241,6 +241,7 @@ void create_branch(const char *head, void remove_branch_state(void) { unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path("REVERT_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_RR")); unlink(git_path("MERGE_MSG")); diff --git a/builtin/commit.c b/builtin/commit.c index c46f2d18e1..8f2bebecf3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1514,6 +1514,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } unlink(git_path("CHERRY_PICK_HEAD")); + unlink(git_path("REVERT_HEAD")); unlink(git_path("MERGE_HEAD")); unlink(git_path("MERGE_MSG")); unlink(git_path("MERGE_MODE")); diff --git a/builtin/revert.c b/builtin/revert.c index 1d112e4ce4..f5ba67a505 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -289,7 +289,7 @@ static char *get_encoding(const char *message) return NULL; } -static void write_cherry_pick_head(struct commit *commit) +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) { const char *filename; int fd; @@ -297,7 +297,7 @@ static void write_cherry_pick_head(struct commit *commit) strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - filename = git_path("CHERRY_PICK_HEAD"); + filename = git_path(pseudoref); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); @@ -597,7 +597,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * write it at all. */ if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit); + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { error(opts->action == REVERT diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index cb45574a7b..ee1659c178 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -253,6 +253,60 @@ test_expect_success 'revert also handles conflicts sanely' ' test_cmp expected actual ' +test_expect_success 'failed revert sets REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + test_cmp_rev picked REVERT_HEAD +' + +test_expect_success 'successful revert does not set REVERT_HEAD' ' + pristine_detach base && + git revert base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'revert --no-commit sets REVERT_HEAD' ' + pristine_detach base && + git revert --no-commit base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_cmp_rev base REVERT_HEAD +' + +test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' ' + pristine_detach base && + echo foo > foo && + test_must_fail git revert base && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' ' + pristine_detach initial && + ( + GIT_CHERRY_PICK_HELP="and then do something else" && + GIT_REVERT_HELP="and then do something else, again" && + export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP && + test_must_fail git revert picked + ) && + test_must_fail git rev-parse --verify CHERRY_PICK_HEAD && + test_cmp_rev picked REVERT_HEAD +' + +test_expect_success 'git reset clears REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + git reset && + test_must_fail git rev-parse --verify REVERT_HEAD +' + +test_expect_success 'failed commit does not clear REVERT_HEAD' ' + pristine_detach initial && + test_must_fail git revert picked && + test_must_fail git commit && + test_cmp_rev picked REVERT_HEAD +' + test_expect_success 'revert conflict, diff3 -m style' ' pristine_detach initial && git config merge.conflictstyle diff3 && -- cgit v1.2.1 From 539047c19ec040819b6f6af2d55714195b812abb Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 19:27:21 -0600 Subject: revert: introduce --abort to cancel a failed cherry-pick After running some ill-advised command like "git cherry-pick HEAD..linux-next", the bewildered novice may want to return to more familiar territory. Introduce a "git cherry-pick --abort" command that rolls back the entire cherry-pick sequence and places the repository back on solid ground. Just like "git merge --abort", this internally uses "git reset --merge", so local changes not involved in the conflict resolution are preserved. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-cherry-pick.txt | 1 + Documentation/git-revert.txt | 1 + Documentation/sequencer.txt | 3 ++ builtin/revert.c | 87 +++++++++++++++++++++++++++++++++-- t/t3510-cherry-pick-sequence.sh | 96 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 3 deletions(-) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 21998b820e..fed5097e00 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] ... 'git cherry-pick' --continue 'git cherry-pick' --quit +'git cherry-pick' --abort DESCRIPTION ----------- diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index b0fcabc8b6..b699a3458e 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] ... 'git revert' --continue 'git revert' --quit +'git revert' --abort DESCRIPTION ----------- diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt index 75f8e869e8..5747f442f2 100644 --- a/Documentation/sequencer.txt +++ b/Documentation/sequencer.txt @@ -7,3 +7,6 @@ Forget about the current operation in progress. Can be used to clear the sequencer state after a failed cherry-pick or revert. + +--abort:: + Cancel the operation and return to the pre-sequence state. diff --git a/builtin/revert.c b/builtin/revert.c index f5ba67a505..70a5fbb672 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -40,7 +40,12 @@ static const char * const cherry_pick_usage[] = { }; enum replay_action { REVERT, CHERRY_PICK }; -enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, REPLAY_CONTINUE }; +enum replay_subcommand { + REPLAY_NONE, + REPLAY_REMOVE_STATE, + REPLAY_CONTINUE, + REPLAY_ROLLBACK +}; struct replay_opts { enum replay_action action; @@ -135,9 +140,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) const char *me = action_name(opts); int remove_state = 0; int contin = 0; + int rollback = 0; struct option options[] = { OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"), OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"), + OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"), OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"), OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"), OPT_NOOP_NOARG('r', NULL), @@ -173,6 +180,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) verify_opt_mutually_compatible(me, "--quit", remove_state, "--continue", contin, + "--abort", rollback, NULL); /* Set the subcommand */ @@ -180,6 +188,8 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) opts->subcommand = REPLAY_REMOVE_STATE; else if (contin) opts->subcommand = REPLAY_CONTINUE; + else if (rollback) + opts->subcommand = REPLAY_ROLLBACK; else opts->subcommand = REPLAY_NONE; @@ -188,8 +198,12 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) char *this_operation; if (opts->subcommand == REPLAY_REMOVE_STATE) this_operation = "--quit"; - else + else if (opts->subcommand == REPLAY_CONTINUE) this_operation = "--continue"; + else { + assert(opts->subcommand == REPLAY_ROLLBACK); + this_operation = "--abort"; + } verify_opt_compatible(me, this_operation, "--no-commit", opts->no_commit, @@ -850,7 +864,7 @@ static int create_seq_dir(void) if (file_exists(seq_dir)) { error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit)\"")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; } else if (mkdir(seq_dir, 0777) < 0) @@ -873,6 +887,71 @@ static void save_head(const char *head) die(_("Error wrapping up %s."), head_file); } +static int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (!resolve_ref("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + goto fail; + } + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + strbuf_release(&buf); + fclose(f); + return 0; +fail: + strbuf_release(&buf); + fclose(f); + return -1; +} + static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) { const char *todo_file = git_path(SEQ_TODO_FILE); @@ -977,6 +1056,8 @@ static int pick_revisions(struct replay_opts *opts) remove_sequencer_state(1); return 0; } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); if (opts->subcommand == REPLAY_CONTINUE) { if (!file_exists(git_path(SEQ_TODO_FILE))) return error(_("No %s in progress"), action_name(opts)); diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index bb67cdcb5d..e97397f96b 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -2,6 +2,7 @@ test_description='Test cherry-pick continuation features + + yetanotherpick: rewrites foo to e + anotherpick: rewrites foo to d + picked: rewrites foo to c + unrelatedpick: rewrites unrelated to reallyunrelated @@ -19,6 +20,12 @@ pristine_detach () { git clean -d -f -f -q -x } +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + test_expect_success setup ' echo unrelated >unrelated && git add unrelated && @@ -27,6 +34,7 @@ test_expect_success setup ' test_commit unrelatedpick unrelated reallyunrelated && test_commit picked foo c && test_commit anotherpick foo d && + test_commit yetanotherpick foo e && git config advice.detachedhead false ' @@ -75,6 +83,11 @@ test_expect_success '--quit does not complain when no cherry-pick is in progress git cherry-pick --quit ' +test_expect_success '--abort requires cherry-pick in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --abort +' + test_expect_success '--quit cleans up sequencer state' ' pristine_detach initial && test_must_fail git cherry-pick base..picked && @@ -103,6 +116,79 @@ test_expect_success 'cherry-pick --reset (another name for --quit)' ' test_cmp expect actual ' +test_expect_success '--abort to cancel multiple cherry-pick' ' + pristine_detach initial && + test_must_fail git cherry-pick base..anotherpick && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success '--abort to cancel single cherry-pick' ' + pristine_detach initial && + test_must_fail git cherry-pick picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success 'cherry-pick --abort to cancel multiple revert' ' + pristine_detach anotherpick && + test_must_fail git revert base..picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success 'revert --abort works, too' ' + pristine_detach anotherpick && + test_must_fail git revert base..picked && + git revert --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD +' + +test_expect_success '--abort to cancel single revert' ' + pristine_detach anotherpick && + test_must_fail git revert picked && + git revert --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev anotherpick HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + +test_expect_success '--abort keeps unrelated change, easy case' ' + pristine_detach unrelatedpick && + echo changed >expect && + test_must_fail git cherry-pick picked..yetanotherpick && + echo changed >unrelated && + git cherry-pick --abort && + test_cmp expect unrelated +' + +test_expect_success '--abort refuses to clobber unrelated change, harder case' ' + pristine_detach initial && + echo changed >expect && + test_must_fail git cherry-pick base..anotherpick && + echo changed >unrelated && + test_must_fail git cherry-pick --abort && + test_cmp expect unrelated && + git rev-list HEAD >log && + test_line_count = 2 log && + test_must_fail git update-index --refresh && + + git checkout unrelated && + git cherry-pick --abort && + test_cmp_rev initial HEAD +' + test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' ' pristine_detach initial && test_must_fail git cherry-pick base..picked && @@ -127,6 +213,16 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le test_cmp expect actual ' +test_expect_failure '--abort after last commit in sequence' ' + pristine_detach initial && + test_must_fail git cherry-pick base..picked && + git cherry-pick --abort && + test_path_is_missing .git/sequencer && + test_cmp_rev initial HEAD && + git update-index --refresh && + git diff-index --exit-code HEAD +' + test_expect_success 'cherry-pick does not implicitly stomp an existing operation' ' pristine_detach initial && test_must_fail git cherry-pick base..anotherpick && -- cgit v1.2.1 From c427b211b3f5d3f967e5fc7abd526b02f83a7246 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 22 Nov 2011 05:20:46 -0600 Subject: revert: remove --reset compatibility option Remove the "git cherry-pick --reset" option, which has a different preferred spelling nowadays ("--quit"). Luckily the old --reset name was not around long enough for anyone to get used to it. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/revert.c | 3 --- t/t3510-cherry-pick-sequence.sh | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 70a5fbb672..0c61668b85 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -154,9 +154,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"), OPT_CALLBACK('X', "strategy-option", &opts, "option", "option for merge strategy", option_parse_x), - { OPTION_BOOLEAN, 0, "reset", &remove_state, NULL, - "alias for --quit (deprecated)", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, OPT_END(), OPT_END(), OPT_END(), diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index e97397f96b..2c4c1c851d 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -95,7 +95,7 @@ test_expect_success '--quit cleans up sequencer state' ' test_path_is_missing .git/sequencer ' -test_expect_success 'cherry-pick --reset (another name for --quit)' ' +test_expect_success '--quit keeps HEAD and conflicted index intact' ' pristine_detach initial && cat >expect <<-\EOF && OBJID @@ -105,7 +105,7 @@ test_expect_success 'cherry-pick --reset (another name for --quit)' ' :000000 100644 OBJID OBJID A unrelated EOF test_must_fail git cherry-pick base..picked && - git cherry-pick --reset && + git cherry-pick --quit && test_path_is_missing .git/sequencer && test_must_fail git update-index --refresh && { -- cgit v1.2.1