X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=builtin%2Frevert.c;h=00df5266d574ea1f8c9ab745ed606bac566392b5;hb=6d5c16a90ccf5724fb885d4b8af295c640da33a4;hp=1d112e4ce435ba608c1ffbf9d916e739db8a8050;hpb=b8c74690b20b7c4dd405f71d63bab325447356da;p=git.git diff --git a/builtin/revert.c b/builtin/revert.c index 1d112e4ce..00df5266d 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; @@ -55,13 +60,14 @@ struct replay_opts { int allow_rerere_auto; int mainline; - int commit_argc; - const char **commit_argv; /* Merge strategy */ const char *strategy; const char **xopts; size_t xopts_nr, xopts_alloc; + + /* Only used by REPLAY_NONE */ + struct rev_info *revs; }; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -135,9 +141,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), @@ -147,9 +155,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(), @@ -165,14 +170,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) die(_("program error")); } - opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str, - PARSE_OPT_KEEP_ARGV0 | - PARSE_OPT_KEEP_UNKNOWN); + argc = parse_options(argc, argv, NULL, options, usage_str, + PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN); /* Check for incompatible subcommands */ verify_opt_mutually_compatible(me, "--quit", remove_state, "--continue", contin, + "--abort", rollback, NULL); /* Set the subcommand */ @@ -180,6 +186,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 +196,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, @@ -202,9 +214,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) NULL); } - else if (opts->commit_argc < 2) - usage_with_options(usage_str, options); - if (opts->allow_ff) verify_opt_compatible(me, "--ff", "--signoff", opts->signoff, @@ -212,7 +221,20 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) "-x", opts->record_origin, "--edit", opts->edit, NULL); - opts->commit_argv = argv; + + if (opts->subcommand != REPLAY_NONE) { + opts->revs = NULL; + } else { + opts->revs = xmalloc(sizeof(*opts->revs)); + init_revisions(opts->revs, NULL); + opts->revs->no_walk = 1; + if (argc < 2) + usage_with_options(usage_str, options); + argc = setup_revisions(argc, argv, opts->revs, NULL); + } + + if (argc > 1) + usage_with_options(usage_str, options); } struct commit_message { @@ -289,7 +311,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 +319,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("%s", pseudoref); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); @@ -321,11 +343,10 @@ static void print_advice(int show_hint) return; } - if (show_hint) { - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add ' or 'git rm '"); - advise("and commit the result with 'git commit'"); - } + if (show_hint) + advise(_("after resolving the conflicts, mark the corrected paths\n" + "with 'git add ' or 'git rm '\n" + "and commit the result with 'git commit'")); } static void write_message(struct strbuf *msgbuf, const char *filename) @@ -597,7 +618,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 @@ -618,23 +641,15 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) return res; } -static void prepare_revs(struct rev_info *revs, struct replay_opts *opts) +static void prepare_revs(struct replay_opts *opts) { - int argc; - - init_revisions(revs, NULL); - revs->no_walk = 1; if (opts->action != REVERT) - revs->reverse = 1; + opts->revs->reverse ^= 1; - argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL); - if (argc > 1) - usage(*revert_or_cherry_pick_usage(opts)); - - if (prepare_revision_walk(revs)) + if (prepare_revision_walk(opts->revs)) die(_("revision walk setup failed")); - if (!revs->commits) + if (!opts->revs->commits) die(_("empty commit set passed")); } @@ -831,14 +846,13 @@ static void read_populate_opts(struct replay_opts **opts_ptr) static void walk_revs_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { - struct rev_info revs; struct commit *commit; struct commit_list **next; - prepare_revs(&revs, opts); + prepare_revs(opts); next = todo_list; - while ((commit = get_revision(&revs))) + while ((commit = get_revision(opts->revs))) next = commit_list_append(commit, next); } @@ -848,7 +862,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) @@ -871,6 +885,72 @@ 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")); + fclose(f); + goto fail; + } + fclose(f); + 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; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) { const char *todo_file = git_path(SEQ_TODO_FILE); @@ -937,33 +1017,64 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) for (cur = todo_list; cur; cur = cur->next) { save_todo(cur, opts); res = do_pick_commit(cur->item, opts); - if (res) { - if (!cur->next) - /* - * An error was encountered while - * picking the last commit; the - * sequencer state is useless now -- - * the user simply needs to resolve - * the conflict and commit - */ - remove_sequencer_state(0); + if (res) return res; - } } /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory */ - remove_sequencer_state(1); + remove_sequencer_state(); return 0; } +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + read_populate_opts(&opts); + read_populate_todo(&todo_list, opts); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + return do_pick_commit(cmit, opts); +} + static int pick_revisions(struct replay_opts *opts) { struct commit_list *todo_list = NULL; unsigned char sha1[20]; + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + read_and_refresh_cache(opts); /* @@ -972,19 +1083,32 @@ static int pick_revisions(struct replay_opts *opts) * one that is being continued */ if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(1); + remove_sequencer_state(); return 0; } - if (opts->subcommand == REPLAY_CONTINUE) { - if (!file_exists(git_path(SEQ_TODO_FILE))) - 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; - return pick_commits(todo_list, opts); + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick ", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); } /*