Merge branch 'ra/cherry-pick-revert-skip'
authorJunio C Hamano <gitster@pobox.com>
Fri, 19 Jul 2019 18:30:21 +0000 (11:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 19 Jul 2019 18:30:21 +0000 (11:30 -0700)
"git cherry-pick/revert" learned a new "--skip" action.

* ra/cherry-pick-revert-skip:
cherry-pick/revert: advise using --skip
cherry-pick/revert: add --skip option
sequencer: use argv_array in reset_merge
sequencer: rename reset_for_rollback to reset_merge
sequencer: add advice for revert

1  2 
Documentation/config/advice.txt
Documentation/git-revert.txt
advice.c
advice.h
builtin/commit.c
builtin/revert.c
sequencer.c
sequencer.h
index ee85c536cec83c9097ae586da404580573448431,1cd9096c987d62ae5900fe0edf5317257cff8a5f..6aaa36020298f54f60cab1973b91641d684b9b18
@@@ -4,10 -4,6 +4,10 @@@ advice.*:
        can tell Git that you do not need help by setting these to 'false':
  +
  --
 +      fetchShowForcedUpdates::
 +              Advice shown when linkgit:git-fetch[1] takes a long time
 +              to calculate forced updates after ref updates, or to warn
 +              that the check is disabled.
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
                'pushNonFFCurrent',
                we can still suggest that the user push to either
                refs/heads/* or refs/tags/* based on the type of the
                source object.
 +      statusAheadBehind::
 +              Shown when linkgit:git-status[1] computes the ahead/behind
 +              counts for a local ref compared to its remote tracking ref,
 +              and that calculation takes longer than expected. Will not
 +              appear if `status.aheadBehind` is false or the option
 +              `--no-ahead-behind` is given.
        statusHints::
                Show directions on how to proceed from the current
                state in the output of linkgit:git-status[1], in
                the template shown when writing commit messages in
                linkgit:git-commit[1], and in the help message shown
 -              by linkgit:git-checkout[1] when switching branch.
 +              by linkgit:git-switch[1] or
 +              linkgit:git-checkout[1] when switching branch.
        statusUoption::
                Advise to consider using the `-u` option to linkgit:git-status[1]
                when the command takes more than 2 seconds to enumerate untracked
        resolveConflict::
                Advice shown by various commands when conflicts
                prevent the operation from being performed.
+       sequencerInUse::
+               Advice shown when a sequencer command is already in progress.
        implicitIdentity::
                Advice on how to set your identity configuration when
                your information is guessed from the system username and
                domain name.
        detachedHead::
 -              Advice shown when you used linkgit:git-checkout[1] to
 -              move to the detach HEAD state, to instruct how to create
 -              a local branch after the fact.
 +              Advice shown when you used
 +              linkgit:git-switch[1] or linkgit:git-checkout[1]
 +              to move to the detach HEAD state, to instruct how to
 +              create a local branch after the fact.
        checkoutAmbiguousRemoteBranchName::
                Advice shown when the argument to
 -              linkgit:git-checkout[1] ambiguously resolves to a
 +              linkgit:git-checkout[1] and linkgit:git-switch[1]
 +              ambiguously resolves to a
                remote tracking branch on more than one remote in
                situations where an unambiguous argument would have
                otherwise caused a remote-tracking branch to be
index fae4d66547fb900d79d2bafad52cf0aba9a51158,665e065ee378f64df9ff61ea1805de5e5f72a72a..9d22270757c9b5d402f680a3f5933989678cb6f0
@@@ -9,9 -9,7 +9,7 @@@ SYNOPSI
  --------
  [verse]
  'git revert' [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>...
- 'git revert' --continue
- 'git revert' --quit
- 'git revert' --abort
+ 'git revert' (--continue | --skip | --abort | --quit)
  
  DESCRIPTION
  -----------
@@@ -26,13 -24,10 +24,13 @@@ effect of some earlier commits (often o
  throw away all uncommitted changes in your working directory, you
  should see linkgit:git-reset[1], particularly the `--hard` option.  If
  you want to extract specific files as they were in another commit, you
 -should see linkgit:git-checkout[1], specifically the `git checkout
 -<commit> -- <filename>` syntax.  Take care with these alternatives as
 +should see linkgit:git-restore[1], specifically the `--source`
 +option. Take care with these alternatives as
  both will discard uncommitted changes in your working directory.
  
 +See "Reset, restore and revert" in linkgit:git[1] for the differences
 +between the three commands.
 +
  OPTIONS
  -------
  <commit>...::
diff --combined advice.c
index 67de6dece4284a52fef27f3e7ef662d68eaa8109,b101f0c2648f7f4ab3bb614816f55f70257c0ed3..3ee0ee2d8fbb04dabcfc0152dc741c7d1f28b220
+++ b/advice.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "color.h"
  #include "help.h"
  
 +int advice_fetch_show_forced_updates = 1;
  int advice_push_update_rejected = 1;
  int advice_push_non_ff_current = 1;
  int advice_push_non_ff_matching = 1;
@@@ -13,10 -12,10 +13,11 @@@ int advice_push_needs_force = 1
  int advice_push_unqualified_ref_name = 1;
  int advice_status_hints = 1;
  int advice_status_u_option = 1;
 +int advice_status_ahead_behind_warning = 1;
  int advice_commit_before_merge = 1;
  int advice_reset_quiet_warning = 1;
  int advice_resolve_conflict = 1;
+ int advice_sequencer_in_use = 1;
  int advice_implicit_identity = 1;
  int advice_detached_head = 1;
  int advice_set_upstream_failure = 1;
@@@ -61,7 -60,6 +62,7 @@@ static struct 
        const char *name;
        int *preference;
  } advice_config[] = {
 +      { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates },
        { "pushUpdateRejected", &advice_push_update_rejected },
        { "pushNonFFCurrent", &advice_push_non_ff_current },
        { "pushNonFFMatching", &advice_push_non_ff_matching },
        { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name },
        { "statusHints", &advice_status_hints },
        { "statusUoption", &advice_status_u_option },
 +      { "statusAheadBehindWarning", &advice_status_ahead_behind_warning },
        { "commitBeforeMerge", &advice_commit_before_merge },
        { "resetQuiet", &advice_reset_quiet_warning },
        { "resolveConflict", &advice_resolve_conflict },
+       { "sequencerInUse", &advice_sequencer_in_use },
        { "implicitIdentity", &advice_implicit_identity },
        { "detachedHead", &advice_detached_head },
        { "setupStreamFailure", &advice_set_upstream_failure },
@@@ -197,22 -195,13 +199,22 @@@ void NORETURN die_conclude_merge(void
  void detach_advice(const char *new_name)
  {
        const char *fmt =
 -      _("Note: checking out '%s'.\n\n"
 +      _("Note: switching to '%s'.\n"
 +      "\n"
        "You are in 'detached HEAD' state. You can look around, make experimental\n"
        "changes and commit them, and you can discard any commits you make in this\n"
 -      "state without impacting any branches by performing another checkout.\n\n"
 +      "state without impacting any branches by switching back to a branch.\n"
 +      "\n"
        "If you want to create a new branch to retain commits you create, you may\n"
 -      "do so (now or later) by using -b with the checkout command again. Example:\n\n"
 -      "  git checkout -b <new-branch-name>\n\n");
 +      "do so (now or later) by using -c with the switch command. Example:\n"
 +      "\n"
 +      "  git switch -c <new-branch-name>\n"
 +      "\n"
 +      "Or undo this operation with:\n"
 +      "\n"
 +      "  git switch -\n"
 +      "\n"
 +      "Turn off this advice by setting config variable advice.detachedHead to false\n\n");
  
        fprintf(stderr, fmt, new_name);
  }
diff --combined advice.h
index 940c4c253e25644cd85362418ea965dd0bb51a3d,ebc838d7bc6c48957a98e50ceb070fec47b28500..d0154048431c773b9bf0ec450478a93d5ede06c0
+++ b/advice.h
@@@ -3,7 -3,6 +3,7 @@@
  
  #include "git-compat-util.h"
  
 +extern int advice_fetch_show_forced_updates;
  extern int advice_push_update_rejected;
  extern int advice_push_non_ff_current;
  extern int advice_push_non_ff_matching;
@@@ -13,10 -12,10 +13,11 @@@ extern int advice_push_needs_force
  extern int advice_push_unqualified_ref_name;
  extern int advice_status_hints;
  extern int advice_status_u_option;
 +extern int advice_status_ahead_behind_warning;
  extern int advice_commit_before_merge;
  extern int advice_reset_quiet_warning;
  extern int advice_resolve_conflict;
+ extern int advice_sequencer_in_use;
  extern int advice_implicit_identity;
  extern int advice_detached_head;
  extern int advice_set_upstream_failure;
diff --combined builtin/commit.c
index 3e4b5bfe4e1d18d8f7dfa8d33d0377b642653375,1f47c51bdcabd4ccbec8b2a451be648f0a17f0f9..ae7aaf6dc6835888e4fb55de8a135331ab05316e
@@@ -60,15 -60,18 +60,18 @@@ N_("The previous cherry-pick is now emp
  "\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",
@@@ -1078,11 -1081,9 +1081,11 @@@ static const char *read_commit_message(
  static struct status_deferred_config {
        enum wt_status_format status_format;
        int show_branch;
 +      enum ahead_behind_flags ahead_behind;
  } status_deferred_config = {
        STATUS_FORMAT_UNSPECIFIED,
 -      -1 /* unspecified */
 +      -1, /* unspecified */
 +      AHEAD_BEHIND_UNSPECIFIED,
  };
  
  static void finalize_deferred_config(struct wt_status *s)
        if (s->show_branch < 0)
                s->show_branch = 0;
  
 +      /*
 +       * If the user did not give a "--[no]-ahead-behind" command
 +       * line argument *AND* we will print in a human-readable format
 +       * (short, long etc.) then we inherit from the status.aheadbehind
 +       * config setting.  In all other cases (and porcelain V[12] formats
 +       * in particular), we inherit _FULL for backwards compatibility.
 +       */
 +      if (use_deferred_config &&
 +          s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
 +              s->ahead_behind_flags = status_deferred_config.ahead_behind;
 +
        if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
                s->ahead_behind_flags = AHEAD_BEHIND_FULL;
  }
