]> git.ipfire.org Git - thirdparty/git.git/blobdiff - sequencer.c
Merge branch 'pw/rebase-i-more-options'
[thirdparty/git.git] / sequencer.c
index 968a2d4ef36d39b217a6e5847957bd145fdb8422..e8676e965fca6813d49cb4b30cc4fab5b8da3390 100644 (file)
@@ -16,7 +16,7 @@
 #include "rerere.h"
 #include "merge-recursive.h"
 #include "refs.h"
-#include "argv-array.h"
+#include "strvec.h"
 #include "quote.h"
 #include "trailer.h"
 #include "log-tree.h"
@@ -32,6 +32,7 @@
 #include "alias.h"
 #include "commit-reach.h"
 #include "rebase-interactive.h"
+#include "reset.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -149,6 +150,8 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
  * command-line.
  */
 static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
+static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate")
+static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date")
 static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
 static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
 static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
@@ -302,6 +305,8 @@ int sequencer_remove_state(struct replay_opts *opts)
                }
        }
 
+       free(opts->committer_name);
+       free(opts->committer_email);
        free(opts->gpg_sign);
        free(opts->strategy);
        for (i = 0; i < opts->xopts_nr; i++)
@@ -354,7 +359,7 @@ static int get_message(struct commit *commit, struct commit_message *out)
        subject_len = find_commit_subject(out->message, &subject);
 
        out->subject = xmemdupz(subject, subject_len);
-       out->label = xstrfmt("%s... %s", abbrev, out->subject);
+       out->label = xstrfmt("%s (%s)", abbrev, out->subject);
        out->parent_label = xstrfmt("parent of %s", out->label);
 
        return 0;
@@ -380,7 +385,8 @@ static void print_advice(struct repository *r, int show_hint,
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
-               unlink(git_path_cherry_pick_head(r));
+               refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+                               NULL, 0);
                return;
        }
 
@@ -419,25 +425,15 @@ static int write_message(const void *buf, size_t len, const char *filename,
        return 0;
 }
 
-/*
- * Reads a file that was presumably written by a shell script, i.e. with an
- * end-of-line marker that needs to be stripped.
- *
- * Note that only the last end-of-line marker is stripped, consistent with the
- * behavior of "$(cat path)" in a shell script.
- *
- * Returns 1 if the file was read, 0 if it could not be read or does not exist.
- */
-static int read_oneliner(struct strbuf *buf,
-       const char *path, int skip_if_empty)
+int read_oneliner(struct strbuf *buf,
+       const char *path, unsigned flags)
 {
        int orig_len = buf->len;
 
-       if (!file_exists(path))
-               return 0;
-
        if (strbuf_read_file(buf, path, 0) < 0) {
-               warning_errno(_("could not read '%s'"), path);
+               if ((flags & READ_ONELINER_WARN_MISSING) ||
+                   (errno != ENOENT && errno != ENOTDIR))
+                       warning_errno(_("could not read '%s'"), path);
                return 0;
        }
 
@@ -447,7 +443,7 @@ static int read_oneliner(struct strbuf *buf,
                buf->buf[buf->len] = '\0';
        }
 
-       if (skip_if_empty && buf->len == orig_len)
+       if ((flags & READ_ONELINER_SKIP_IF_EMPTY) && buf->len == orig_len)
                return 0;
 
        return 1;
@@ -839,10 +835,10 @@ finish:
 
 /*
  * Read a GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL AND GIT_AUTHOR_DATE from a
- * file with shell quoting into struct argv_array. Returns -1 on
+ * file with shell quoting into struct strvec. Returns -1 on
  * error, 0 otherwise.
  */
-static int read_env_script(struct argv_array *env)
+static int read_env_script(struct strvec *env)
 {
        char *name, *email, *date;
 
@@ -850,9 +846,9 @@ static int read_env_script(struct argv_array *env)
                               &name, &email, &date, 0))
                return -1;
 
-       argv_array_pushf(env, "GIT_AUTHOR_NAME=%s", name);
-       argv_array_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
-       argv_array_pushf(env, "GIT_AUTHOR_DATE=%s", date);
+       strvec_pushf(env, "GIT_AUTHOR_NAME=%s", name);
+       strvec_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
+       strvec_pushf(env, "GIT_AUTHOR_DATE=%s", date);
        free(name);
        free(email);
        free(date);
@@ -872,6 +868,22 @@ static char *get_author(const char *message)
        return NULL;
 }
 
+static const char *author_date_from_env_array(const struct strvec *env)
+{
+       int i;
+       const char *date;
+
+       for (i = 0; i < env->nr; i++)
+               if (skip_prefix(env->v[i],
+                               "GIT_AUTHOR_DATE=", &date))
+                       return date;
+       /*
+        * If GIT_AUTHOR_DATE is missing we should have already errored out when
+        * reading the script
+        */
+       BUG("GIT_AUTHOR_DATE missing from author script");
+}
+
 static const char staged_changes_advice[] =
 N_("you have staged changes in your working tree\n"
 "If these changes are meant to be squashed into the previous commit, run:\n"
@@ -938,32 +950,42 @@ static int run_git_commit(struct repository *r,
                             gpg_opt, gpg_opt);
        }
 
