general improvements
[gitweb.git] / sequencer.c
index b91c981b326a16122ec1a5cbbd9f2c545115fa8c..d648aaf416510e656a372abe54d1a28d995de889 100644 (file)
@@ -2654,15 +2654,41 @@ 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 (advice_sequencer_in_use)
+                       advise(in_progress_advice,
+                               advise_skip ? "--skip | " : "");
                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;
 }
 
@@ -2713,15 +2739,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 <arg> + 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)
@@ -2735,7 +2766,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)
@@ -2778,7 +2818,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);
@@ -2787,6 +2827,70 @@ 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);
+
+       /*
+        * Check whether the subcommand requested to skip the commit is actually
+        * in progress and that it's safe to skip the commit.
+        *
+        * opts->action tells us which subcommand requested to skip the commit.
+        * If the corresponding .git/<ACTION>_HEAD exists, we know that the
+        * action is in progress and we can skip the commit.
+        *
+        * Otherwise we check that the last instruction was related to the
+        * particular subcommand we're trying to execute and barf if that's not
+        * the case.
+        *
+        * Finally we check that the rollback is "safe", i.e., has the HEAD
+        * moved? In this case, it doesn't make sense to "reset the merge" and
+        * "skip the commit" as the user already handled this by committing. But
+        * we'd not want to barf here, instead give advice on how to proceed. We
+        * only need to check that when .git/<ACTION>_HEAD doesn't exist because
+        * it gets removed when the user commits, so if it still exists we're
+        * sure the user can't have committed before.
+        */
+       switch (opts->action) {
+       case REPLAY_REVERT:
+               if (!file_exists(git_path_revert_head(r))) {
+                       if (action != REPLAY_REVERT)
+                               return error(_("no revert in progress"));
+                       if (!rollback_is_safe())
+                               goto give_advice;
+               }
+               break;
+       case REPLAY_PICK:
+               if (!file_exists(git_path_cherry_pick_head(r))) {
+                       if (action != REPLAY_PICK)
+                               return error(_("no cherry-pick in progress"));
+                       if (!rollback_is_safe())
+                               goto give_advice;
+               }
+               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:
+       error(_("there is nothing to skip"));
+
+       if (advice_resolve_conflict) {
+               advise(_("have you committed already?\n"
+                        "try \"git %s --continue\""),
+                        action == REPLAY_REVERT ? "revert" : "cherry-pick");
+       }
+       return -1;
+}
+
 static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
 {
        struct lock_file todo_lock = LOCK_INIT;
@@ -3198,7 +3302,7 @@ static int do_reset(struct repository *r,
                return error_resolve_conflict(_(action_name(opts)));
        }
 
-       if (!fill_tree_descriptor(&desc, &oid)) {
+       if (!fill_tree_descriptor(r, &desc, &oid)) {
                error(_("failed to find tree of %s"), oid_to_hex(&oid));
                rollback_lock_file(&lock);
                free((void *)desc.buffer);
@@ -3260,6 +3364,9 @@ static int do_merge(struct repository *r,
        struct commit *head_commit, *merge_commit, *i;
        struct commit_list *bases, *j, *reversed = NULL;
        struct commit_list *to_merge = NULL, **tail = &to_merge;
+       const char *strategy = !opts->xopts_nr &&
+               (!opts->strategy || !strcmp(opts->strategy, "recursive")) ?
+               NULL : opts->strategy;
        struct merge_options o;
        int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
        static struct lock_file lock;
@@ -3412,7 +3519,7 @@ static int do_merge(struct repository *r,
                goto leave_merge;
        }
 
-       if (to_merge->next) {
+       if (strategy || to_merge->next) {
                /* Octopus merge */
                struct child_process cmd = CHILD_PROCESS_INIT;
 
@@ -3426,7 +3533,14 @@ static int do_merge(struct repository *r,
                cmd.git_cmd = 1;
                argv_array_push(&cmd.args, "merge");
                argv_array_push(&cmd.args, "-s");
-               argv_array_push(&cmd.args, "octopus");
+               if (!strategy)
+                       argv_array_push(&cmd.args, "octopus");
+               else {
+                       argv_array_push(&cmd.args, strategy);
+                       for (k = 0; k < opts->xopts_nr; k++)
+                               argv_array_pushf(&cmd.args,
+                                                "-X%s", opts->xopts[k]);
+               }
                argv_array_push(&cmd.args, "--no-edit");
                argv_array_push(&cmd.args, "--no-ff");
                argv_array_push(&cmd.args, "--no-log");
@@ -3737,7 +3851,7 @@ static int pick_commits(struct repository *r,
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());
                        unlink(rebase_path_amend());
-                       unlink(git_path_merge_head(the_repository));
+                       unlink(git_path_merge_head(r));
                        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
 
                        if (item->command == TODO_BREAK) {
@@ -4122,7 +4236,7 @@ static int commit_staged_changes(struct repository *r,
                           opts, flags))
                return error(_("could not commit staged changes."));
        unlink(rebase_path_amend());
-       unlink(git_path_merge_head(the_repository));
+       unlink(git_path_merge_head(r));
        if (final_fixup) {
                unlink(rebase_path_fixup_msg());
                unlink(rebase_path_squash_msg());
@@ -4257,7 +4371,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"));
@@ -4450,6 +4564,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 {
        int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
+       int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO;
        struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
        struct strbuf label = STRBUF_INIT;
        struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -4616,7 +4731,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
 
                if (!commit)
                        strbuf_addf(out, "%s %s\n", cmd_reset,
-                                   rebase_cousins ? "onto" : "[new root]");
+                                   rebase_cousins || root_with_onto ?
+                                   "onto" : "[new root]");
                else {
                        const char *to = NULL;