@@@ -1259,10 -1249,6 +1262,10 @@@ static int git_status_config(const cha
                status_deferred_config.show_branch = git_config_bool(k, v);
                return 0;
        }
 +      if (!strcmp(k, "status.aheadbehind")) {
 +              status_deferred_config.ahead_behind = git_config_bool(k, v);
 +              return 0;
 +      }
        if (!strcmp(k, "status.showstash")) {
                s->show_stash = git_config_bool(k, v);
                return 0;
@@@ -1675,7 -1661,7 +1678,7 @@@ int cmd_commit(int argc, const char **a
                die("%s", err.buf);
        }
  
 -      sequencer_post_commit_cleanup(the_repository);
 +      sequencer_post_commit_cleanup(the_repository, 0);
        unlink(git_path_merge_head(the_repository));
        unlink(git_path_merge_msg(the_repository));
        unlink(git_path_merge_mode(the_repository));
        if (commit_index_files())
                die(_("repository has been updated, but unable to write\n"
                      "new_index file. Check that disk is not full and quota is\n"
 -                    "not exceeded, and then \"git reset HEAD\" to recover."));
 +                    "not exceeded, and then \"git restore --staged :/\" to recover."));
  
 -      if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
 -              write_commit_graph_reachable(get_object_directory(), 0, 0);
 +      if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
 +          write_commit_graph_reachable(get_object_directory(), 0, NULL))
 +              return 1;
  
        repo_rerere(the_repository, 0);
        run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