-       argv_array_push(&cmd.args, "commit");
+       if (opts->committer_date_is_author_date)
+               strvec_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
+                            opts->ignore_date ?
+                            "" :
+                            author_date_from_env_array(&cmd.env_array));
+       if (opts->ignore_date)
+               strvec_push(&cmd.env_array, "GIT_AUTHOR_DATE=");
+
+       strvec_push(&cmd.args, "commit");
 
        if (!(flags & VERIFY_MSG))
-               argv_array_push(&cmd.args, "-n");
+               strvec_push(&cmd.args, "-n");
        if ((flags & AMEND_MSG))
-               argv_array_push(&cmd.args, "--amend");
+               strvec_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
-               argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+               strvec_pushf(&cmd.args, "-S%s", opts->gpg_sign);
+       else
+               strvec_push(&cmd.args, "--no-gpg-sign");
        if (defmsg)
-               argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
+               strvec_pushl(&cmd.args, "-F", defmsg, NULL);
        else if (!(flags & EDIT_MSG))
-               argv_array_pushl(&cmd.args, "-C", "HEAD", NULL);
+               strvec_pushl(&cmd.args, "-C", "HEAD", NULL);
        if ((flags & CLEANUP_MSG))
-               argv_array_push(&cmd.args, "--cleanup=strip");
+               strvec_push(&cmd.args, "--cleanup=strip");
        if ((flags & EDIT_MSG))
-               argv_array_push(&cmd.args, "-e");
+               strvec_push(&cmd.args, "-e");
        else if (!(flags & CLEANUP_MSG) &&
                 !opts->signoff && !opts->record_origin &&
                 !opts->explicit_cleanup)
-               argv_array_push(&cmd.args, "--cleanup=verbatim");
+               strvec_push(&cmd.args, "--cleanup=verbatim");
 
        if ((flags & ALLOW_EMPTY))
-               argv_array_push(&cmd.args, "--allow-empty");
+               strvec_push(&cmd.args, "--allow-empty");
 
        if (!(flags & EDIT_MSG))
-               argv_array_push(&cmd.args, "--allow-empty-message");
+               strvec_push(&cmd.args, "--allow-empty-message");
 
        if (is_rebase_i(opts) && !(flags & EDIT_MSG))
                return run_command_silent_on_success(&cmd);
@@ -1315,6 +1337,7 @@ static int try_to_commit(struct repository *r,
        struct strbuf err = STRBUF_INIT;
        struct strbuf commit_msg = STRBUF_INIT;
        char *amend_author = NULL;
+       const char *committer = NULL;
        const char *hook_commit = NULL;
        enum commit_msg_cleanup_mode cleanup;
        int res = 0;
@@ -1406,10 +1429,57 @@ static int try_to_commit(struct repository *r,
                goto out;
        }
 
-       reset_ident_date();
+       if (opts->committer_date_is_author_date) {
+               struct ident_split id;
+               struct strbuf date = STRBUF_INIT;
+
+               if (!opts->ignore_date) {
+                       if (split_ident_line(&id, author, (int)strlen(author)) < 0) {
+                               res = error(_("invalid author identity '%s'"),
+                                           author);
+                               goto out;
+                       }
+                       if (!id.date_begin) {
+                               res = error(_(
+                                       "corrupt author: missing date information"));
+                               goto out;
+                       }
+                       strbuf_addf(&date, "@%.*s %.*s",
+                                   (int)(id.date_end - id.date_begin),
+                                   id.date_begin,
+                                   (int)(id.tz_end - id.tz_begin),
+                                   id.tz_begin);
+               } else {
+                       reset_ident_date();
+               }
+               committer = fmt_ident(opts->committer_name,
+                                     opts->committer_email,
+                                     WANT_COMMITTER_IDENT,
+                                     opts->ignore_date ? NULL : date.buf,
+                                     IDENT_STRICT);
+               strbuf_release(&date);
+       } else {
+               reset_ident_date();
+       }
+
+       if (opts->ignore_date) {
+               struct ident_split id;
+               char *name, *email;
+
+               if (split_ident_line(&id, author, strlen(author)) < 0) {
+                       error(_("invalid author identity '%s'"), author);
+                       goto out;
+               }
+               name = xmemdupz(id.name_begin, id.name_end - id.name_begin);
+               email = xmemdupz(id.mail_begin, id.mail_end - id.mail_begin);
+               author = fmt_ident(name, email, WANT_AUTHOR_IDENT, NULL,
+                                  IDENT_STRICT);
+               free(name);
+               free(email);
+       }
 
        if (commit_tree_extended(msg->buf, msg->len, &tree, parents, oid,
-                                author, NULL, opts->gpg_sign, extra)) {
+                                author, committer, opts->gpg_sign, extra)) {
                res = error(_("failed to write commit object"));
                goto out;
        }
