diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index fc3b993c3338b5..a5dff10fa23cc9 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -142,10 +142,6 @@ default. You can use `--no-utf8` to override this. user to lie about the author date by using the same value as the committer date. ---skip:: - Skip the current patch. This is only meaningful when - restarting an aborted patch. - -S[]:: --gpg-sign[=]:: GPG-sign commits. The `keyid` argument is optional and @@ -162,6 +158,10 @@ default. You can use `--no-utf8` to override this. extracted from the e-mail message and the current index file, and continue. +--skip:: + Skip the current patch. This is only meaningful when + restarting an aborted patch. + --resolvemsg=:: When a patch failure occurs, will be printed to the screen before exiting. This overrides the diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 754b16ce0c9da6..83ce51aedfea54 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -10,9 +10,7 @@ SYNOPSIS [verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] [-S[]] ... -'git cherry-pick' --continue -'git cherry-pick' --quit -'git cherry-pick' --abort +'git cherry-pick' (--continue | --skip | --abort | --quit) DESCRIPTION ----------- diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 6294dbc09d2138..740407855e0584 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -13,8 +13,7 @@ SYNOPSIS [-s ] [-X ] [-S[]] [--[no-]allow-unrelated-histories] [--[no-]rerere-autoupdate] [-m ] [-F ] [...] -'git merge' --abort -'git merge' --continue +'git merge' (--continue | --skip | --abort | --quit) DESCRIPTION ----------- @@ -88,6 +87,11 @@ will be appended to the specified message. Allow the rerere mechanism to update the index with the result of auto-conflict resolution if possible. +--continue:: + After a 'git merge' stops due to conflicts you can conclude the + merge by running 'git merge --continue' (see "HOW TO RESOLVE + CONFLICTS" section below). + --abort:: Abort the current conflict resolution process, and try to reconstruct the pre-merge state. @@ -100,10 +104,9 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. ---continue:: - After a 'git merge' stops due to conflicts you can conclude the - merge by running 'git merge --continue' (see "HOW TO RESOLVE - CONFLICTS" section below). +--quit:: + Forget about the current merge in progress. Leave the index + and the working tree as-is. ...:: Commits, usually other branch heads, to merge into our branch. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 5e4e9276479c94..ededa36dc60ae2 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -12,7 +12,7 @@ SYNOPSIS [ []] 'git rebase' [-i | --interactive] [] [--exec ] [--onto ] --root [] -'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch +'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch) DESCRIPTION ----------- @@ -228,6 +228,9 @@ leave out at most one of A and B, in which case it defaults to HEAD. --continue:: Restart the rebasing process after having resolved a merge conflict. +--skip:: + Restart the rebasing process by skipping the current patch. + --abort:: Abort the rebase operation and reset HEAD to the original branch. If was provided when the rebase operation was @@ -240,6 +243,14 @@ leave out at most one of A and B, in which case it defaults to HEAD. original branch. The index and working tree are also left unchanged as a result. +--edit-todo:: + Edit the todo list during an interactive rebase. + +--show-current-patch:: + Show the current patch in an interactive rebase or when rebase + is stopped because of conflicts. This is the equivalent of + `git show REBASE_HEAD`. + --keep-empty:: Keep the commits that do not change anything from its parents in the result. @@ -253,17 +264,6 @@ See also INCOMPATIBLE OPTIONS below. + See also INCOMPATIBLE OPTIONS below. ---skip:: - Restart the rebasing process by skipping the current patch. - ---edit-todo:: - Edit the todo list during an interactive rebase. - ---show-current-patch:: - Show the current patch in an interactive rebase or when rebase - is stopped because of conflicts. This is the equivalent of - `git show REBASE_HEAD`. - -m:: --merge:: Use merging strategies to rebase. When the recursive (default) merge diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 0c82ca5bc0e5a3..665e065ee378f6 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -9,9 +9,7 @@ SYNOPSIS -------- [verse] 'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[]] ... -'git revert' --continue -'git revert' --quit -'git revert' --abort +'git revert' (--continue | --skip | --abort | --quit) DESCRIPTION ----------- diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt index 5a57c4a4077f0b..4db07ea723a7ee 100644 --- a/Documentation/sequencer.txt +++ b/Documentation/sequencer.txt @@ -3,10 +3,14 @@ `.git/sequencer`. Can be used to continue after resolving conflicts in a failed cherry-pick or revert. +--skip:: + Skip the current commit and continue with the rest of the + sequence. + +--abort:: + Cancel the operation and return to the pre-sequence state. + --quit:: 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/branch.c b/branch.c index a594cc23e25458..4b345b84810bf9 100644 --- a/branch.c +++ b/branch.c @@ -338,14 +338,20 @@ void create_branch(struct repository *r, free(real_ref); } -void remove_branch_state(struct repository *r) +void remove_merge_branch_state(struct repository *r) { - sequencer_post_commit_cleanup(r); unlink(git_path_merge_head(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); +} + +void remove_branch_state(struct repository *r) +{ + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_revert_head(r)); unlink(git_path_squash_msg(r)); + remove_merge_branch_state(r); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index 6f38db14e9c496..064ee576f29764 100644 --- a/branch.h +++ b/branch.h @@ -60,6 +60,12 @@ int validate_branchname(const char *name, struct strbuf *ref); */ int validate_new_branchname(const char *name, struct strbuf *ref, int force); +/* + * Remove information about the merge state on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_merge_branch_state(struct repository *r); + /* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) diff --git a/builtin/am.c b/builtin/am.c index 912d9821b18acf..74a88221123225 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2164,11 +2164,30 @@ int cmd_am(int argc, const char **argv, const char *prefix) const char * const usage[] = { N_("git am [] [( | )...]"), - N_("git am [] (--continue | --skip | --abort)"), + N_("git am [] (--continue | --skip | --abort | --quit" + " | --show-current-patch)"), NULL }; struct option options[] = { + OPT_CMDMODE(0, "continue", &resume, + N_("continue applying patches after resolving a conflict"), + RESUME_RESOLVED), + OPT_CMDMODE(0, "skip", &resume, + N_("skip the current patch"), + RESUME_SKIP), + OPT_CMDMODE(0, "abort", &resume, + N_("restore the original branch and abort the patching operation."), + RESUME_ABORT), + OPT_CMDMODE(0, "quit", &resume, + N_("abort the patching operation but keep HEAD where it is."), + RESUME_QUIT), + OPT_CMDMODE(0, "show-current-patch", &resume, + N_("show the patch being applied."), + RESUME_SHOW_PATCH), + OPT_CMDMODE('r', "resolved", &resume, + N_("synonym for --continue"), + RESUME_RESOLVED), OPT_BOOL('i', "interactive", &state.interactive, N_("run interactively")), OPT_HIDDEN_BOOL('b', "binary", &binary, @@ -2227,24 +2246,6 @@ int cmd_am(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), - OPT_CMDMODE(0, "continue", &resume, - N_("continue applying patches after resolving a conflict"), - RESUME_RESOLVED), - OPT_CMDMODE('r', "resolved", &resume, - N_("synonyms for --continue"), - RESUME_RESOLVED), - OPT_CMDMODE(0, "skip", &resume, - N_("skip the current patch"), - RESUME_SKIP), - OPT_CMDMODE(0, "abort", &resume, - N_("restore the original branch and abort the patching operation."), - RESUME_ABORT), - OPT_CMDMODE(0, "quit", &resume, - N_("abort the patching operation but keep HEAD where it is."), - RESUME_QUIT), - OPT_CMDMODE(0, "show-current-patch", &resume, - N_("show the patch being applied."), - RESUME_SHOW_PATCH), OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), diff --git a/builtin/commit.c b/builtin/commit.c index 1c9e8e2228c7ce..1f47c51bdcabd4 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -60,15 +60,18 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ "\n"); static const char empty_cherry_pick_advice_single[] = -N_("Otherwise, please use 'git reset'\n"); +N_("Otherwise, please use 'git cherry-pick --skip'\n"); static const char empty_cherry_pick_advice_multi[] = -N_("If you wish to skip this commit, use:\n" +N_("and then use:\n" "\n" -" git reset\n" +" git cherry-pick --continue\n" "\n" -"Then \"git cherry-pick --continue\" will resume cherry-picking\n" -"the remaining commits.\n"); +"to resume cherry-picking the remaining commits.\n" +"If you wish to skip this commit, use:\n" +"\n" +" git cherry-pick --skip\n" +"\n"); static const char *color_status_slots[] = { [WT_STATUS_HEADER] = "header", diff --git a/builtin/merge.c b/builtin/merge.c index e96f72af804476..7153c4bd0db1c2 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -37,6 +37,7 @@ #include "packfile.h" #include "tag.h" #include "alias.h" +#include "branch.h" #include "commit-reach.h" #include "wt-status.h" @@ -52,8 +53,7 @@ struct strategy { static const char * const builtin_merge_usage[] = { N_("git merge [] [...]"), - N_("git merge --abort"), - N_("git merge --continue"), + N_("git merge (--continue | --skip | --abort | --quit)"), NULL }; @@ -73,6 +73,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; @@ -238,6 +239,12 @@ static int option_parse_n(const struct option *opt, } static struct option builtin_merge_options[] = { + OPT_BOOL(0, "continue", &continue_current_merge, + N_("continue the current in-progress merge")), + OPT_BOOL(0, "abort", &abort_current_merge, + N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), { OPTION_CALLBACK, 'n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), PARSE_OPT_NOARG, option_parse_n }, @@ -272,10 +279,6 @@ static struct option builtin_merge_options[] = { N_("read message from file"), PARSE_OPT_NONEG, NULL, 0, option_read_message }, OPT__VERBOSITY(&verbosity), - OPT_BOOL(0, "abort", &abort_current_merge, - N_("abort the current in-progress merge")), - OPT_BOOL(0, "continue", &continue_current_merge, - N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), @@ -287,14 +290,6 @@ static struct option builtin_merge_options[] = { OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); - unlink(git_path_merge_mode(the_repository)); -} - static int save_state(struct object_id *stash) { int len; @@ -388,7 +383,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -881,7 +876,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -907,7 +902,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -1289,6 +1284,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); + goto done; + } + if (continue_current_merge) { int nargc = 1; const char *nargv[] = {"commit", NULL}; @@ -1495,7 +1500,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; diff --git a/builtin/rebase.c b/builtin/rebase.c index db6ca9bd7d4c4e..def9a02ba92052 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -33,7 +33,8 @@ static char const * const builtin_rebase_usage[] = { "[] []"), N_("git rebase [-i] [options] [--exec ] [--onto ] " "--root []"), - N_("git rebase --continue | --abort | --skip | --edit-todo"), + N_("git rebase (--continue | --skip | --abort | --quit | --edit-todo | " + "--show-current-patch)"), NULL }; @@ -1385,6 +1386,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct object_id squash_onto; char *squash_onto_name = NULL; struct option builtin_rebase_options[] = { + OPT_CMDMODE(0, "continue", &action, N_("continue"), + ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", &action, + N_("skip current patch and continue"), ACTION_SKIP), + OPT_CMDMODE(0, "abort", &action, + N_("abort and check out the original branch"), + ACTION_ABORT), + OPT_CMDMODE(0, "quit", &action, + N_("abort but keep HEAD where it is"), ACTION_QUIT), + OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list " + "during an interactive rebase"), ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &action, + N_("show the patch file being applied or merged"), + ACTION_SHOW_CURRENT_PATCH), OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), @@ -1419,20 +1434,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-ff", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), - OPT_CMDMODE(0, "continue", &action, N_("continue"), - ACTION_CONTINUE), - OPT_CMDMODE(0, "skip", &action, - N_("skip current patch and continue"), ACTION_SKIP), - OPT_CMDMODE(0, "abort", &action, - N_("abort and check out the original branch"), - ACTION_ABORT), - OPT_CMDMODE(0, "quit", &action, - N_("abort but keep HEAD where it is"), ACTION_QUIT), - OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list " - "during an interactive rebase"), ACTION_EDIT_TODO), - OPT_CMDMODE(0, "show-current-patch", &action, - N_("show the patch file being applied or merged"), - ACTION_SHOW_CURRENT_PATCH), { OPTION_CALLBACK, 'm', "merge", &options, NULL, N_("use merging strategies to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, diff --git a/builtin/revert.c b/builtin/revert.c index d4dcedbdc683f9..1cc512d9b54ac0 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -22,13 +22,13 @@ static const char * const revert_usage[] = { N_("git revert [] ..."), - N_("git revert "), + N_("git revert (--continue | --skip | --abort | --quit)"), NULL }; static const char * const cherry_pick_usage[] = { N_("git cherry-pick [] ..."), - N_("git cherry-pick "), + N_("git cherry-pick (--continue | --skip | --abort | --quit)"), NULL }; @@ -99,9 +99,10 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) const char *cleanup_arg = NULL; int cmd = 0; struct option base_options[] = { - OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'), OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'), + OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'), OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'), + OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'), OPT_CLEANUP(&cleanup_arg), OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")), @@ -151,6 +152,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) this_operation = "--quit"; else if (cmd == 'c') this_operation = "--continue"; + else if (cmd == 's') + this_operation = "--skip"; else { assert(cmd == 'a'); this_operation = "--abort"; @@ -210,6 +213,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) return sequencer_continue(the_repository, opts); if (cmd == 'a') return sequencer_rollback(the_repository, opts); + if (cmd == 's') + return sequencer_skip(the_repository, opts); return sequencer_pick_revisions(the_repository, opts); } diff --git a/sequencer.c b/sequencer.c index f88a97fb10a322..6e8afe8f223de1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2650,15 +2650,42 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, return 0; } -static int create_seq_dir(void) +static int create_seq_dir(struct repository *r) { - if (file_exists(git_path_seq_dir())) { - error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + enum replay_action action; + const char *in_progress_error = NULL; + const char *in_progress_advice = NULL; + unsigned int advise_skip = file_exists(git_path_revert_head(r)) || + file_exists(git_path_cherry_pick_head(r)); + + if (!sequencer_get_last_command(r, &action)) { + switch (action) { + case REPLAY_REVERT: + in_progress_error = _("revert is already in progress"); + in_progress_advice = + _("try \"git revert (--continue | %s--abort | --quit)\""); + break; + case REPLAY_PICK: + in_progress_error = _("cherry-pick is already in progress"); + in_progress_advice = + _("try \"git cherry-pick (--continue | %s--abort | --quit)\""); + break; + default: + BUG(_("unexpected action in create_seq_dir")); + } + } + if (in_progress_error) { + error("%s", in_progress_error); + if (advise_skip) + advise(in_progress_advice, "--skip | "); + else + advise(in_progress_advice, ""); return -1; - } else if (mkdir(git_path_seq_dir(), 0777) < 0) + } + if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); + return 0; } @@ -2709,15 +2736,20 @@ static int rollback_is_safe(void) return oideq(&actual_head, &expected_head); } -static int reset_for_rollback(const struct object_id *oid) +static int reset_merge(const struct object_id *oid) { - const char *argv[4]; /* reset --merge + NULL */ + int ret; + struct argv_array argv = ARGV_ARRAY_INIT; - argv[0] = "reset"; - argv[1] = "--merge"; - argv[2] = oid_to_hex(oid); - argv[3] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); + argv_array_pushl(&argv, "reset", "--merge", NULL); + + if (!is_null_oid(oid)) + argv_array_push(&argv, oid_to_hex(oid)); + + ret = run_command_v_opt(argv.argv, RUN_GIT_CMD); + argv_array_clear(&argv); + + return ret; } static int rollback_single_pick(struct repository *r) @@ -2731,7 +2763,16 @@ static int rollback_single_pick(struct repository *r) return error(_("cannot resolve HEAD")); if (is_null_oid(&head_oid)) return error(_("cannot abort from a branch yet to be born")); - return reset_for_rollback(&head_oid); + return reset_merge(&head_oid); +} + +static int skip_single_pick(void) +{ + struct object_id head; + + if (read_ref_full("HEAD", 0, &head, NULL)) + return error(_("cannot resolve HEAD")); + return reset_merge(&head); } int sequencer_rollback(struct repository *r, struct replay_opts *opts) @@ -2774,7 +2815,7 @@ int sequencer_rollback(struct repository *r, struct replay_opts *opts) warning(_("You seem to have moved HEAD. " "Not rewinding, check your HEAD!")); } else - if (reset_for_rollback(&oid)) + if (reset_merge(&oid)) goto fail; strbuf_release(&buf); return sequencer_remove_state(opts); @@ -2783,6 +2824,74 @@ int sequencer_rollback(struct repository *r, struct replay_opts *opts) return -1; } +int sequencer_skip(struct repository *r, struct replay_opts *opts) +{ + enum replay_action action = -1; + sequencer_get_last_command(r, &action); + + /* + * opts->action tells us which subcommand requested to skip + * the commit. + */ + switch (opts->action) { + case REPLAY_REVERT: + /* + * If .git/REVERT_HEAD exists then we are sure that we are in + * the middle of a revert and we allow to skip the commit. + */ + if (!file_exists(git_path_revert_head(r))) { + /* + * Check if the last instruction executed was related to + * revert. If so, we are sure that a revert is in progress. + * + * NB: single commit revert is also counted in this + * definition of "progress" (and was dealt with in the + * previous check). + */ + if (action == REPLAY_REVERT) { + /* + * Check if the user has moved the HEAD, i.e., + * already committed. In this case, we would like + * to advise instead of skipping. + */ + if (!rollback_is_safe()) + goto give_advice; + else + /* skip commit :) */ + break; + } + return error(_("no revert in progress")); + } + break; + case REPLAY_PICK: + if (!file_exists(git_path_cherry_pick_head(r))) { + if (action == REPLAY_PICK) { + if (!rollback_is_safe()) + goto give_advice; + else + break; + } + return error(_("no cherry-pick in progress")); + } + break; + default: + BUG("unexpected action in sequencer_skip"); + } + + if (skip_single_pick()) + return error(_("failed to skip the commit")); + if (!is_directory(git_path_seq_dir())) + return 0; + + return sequencer_continue(r, opts); + +give_advice: + advise(_("have you committed already?\n" + "try \"git %s --continue\""), + action == REPLAY_REVERT ? "revert" : "cherry-pick"); + return error(_("there is nothing to skip")); +} + static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) { struct lock_file todo_lock = LOCK_INIT; @@ -4237,7 +4346,7 @@ int sequencer_pick_revisions(struct repository *r, */ if (walk_revs_populate_todo(&todo_list, opts) || - create_seq_dir() < 0) + create_seq_dir(r) < 0) return -1; if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT)) return error(_("can't revert as initial commit")); diff --git a/sequencer.h b/sequencer.h index 0c494b83d43e2c..731b9853ebd265 100644 --- a/sequencer.h +++ b/sequencer.h @@ -129,6 +129,7 @@ int sequencer_pick_revisions(struct repository *repo, struct replay_opts *opts); int sequencer_continue(struct repository *repo, struct replay_opts *opts); int sequencer_rollback(struct repository *repo, struct replay_opts *opts); +int sequencer_skip(struct repository *repo, struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); #define TODO_LIST_KEEP_EMPTY (1U << 0) diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 941d5026da2adc..0e8adc95fcdadc 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -93,6 +93,128 @@ test_expect_success 'cherry-pick cleans up sequencer state upon success' ' test_path_is_missing .git/sequencer ' +test_expect_success 'cherry-pick --skip requires cherry-pick in progress' ' + pristine_detach initial && + test_must_fail git cherry-pick --skip +' + +test_expect_success 'revert --skip requires revert in progress' ' + pristine_detach initial && + test_must_fail git revert --skip +' + +test_expect_success 'cherry-pick --skip to skip commit' ' + pristine_detach initial && + test_must_fail git cherry-pick anotherpick && + test_must_fail git revert --skip && + git cherry-pick --skip && + test_cmp_rev initial HEAD && + test_path_is_missing .git/CHERRY_PICK_HEAD +' + +test_expect_success 'revert --skip to skip commit' ' + pristine_detach anotherpick && + test_must_fail git revert anotherpick~1 && + test_must_fail git cherry-pick --skip && + git revert --skip && + test_cmp_rev anotherpick HEAD +' + +test_expect_success 'skip "empty" commit' ' + pristine_detach picked && + test_commit dummy foo d && + test_must_fail git cherry-pick anotherpick && + git cherry-pick --skip && + test_cmp_rev dummy HEAD +' + +test_expect_success 'skip a commit and check if rest of sequence is correct' ' + pristine_detach initial && + echo e >expect && + cat >expect.log <<-EOF && + OBJID + :100644 100644 OBJID OBJID M foo + OBJID + :100644 100644 OBJID OBJID M foo + 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..yetanotherpick && + test_must_fail git cherry-pick --skip && + echo d >foo && + git add foo && + git cherry-pick --continue && + { + git rev-list HEAD | + git diff-tree --root --stdin | + sed "s/$OID_REGEX/OBJID/g" + } >actual.log && + test_cmp expect foo && + test_cmp expect.log actual.log +' + +test_expect_success 'check advice when we move HEAD by committing' ' + pristine_detach initial && + cat >expect <<-EOF && + hint: have you committed already? + hint: try "git cherry-pick --continue" + error: there is nothing to skip + fatal: cherry-pick failed + EOF + test_must_fail git cherry-pick base..yetanotherpick && + echo c >foo && + git commit -a && + test_path_is_missing .git/CHERRY_PICK_HEAD && + test_must_fail git cherry-pick --skip 2>advice && + test_i18ncmp expect advice +' + +test_expect_success 'selectively advise --skip while launching another sequence' ' + pristine_detach initial && + cat >expect <<-EOF && + error: cherry-pick is already in progress + hint: try "git cherry-pick (--continue | --skip | --abort | --quit)" + fatal: cherry-pick failed + EOF + test_must_fail git cherry-pick picked..yetanotherpick && + test_must_fail git cherry-pick picked..yetanotherpick 2>advice && + test_i18ncmp expect advice && + cat >expect <<-EOF && + error: cherry-pick is already in progress + hint: try "git cherry-pick (--continue | --abort | --quit)" + fatal: cherry-pick failed + EOF + git reset --merge && + test_must_fail git cherry-pick picked..yetanotherpick 2>advice && + test_i18ncmp expect advice +' + +test_expect_success 'allow skipping commit but not abort for a new history' ' + pristine_detach initial && + cat >expect <<-EOF && + error: cannot abort from a branch yet to be born + fatal: cherry-pick failed + EOF + git checkout --orphan new_disconnected && + git reset --hard && + test_must_fail git cherry-pick anotherpick && + test_must_fail git cherry-pick --abort 2>advice && + git cherry-pick --skip && + test_i18ncmp expect advice +' + +test_expect_success 'allow skipping stopped cherry-pick because of untracked file modifications' ' + pristine_detach initial && + git rm --cached unrelated && + git commit -m "untrack unrelated" && + test_must_fail git cherry-pick initial base && + test_path_is_missing .git/CHERRY_PICK_HEAD && + git cherry-pick --skip +' + test_expect_success '--quit does not complain when no cherry-pick is in progress' ' pristine_detach initial && git cherry-pick --quit diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 7f9c68cbe75a68..3e16aaed3b775b 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -867,4 +867,30 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' verify_parents $c0 $c1 ' +test_expect_success 'merge --quit' ' + git init merge-quit && + ( + cd merge-quit && + test_commit base && + echo one >>base.t && + git commit -am one && + git branch one && + git checkout base && + echo two >>base.t && + git commit -am two && + test_must_fail git -c rerere.enabled=true merge one && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + git rerere status >rerere.before && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + git rerere status >rerere.after && + test_must_be_empty rerere.after && + ! test_cmp rerere.after rerere.before + ) +' + test_done