diff --combined builtin/revert.c
index 4e71b2f2aa292ffc3187aac61001e7fd529255b3,5dc5891ea2a262125641f94b4e175b0e09afee46..f61cc5d82cf2697583b5851893ba4076f96643a5
@@@ -102,6 -102,7 +102,7 @@@ static int run_sequencer(int argc, cons
                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, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
+               OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'),
                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")),
                        this_operation = "--quit";
                else if (cmd == 'c')
                        this_operation = "--continue";
+               else if (cmd == 's')
+                       this_operation = "--skip";
                else {
                        assert(cmd == 'a');
                        this_operation = "--abort";
        if (cmd == 'q') {
                int ret = sequencer_remove_state(opts);
                if (!ret)
 -                      remove_branch_state(the_repository);
 +                      remove_branch_state(the_repository, 0);
                return ret;
        }
        if (cmd == 'c')
                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 --combined sequencer.c
index b91c981b326a16122ec1a5cbbd9f2c545115fa8c,7d0e5f93663e8c4d5737e203b5e27840dc0870e0..66126e020d34ec99c61d4b6a44cb949aae10e309
@@@ -279,7 -279,7 +279,7 @@@ static const char *gpg_sign_opt_quoted(
  int sequencer_remove_state(struct replay_opts *opts)
  {
        struct strbuf buf = STRBUF_INIT;
 -      int i;
 +      int i, ret = 0;
  
        if (is_rebase_i(opts) &&
            strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) {
                        char *eol = strchr(p, '\n');
                        if (eol)
                                *eol = '\0';
 -                      if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0)
 +                      if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) {
                                warning(_("could not delete '%s'"), p);
 +                              ret = -1;
 +                      }
                        if (!eol)
                                break;
                        p = eol + 1;
  
        strbuf_reset(&buf);
        strbuf_addstr(&buf, get_dir(opts));
 -      remove_dir_recursively(&buf, 0);
 +      if (remove_dir_recursively(&buf, 0))
 +              ret = error(_("could not remove '%s'"), buf.buf);
        strbuf_release(&buf);
  
 -      return 0;
 +      return ret;
  }
  
  static const char *action_name(const struct replay_opts *opts)
@@@ -2079,18 -2076,6 +2079,18 @@@ const char *todo_item_get_arg(struct to
        return todo_list->buf.buf + item->arg_offset;
  }
  
 +static int is_command(enum todo_command command, const char **bol)
 +{
 +      const char *str = todo_command_info[command].str;
 +      const char nick = todo_command_info[command].c;
 +      const char *p = *bol + 1;
 +
 +      return skip_prefix(*bol, str, bol) ||
 +              ((nick && **bol == nick) &&
 +               (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) &&
 +               (*bol = p));
 +}
 +
  static int parse_insn_line(struct repository *r, struct todo_item *item,
                           const char *buf, const char *bol, char *eol)
  {
        }
  
        for (i = 0; i < TODO_COMMENT; i++)
 -              if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
 -                      item->command = i;
 -                      break;
 -              } else if ((bol + 1 == eol || bol[1] == ' ') &&
 -                         *bol == todo_command_info[i].c) {
 -                      bol++;
 +              if (is_command(i, &bol)) {
                        item->command = i;
                        break;
                }
  
  int sequencer_get_last_command(struct repository *r, enum replay_action *action)
  {
 -      struct todo_item item;
 -      char *eol;
 -      const char *todo_file;
 +      const char *todo_file, *bol;
        struct strbuf buf = STRBUF_INIT;
 -      int ret = -1;
 +      int ret = 0;
  
        todo_file = git_path_todo_file();
        if (strbuf_read_file(&buf, todo_file, 0) < 0) {
 -              if (errno == ENOENT)
 +              if (errno == ENOENT || errno == ENOTDIR)
                        return -1;
                else
                        return error_errno("unable to open '%s'", todo_file);
        }
 -      eol = strchrnul(buf.buf, '\n');
 -      if (buf.buf != eol && eol[-1] == '\r')
 -              eol--; /* strip Carriage Return */
 -      if (parse_insn_line(r, &item, buf.buf, buf.buf, eol))
 -              goto fail;
 -      if (item.command == TODO_PICK)
 +      bol = buf.buf + strspn(buf.buf, " \t\r\n");
 +      if (is_command(TODO_PICK, &bol) && (*bol == ' ' || *bol == '\t'))
                *action = REPLAY_PICK;
 -      else if (item.command == TODO_REVERT)
 +      else if (is_command(TODO_REVERT, &bol) &&
 +               (*bol == ' ' || *bol == '\t'))
                *action = REPLAY_REVERT;
        else
 -              goto fail;
 -
 -      ret = 0;
 +              ret = -1;
  
 - fail:
        strbuf_release(&buf);
  
        return ret;
@@@ -2313,21 -2311,19 +2313,21 @@@ static int have_finished_the_last_pick(
        return ret;
  }
  
 -void sequencer_post_commit_cleanup(struct repository *r)
 +void sequencer_post_commit_cleanup(struct repository *r, int verbose)
  {
        struct replay_opts opts = REPLAY_OPTS_INIT;
        int need_cleanup = 0;
  
        if (file_exists(git_path_cherry_pick_head(r))) {
 -              unlink(git_path_cherry_pick_head(r));
 +              if (!unlink(git_path_cherry_pick_head(r)) && verbose)
 +                      warning(_("cancelling a cherry picking in progress"));
                opts.action = REPLAY_PICK;
                need_cleanup = 1;
        }
  
        if (file_exists(git_path_revert_head(r))) {
 -              unlink(git_path_revert_head(r));
 +              if (!unlink(git_path_revert_head(r)) && verbose)
 +                      warning(_("cancelling a revert in progress"));
                opts.action = REPLAY_REVERT;
                need_cleanup = 1;
        }
@@@ -2654,15 -2650,41 +2654,41 @@@ static int walk_revs_populate_todo(stru
        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 -2735,20 +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)
                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)
                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 -2823,70 +2827,70 @@@ fail
        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;
@@@ -3740,11 -3840,8 +3844,11 @@@ static int pick_commits(struct reposito
                        unlink(git_path_merge_head(the_repository));
                        delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
  
 -                      if (item->command == TODO_BREAK)
 +                      if (item->command == TODO_BREAK) {
 +                              if (!opts->verbose)
 +                                      term_clear_line();
                                return stopped_at_head(r);
 +                      }
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
                        }
                        if (item->command == TODO_EDIT) {
                                struct commit *commit = item->commit;
 -                              if (!res)
 +                              if (!res) {
 +                                      if (!opts->verbose)
 +                                              term_clear_line();
                                        fprintf(stderr,
                                                _("Stopped at %s...  %.*s\n"),
                                                short_commit_name(commit),
                                                item->arg_len, arg);
 +                              }
                                return error_with_patch(r, commit,
                                        arg, item->arg_len, opts, res, !res);
                        }
                        int saved = *end_of_arg;
                        struct stat st;
  
 +                      if (!opts->verbose)
 +                              term_clear_line();
                        *end_of_arg = '\0';
                        res = do_exec(r, arg);
                        *end_of_arg = saved;
@@@ -3971,13 -4063,10 +4075,13 @@@ cleanup_head_ref
                }
                apply_autostash(opts);
  
 -              if (!opts->quiet)
 +              if (!opts->quiet) {
 +                      if (!opts->verbose)
 +                              term_clear_line();
                        fprintf(stderr,
                                "Successfully rebased and updated %s.\n",
                                head_ref.buf);
 +              }
  
                strbuf_release(&buf);
                strbuf_release(&head_ref);
@@@ -4257,7 -4346,7 +4361,7 @@@ int sequencer_pick_revisions(struct rep
         */
  
        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 --combined sequencer.h
index 3d0b68c34ce1fed23899415660127c999fa999c6,731b9853ebd265793f3ce8b43aa13a96b084fd0c..6704acbb9c93a55cb7ec69d2e045d67850bc4049
@@@ -129,6 -129,7 +129,7 @@@ int sequencer_pick_revisions(struct rep
                             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)
@@@ -200,6 -201,6 +201,6 @@@ int read_author_script(const char *path
  void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);
  int write_basic_state(struct replay_opts *opts, const char *head_name,
                      struct commit *onto, const char *orig_head);
 -void sequencer_post_commit_cleanup(struct repository *r);
 +void sequencer_post_commit_cleanup(struct repository *r, int verbose);
  int sequencer_get_last_command(struct repository* r,
                               enum replay_action *action);