@@ -1462,7 +1532,8 @@ static int do_commit(struct repository *r,
                                    author, opts, flags, &oid);
                strbuf_release(&sb);
                if (!res) {
-                       unlink(git_path_cherry_pick_head(r));
+                       refs_delete_ref(get_main_ref_store(r), "",
+                                       "CHERRY_PICK_HEAD", NULL, 0);
                        unlink(git_path_merge_msg(r));
                        if (!is_rebase_i(opts))
                                print_commit_summary(r, NULL, &oid,
@@ -1578,7 +1649,7 @@ static const char *command_to_string(const enum todo_command command)
 
 static char command_to_char(const enum todo_command command)
 {
-       if (command < TODO_COMMENT && todo_command_info[command].c)
+       if (command < TODO_COMMENT)
                return todo_command_info[command].c;
        return comment_line_char;
 }
@@ -1973,7 +2044,8 @@ static int do_pick_commit(struct repository *r,
                flags |= ALLOW_EMPTY;
        } else if (allow == 2) {
                drop_commit = 1;
-               unlink(git_path_cherry_pick_head(r));
+               refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+                               NULL, 0);
                unlink(git_path_merge_msg(r));
                fprintf(stderr,
                        _("dropping %s %s -- patch contents already upstream\n"),
@@ -2312,15 +2384,19 @@ 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))) {
-               if (!unlink(git_path_cherry_pick_head(r)) && verbose)
+       if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
+               if (!refs_delete_ref(get_main_ref_store(r), "",
+                                    "CHERRY_PICK_HEAD", NULL, 0) &&
+                   verbose)
                        warning(_("cancelling a cherry picking in progress"));
                opts.action = REPLAY_PICK;
                need_cleanup = 1;
        }
 
-       if (file_exists(git_path_revert_head(r))) {
-               if (!unlink(git_path_revert_head(r)) && verbose)
+       if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
+               if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD",
+                                    NULL, 0) &&
+                   verbose)
                        warning(_("cancelling a revert in progress"));
                opts.action = REPLAY_REVERT;
                need_cleanup = 1;
@@ -2502,8 +2578,10 @@ static int read_populate_opts(struct replay_opts *opts)
 {
        if (is_rebase_i(opts)) {
                struct strbuf buf = STRBUF_INIT;
+               int ret = 0;
 
-               if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
+               if (read_oneliner(&buf, rebase_path_gpg_sign_opt(),
+                                 READ_ONELINER_SKIP_IF_EMPTY)) {
                        if (!starts_with(buf.buf, "-S"))
                                strbuf_reset(&buf);
                        else {
@@ -2513,7 +2591,8 @@ static int read_populate_opts(struct replay_opts *opts)
                        strbuf_reset(&buf);
                }
 
-               if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
+               if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(),
+                                 READ_ONELINER_SKIP_IF_EMPTY)) {
                        if (!strcmp(buf.buf, "--rerere-autoupdate"))
                                opts->allow_rerere_auto = RERERE_AUTOUPDATE;
                        else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
@@ -2532,6 +2611,16 @@ static int read_populate_opts(struct replay_opts *opts)
                        opts->signoff = 1;
                }
 
+               if (file_exists(rebase_path_cdate_is_adate())) {
+                       opts->allow_ff = 0;
+                       opts->committer_date_is_author_date = 1;
+               }
+
+               if (file_exists(rebase_path_ignore_date())) {
+                       opts->allow_ff = 0;
+                       opts->ignore_date = 1;
+               }
+
                if (file_exists(rebase_path_reschedule_failed_exec()))
                        opts->reschedule_failed_exec = 1;
 
@@ -2542,10 +2631,11 @@ static int read_populate_opts(struct replay_opts *opts)
                        opts->keep_redundant_commits = 1;
 
                read_strategy_opts(opts, &buf);
-               strbuf_release(&buf);
+               strbuf_reset(&buf);
 
                if (read_oneliner(&opts->current_fixups,
-                                 rebase_path_current_fixups(), 1)) {
+                                 rebase_path_current_fixups(),
+                                 READ_ONELINER_SKIP_IF_EMPTY)) {
                        const char *p = opts->current_fixups.buf;
                        opts->current_fixup_count = 1;
                        while ((p = strchr(p, '\n'))) {
@@ -2555,12 +2645,16 @@ static int read_populate_opts(struct replay_opts *opts)
                }
 
                if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
-                       if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
-                               return error(_("unusable squash-onto"));
+                       if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) {
+                               ret = error(_("unusable squash-onto"));
+                               goto done_rebase_i;
+                       }
                        opts->have_squash_onto = 1;
                }
 
-               return 0;
+done_rebase_i:
+               strbuf_release(&buf);
+               return ret;
        }
 
        if (!file_exists(git_path_opts_file()))
@@ -2622,6 +2716,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,
                write_file(rebase_path_drop_redundant_commits(), "%s", "");
        if (opts->keep_redundant_commits)
                write_file(rebase_path_keep_redundant_commits(), "%s", "");
+       if (opts->committer_date_is_author_date)
+               write_file(rebase_path_cdate_is_adate(), "%s", "");
+       if (opts->ignore_date)
+               write_file(rebase_path_ignore_date(), "%s", "");
        if (opts->reschedule_failed_exec)
                write_file(rebase_path_reschedule_failed_exec(), "%s", "");
 
@@ -2670,8 +2768,9 @@ static int create_seq_dir(struct repository *r)
        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));
+       unsigned int advise_skip =
+               refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") ||
+               refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD");
 
        if (!sequencer_get_last_command(r, &action)) {
                switch (action) {
@@ -2753,15 +2852,15 @@ static int rollback_is_safe(void)
 static int reset_merge(const struct object_id *oid)
 {
        int ret;
-       struct argv_array argv = ARGV_ARRAY_INIT;
+       struct strvec argv = STRVEC_INIT;
 
-       argv_array_pushl(&argv, "reset", "--merge", NULL);
+       strvec_pushl(&argv, "reset", "--merge", NULL);
 
        if (!is_null_oid(oid))
-               argv_array_push(&argv, oid_to_hex(oid));
+               strvec_push(&argv, oid_to_hex(oid));
 
-       ret = run_command_v_opt(argv.argv, RUN_GIT_CMD);
-       argv_array_clear(&argv);
+       ret = run_command_v_opt(argv.v, RUN_GIT_CMD);
+       strvec_clear(&argv);
 
        return ret;
 }
@@ -2770,8 +2869,8 @@ static int rollback_single_pick(struct repository *r)
 {
        struct object_id head_oid;
 
-       if (!file_exists(git_path_cherry_pick_head(r)) &&
-           !file_exists(git_path_revert_head(r)))
+       if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+           !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
                return error(_("no cherry-pick or revert in progress"));
        if (read_ref_full("HEAD", 0, &head_oid, NULL))
                return error(_("cannot resolve HEAD"));
@@ -2865,7 +2964,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
         */
        switch (opts->action) {
        case REPLAY_REVERT:
-               if (!file_exists(git_path_revert_head(r))) {
+               if (!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
                        if (action != REPLAY_REVERT)
                                return error(_("no revert in progress"));
                        if (!rollback_is_safe())
@@ -2873,7 +2972,8 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
                }
                break;
        case REPLAY_PICK:
-               if (!file_exists(git_path_cherry_pick_head(r))) {
+               if (!refs_ref_exists(get_main_ref_store(r),
+                                    "CHERRY_PICK_HEAD")) {
                        if (action != REPLAY_PICK)
                                return error(_("no cherry-pick in progress"));
                        if (!rollback_is_safe())
@@ -3124,17 +3224,17 @@ static int error_failed_squash(struct repository *r,
 
 static int do_exec(struct repository *r, const char *command_line)
 {
-       struct argv_array child_env = ARGV_ARRAY_INIT;
+       struct strvec child_env = STRVEC_INIT;
        const char *child_argv[] = { NULL, NULL };
        int dirty, status;
 
-       fprintf(stderr, "Executing: %s\n", command_line);
+       fprintf(stderr, _("Executing: %s\n"), command_line);
        child_argv[0] = command_line;
-       argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
-       argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
-                        absolute_path(get_git_work_tree()));
+       strvec_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+       strvec_pushf(&child_env, "GIT_WORK_TREE=%s",
+                    absolute_path(get_git_work_tree()));
        status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
-                                         child_env.argv);
+                                         child_env.v);
 
        /* force re-reading of the cache */
        if (discard_index(r->index) < 0 || repo_read_index(r) < 0)
@@ -3164,7 +3264,7 @@ static int do_exec(struct repository *r, const char *command_line)
                status = 1;
        }
 
-       argv_array_clear(&child_env);
+       strvec_clear(&child_env);
 
        return status;
 }
@@ -3542,33 +3642,42 @@ static int do_merge(struct repository *r,
                        goto leave_merge;
                }
 
+               if (opts->committer_date_is_author_date)
+                       strvec_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
+                                    opts->ignore_date ?
+                                    "" :
+                                    author_date_from_env_array(&cmd.env_array));
+               if (opts->ignore_date)
+                       strvec_push(&cmd.env_array, "GIT_AUTHOR_DATE=");
+
                cmd.git_cmd = 1;
-               argv_array_push(&cmd.args, "merge");
-               argv_array_push(&cmd.args, "-s");
+               strvec_push(&cmd.args, "merge");
+               strvec_push(&cmd.args, "-s");
                if (!strategy)
-                       argv_array_push(&cmd.args, "octopus");
+                       strvec_push(&cmd.args, "octopus");
                else {
-                       argv_array_push(&cmd.args, strategy);
+                       strvec_push(&cmd.args, strategy);
                        for (k = 0; k < opts->xopts_nr; k++)
-                               argv_array_pushf(&cmd.args,
-                                                "-X%s", opts->xopts[k]);
+                               strvec_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");
-               argv_array_push(&cmd.args, "--no-stat");
-               argv_array_push(&cmd.args, "-F");
-               argv_array_push(&cmd.args, git_path_merge_msg(r));
+               strvec_push(&cmd.args, "--no-edit");
+               strvec_push(&cmd.args, "--no-ff");
+               strvec_push(&cmd.args, "--no-log");
+               strvec_push(&cmd.args, "--no-stat");
+               strvec_push(&cmd.args, "-F");
+               strvec_push(&cmd.args, git_path_merge_msg(r));
                if (opts->gpg_sign)
-                       argv_array_push(&cmd.args, opts->gpg_sign);
+                       strvec_push(&cmd.args, opts->gpg_sign);
 
                /* Add the tips to be merged */
                for (j = to_merge; j; j = j->next)
-                       argv_array_push(&cmd.args,
-                                       oid_to_hex(&j->item->object.oid));
+                       strvec_push(&cmd.args,
+                                   oid_to_hex(&j->item->object.oid));
 
                strbuf_release(&ref_name);
-               unlink(git_path_cherry_pick_head(r));
+               refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
+                               NULL, 0);
                rollback_lock_file(&lock);
 
                rollback_lock_file(&lock);
@@ -3675,59 +3784,142 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
        return -1;
 }
 
-static int apply_autostash(struct replay_opts *opts)
+void create_autostash(struct repository *r, const char *path,
+                     const char *default_reflog_action)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct lock_file lock_file = LOCK_INIT;
+       int fd;
+
+       fd = repo_hold_locked_index(r, &lock_file, 0);
+       refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
+       if (0 <= fd)
+               repo_update_index_if_able(r, &lock_file);
+       rollback_lock_file(&lock_file);
+
+       if (has_unstaged_changes(r, 1) ||
+           has_uncommitted_changes(r, 1)) {
+               struct child_process stash = CHILD_PROCESS_INIT;
+               struct object_id oid;
+
+               strvec_pushl(&stash.args,
+                            "stash", "create", "autostash", NULL);
+               stash.git_cmd = 1;
+               stash.no_stdin = 1;
+               strbuf_reset(&buf);
+               if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
+                       die(_("Cannot autostash"));
+               strbuf_trim_trailing_newline(&buf);
+               if (get_oid(buf.buf, &oid))
+                       die(_("Unexpected stash response: '%s'"),
+                           buf.buf);
+               strbuf_reset(&buf);
+               strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
+
+               if (safe_create_leading_directories_const(path))
+                       die(_("Could not create directory for '%s'"),
+                           path);
+               write_file(path, "%s", oid_to_hex(&oid));
+               printf(_("Created autostash: %s\n"), buf.buf);
+               if (reset_head(r, NULL, "reset --hard",
+                              NULL, RESET_HEAD_HARD, NULL, NULL,
+                              default_reflog_action) < 0)
+                       die(_("could not reset --hard"));
+
+               if (discard_index(r->index) < 0 ||
+                       repo_read_index(r) < 0)
+                       die(_("could not read index"));
+       }
+       strbuf_release(&buf);
+}
+
+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
 {
-       struct strbuf stash_sha1 = STRBUF_INIT;
        struct child_process child = CHILD_PROCESS_INIT;
        int ret = 0;
 
-       if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) {
-               strbuf_release(&stash_sha1);
-               return 0;
+       if (attempt_apply) {
+               child.git_cmd = 1;
+               child.no_stdout = 1;
+               child.no_stderr = 1;
+               strvec_push(&child.args, "stash");
+               strvec_push(&child.args, "apply");
+               strvec_push(&child.args, stash_oid);
+               ret = run_command(&child);
        }
-       strbuf_trim(&stash_sha1);
 
-       child.git_cmd = 1;
-       child.no_stdout = 1;
-       child.no_stderr = 1;
-       argv_array_push(&child.args, "stash");
-       argv_array_push(&child.args, "apply");
-       argv_array_push(&child.args, stash_sha1.buf);
-       if (!run_command(&child))
+       if (attempt_apply && !ret)
                fprintf(stderr, _("Applied autostash.\n"));
        else {
                struct child_process store = CHILD_PROCESS_INIT;
 
                store.git_cmd = 1;
-               argv_array_push(&store.args, "stash");
-               argv_array_push(&store.args, "store");
-               argv_array_push(&store.args, "-m");
-               argv_array_push(&store.args, "autostash");
-               argv_array_push(&store.args, "-q");
-               argv_array_push(&store.args, stash_sha1.buf);
+               strvec_push(&store.args, "stash");
+               strvec_push(&store.args, "store");
+               strvec_push(&store.args, "-m");
+               strvec_push(&store.args, "autostash");
+               strvec_push(&store.args, "-q");
+               strvec_push(&store.args, stash_oid);
                if (run_command(&store))
-                       ret = error(_("cannot store %s"), stash_sha1.buf);
+                       ret = error(_("cannot store %s"), stash_oid);
                else
                        fprintf(stderr,
-                               _("Applying autostash resulted in conflicts.\n"
+                               _("%s\n"
                                  "Your changes are safe in the stash.\n"
                                  "You can run \"git stash pop\" or"
-                                 " \"git stash drop\" at any time.\n"));
+                                 " \"git stash drop\" at any time.\n"),
+                               attempt_apply ?
+                               _("Applying autostash resulted in conflicts.") :
+                               _("Autostash exists; creating a new stash entry."));
+       }
+
+       return ret;
+}
+
+static int apply_save_autostash(const char *path, int attempt_apply)
+{
+       struct strbuf stash_oid = STRBUF_INIT;
+       int ret = 0;
+
+       if (!read_oneliner(&stash_oid, path,
+                          READ_ONELINER_SKIP_IF_EMPTY)) {
+               strbuf_release(&stash_oid);
+               return 0;
        }
+       strbuf_trim(&stash_oid);
+
+       ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
 
-       strbuf_release(&stash_sha1);
+       unlink(path);
+       strbuf_release(&stash_oid);
        return ret;
 }
 
+int save_autostash(const char *path)
+{
+       return apply_save_autostash(path, 0);
+}
+
+int apply_autostash(const char *path)
+{
+       return apply_save_autostash(path, 1);
+}
+
+int apply_autostash_oid(const char *stash_oid)
+{
+       return apply_save_autostash_oid(stash_oid, 1);
+}
+
 static const char *reflog_message(struct replay_opts *opts,
        const char *sub_action, const char *fmt, ...)
 {
        va_list ap;
        static struct strbuf buf = STRBUF_INIT;
+       char *reflog_action = getenv(GIT_REFLOG_ACTION);
 
        va_start(ap, fmt);
        strbuf_reset(&buf);
-       strbuf_addstr(&buf, action_name(opts));
+       strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts));
        if (sub_action)
                strbuf_addf(&buf, " (%s)", sub_action);
        if (fmt) {
@@ -3747,9 +3939,9 @@ static int run_git_checkout(struct repository *r, struct replay_opts *opts,
 
        cmd.git_cmd = 1;
 
-       argv_array_push(&cmd.args, "checkout");
-       argv_array_push(&cmd.args, commit);
-       argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
+       strvec_push(&cmd.args, "checkout");
+       strvec_push(&cmd.args, commit);
+       strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
 
        if (opts->verbose)
                ret = run_command(&cmd);
@@ -3773,7 +3965,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
                return error(_("%s: not a valid OID"), orig_head);
 
        if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
                sequencer_remove_state(opts);
                return error(_("could not detach HEAD"));
        }
@@ -3815,11 +4007,16 @@ static int pick_commits(struct repository *r,
                        struct replay_opts *opts)
 {
        int res = 0, reschedule = 0;
+       char *prev_reflog_action;
 
+       /* Note that 0 for 3rd parameter of setenv means set only if not set */
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+       prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION));
        if (opts->allow_ff)
                assert(!(opts->signoff || opts->no_commit ||
-                               opts->record_origin || opts->edit));
+                        opts->record_origin || opts->edit ||
+                        opts->committer_date_is_author_date ||
+                        opts->ignore_date));
        if (read_and_refresh_cache(r, opts))
                return -1;
 
@@ -3841,7 +4038,7 @@ static int pick_commits(struct repository *r,
                                        fclose(f);
                                }
                                if (!opts->quiet)
-                                       fprintf(stderr, "Rebasing (%d/%d)%s",
+                                       fprintf(stderr, _("Rebasing (%d/%d)%s"),
                                                todo_list->done_nr,
                                                todo_list->total_nr,
                                                opts->verbose ? "\n" : "\r");
@@ -3861,12 +4058,14 @@ static int pick_commits(struct repository *r,
                }
                if (item->command <= TODO_SQUASH) {
                        if (is_rebase_i(opts))
-                               setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+                               setenv(GIT_REFLOG_ACTION, reflog_message(opts,
                                        command_to_string(item->command), NULL),
                                        1);
                        res = do_pick_commit(r, item->command, item->commit,
                                             opts, is_final_fixup(todo_list),
                                             &check_todo);
+                       if (is_rebase_i(opts))
+                               setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
                        if (is_rebase_i(opts) && res < 0) {
                                /* Reschedule */
                                advise(_(rescheduled_advice),
@@ -4068,9 +4267,9 @@ cleanup_head_ref:
 
                        child.in = open(rebase_path_rewritten_list(), O_RDONLY);
                        child.git_cmd = 1;
-                       argv_array_push(&child.args, "notes");
-                       argv_array_push(&child.args, "copy");
-                       argv_array_push(&child.args, "--for-rewrite=rebase");
+                       strvec_push(&child.args, "notes");
+                       strvec_push(&child.args, "copy");
+                       strvec_push(&child.args, "--for-rewrite=rebase");
                        /* we don't care if this copying failed */
                        run_command(&child);
 
@@ -4081,19 +4280,19 @@ cleanup_head_ref:
                                        O_RDONLY);
                                hook.stdout_to_stderr = 1;
                                hook.trace2_hook_name = "post-rewrite";
-                               argv_array_push(&hook.args, post_rewrite_hook);
-                               argv_array_push(&hook.args, "rebase");
+                               strvec_push(&hook.args, post_rewrite_hook);
+                               strvec_push(&hook.args, "rebase");
                                /* we don't care if this hook failed */
                                run_command(&hook);
                        }
                }
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
 
                if (!opts->quiet) {
                        if (!opts->verbose)
                                term_clear_line();
                        fprintf(stderr,
-                               "Successfully rebased and updated %s.\n",
+                               _("Successfully rebased and updated %s.\n"),
                                head_ref.buf);
                }
 
@@ -4112,8 +4311,8 @@ static int continue_single_pick(struct repository *r)
 {
        const char *argv[] = { "commit", NULL };
 
-       if (!file_exists(git_path_cherry_pick_head(r)) &&
-           !file_exists(git_path_revert_head(r)))
+       if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
+           !refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
                return error(_("no cherry-pick or revert in progress"));
        return run_command_v_opt(argv, RUN_GIT_CMD);
 }
@@ -4229,9 +4428,10 @@ static int commit_staged_changes(struct repository *r,
        }
 
        if (is_clean) {
-               const char *cherry_pick_head = git_path_cherry_pick_head(r);
-
-               if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
+               if (refs_ref_exists(get_main_ref_store(r),
+                                   "CHERRY_PICK_HEAD") &&
+                   refs_delete_ref(get_main_ref_store(r), "",
+                                   "CHERRY_PICK_HEAD", NULL, 0))
                        return error(_("could not remove CHERRY_PICK_HEAD"));
                if (!final_fixup)
                        return 0;
@@ -4258,6 +4458,22 @@ static int commit_staged_changes(struct repository *r,
        return 0;
 }
 
+static int init_committer(struct replay_opts *opts)
+{
+       struct ident_split id;
+       const char *committer;
+
+       committer = git_committer_info(IDENT_STRICT);
+       if (split_ident_line(&id, committer, strlen(committer)) < 0)
+               return error(_("invalid committer '%s'"), committer);
+       opts->committer_name =
+               xmemdupz(id.name_begin, id.name_end - id.name_begin);
+       opts->committer_email =
+               xmemdupz(id.mail_begin, id.mail_end - id.mail_end);
+
+       return 0;
+}
+
 int sequencer_continue(struct repository *r, struct replay_opts *opts)
 {
        struct todo_list todo_list = TODO_LIST_INIT;
@@ -4269,6 +4485,9 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
        if (read_populate_opts(opts))
                return -1;
        if (is_rebase_i(opts)) {
+               if (opts->committer_date_is_author_date && init_committer(opts))
+                       return -1;
+
                if ((res = read_populate_todo(r, &todo_list, opts)))
                        goto release_todo_list;
 
@@ -4290,8 +4509,9 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
 
        if (!is_rebase_i(opts)) {
                /* Verify that the conflict has been resolved */
-               if (file_exists(git_path_cherry_pick_head(r)) ||
-                   file_exists(git_path_revert_head(r))) {
+               if (refs_ref_exists(get_main_ref_store(r),
+                                   "CHERRY_PICK_HEAD") ||
+                   refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
                        res = continue_single_pick(r);
                        if (res)
                                goto release_todo_list;
@@ -4305,7 +4525,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)
                struct strbuf buf = STRBUF_INIT;
                struct object_id oid;
 
-               if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
+               if (read_oneliner(&buf, rebase_path_stopped_sha(),
+                                 READ_ONELINER_SKIP_IF_EMPTY) &&
                    !get_oid_committish(buf.buf, &oid))
                        record_in_rewritten(&oid, peek_command(&todo_list, 0));
                strbuf_release(&buf);
@@ -4607,6 +4828,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                                   struct rev_info *revs, struct strbuf *out,
                                   unsigned flags)
 {
+       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;
@@ -4661,6 +4883,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                is_empty = is_original_commit_empty(commit);
                if (!is_empty && (commit->object.flags & PATCHSAME))
                        continue;
+               if (is_empty && !keep_empty)
+                       continue;
 
                strbuf_reset(&oneline);
                pretty_print_commit(pp, commit, &oneline);
@@ -4672,6 +4896,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
                        strbuf_addf(&buf, "%s %s %s", cmd_pick,
                                    oid_to_hex(&commit->object.oid),
                                    oneline.buf);
+                       if (is_empty)
+                               strbuf_addf(&buf, " %c empty",
+                                           comment_line_char);
 
                        FLEX_ALLOC_STR(entry, string, buf.buf);
                        oidcpy(&entry->entry.oid, &commit->object.oid);
@@ -4835,14 +5062,16 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        struct pretty_print_context pp = {0};
        struct rev_info revs;
        struct commit *commit;
+       int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
        const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
        int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
+       int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
 
        repo_init_revisions(r, &revs, NULL);
        revs.verbose_header = 1;
        if (!rebase_merges)
                revs.max_parents = 1;
-       revs.cherry_mark = 1;
+       revs.cherry_mark = !reapply_cherry_picks;
        revs.limited = 1;
        revs.reverse = 1;
        revs.right_only = 1;
@@ -4874,9 +5103,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
 
                if (!is_empty && (commit->object.flags & PATCHSAME))
                        continue;
+               if (is_empty && !keep_empty)
+                       continue;
                strbuf_addf(out, "%s %s ", insn,
                            oid_to_hex(&commit->object.oid));
                pretty_print_commit(&pp, commit, out);
+               if (is_empty)
+                       strbuf_addf(out, " %c empty", comment_line_char);
                strbuf_addch(out, '\n');
        }
        return 0;
@@ -4963,6 +5196,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
                max = num;
 
        for (item = todo_list->items, i = 0; i < max; i++, item++) {
+               char cmd;
+
                /* if the item is not a command write it and continue */
                if (item->command >= TODO_COMMENT) {
                        strbuf_addf(buf, "%.*s\n", item->arg_len,
@@ -4971,8 +5206,9 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
                }
 
                /* add command to the buffer */
-               if (flags & TODO_LIST_ABBREVIATE_CMDS)
-                       strbuf_addch(buf, command_to_char(item->command));
+               cmd = command_to_char(item->command);
+               if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd)
+                       strbuf_addch(buf, cmd);
                else
                        strbuf_addstr(buf, command_to_string(item->command));
 
@@ -5073,13 +5309,14 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
                    struct string_list *commands, unsigned autosquash,
                    struct todo_list *todo_list)
 {
-       const char *shortonto, *todo_file = rebase_path_todo();
+       char shortonto[GIT_MAX_HEXSZ + 1];
+       const char *todo_file = rebase_path_todo();
        struct todo_list new_todo = TODO_LIST_INIT;
        struct strbuf *buf = &todo_list->buf, buf2 = STRBUF_INIT;
        struct object_id oid = onto->object.oid;
        int res;
 
-       shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
+       find_unique_abbrev_r(shortonto, &oid, DEFAULT_ABBREV);
 
        if (buf->len == 0) {
                struct todo_item *item = append_new_todo(todo_list);
@@ -5095,7 +5332,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
                todo_list_add_exec_commands(todo_list, commands);
 
        if (count_commands(todo_list) == 0) {
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
                sequencer_remove_state(opts);
 
                return error(_("nothing to do"));
@@ -5106,12 +5343,12 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
        if (res == -1)
                return -1;
        else if (res == -2) {
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
                sequencer_remove_state(opts);
 
                return -1;
        } else if (res == -3) {
-               apply_autostash(opts);
+               apply_autostash(rebase_path_autostash());
                sequencer_remove_state(opts);
                todo_list_release(&new_todo);
 
@@ -5145,6 +5382,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
 
        res = -1;
 
+       if (opts->committer_date_is_author_date && init_committer(opts))
+               goto cleanup;
+
        if (checkout_onto(r, opts, onto_name, &oid, orig_head))
                goto cleanup;
 
@@ -5280,10 +5520,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
                        todo_list->items[i].command =
                                starts_with(subject, "fixup!") ?
                                TODO_FIXUP : TODO_SQUASH;
-                       if (next[i2] < 0)
+                       if (tail[i2] < 0) {
+                               next[i] = next[i2];
                                next[i2] = i;
-                       else
+                       } else {
+                               next[i] = next[tail[i2]];
                                next[tail[i2]] = i;
+                       }
                        tail[i2] = i;
                } else if (!hashmap_get_from_hash(&subject2item,
                                                strhash(subject), subject)) {
@@ -5334,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
 
 int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
 {
-       if (file_exists(git_path_cherry_pick_head(r))) {
+       if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
                struct object_id cherry_pick_head, rebase_head;
 
                if (file_exists(git_path_seq_dir()))