]> git.ipfire.org Git - thirdparty/git.git/blobdiff - sequencer.c
Merge branch 'jk/sequencer-missing-author-name-check'
[thirdparty/git.git] / sequencer.c
index d8ae21d0d220dee4544185d72c23edcbca8132f5..debb2ecbafe2efdff334042b00c85badf7b88dfe 100644 (file)
@@ -35,6 +35,8 @@
 #include "commit-reach.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "branch.h"
+#include "log-tree.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -147,6 +149,20 @@ static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
  */
 static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
 
+/*
+ * The update-refs file stores a list of refs that will be updated at the end
+ * of the rebase sequence. The 'update-ref <ref>' commands in the todo file
+ * update the OIDs for the refs in this file, but the refs are not updated
+ * until the end of the rebase sequence.
+ *
+ * rebase_path_update_refs() returns the path to this file for a given
+ * worktree directory. For the current worktree, pass the_repository->gitdir.
+ */
+static char *rebase_path_update_refs(const char *wt_git_dir)
+{
+       return xstrfmt("%s/rebase-merge/update-refs", wt_git_dir);
+}
+
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line.
@@ -169,6 +185,30 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-res
 static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits")
 static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits")
 
+/**
+ * A 'struct update_refs_record' represents a value in the update-refs
+ * list. We use a string_list to map refs to these (before, after) pairs.
+ */
+struct update_ref_record {
+       struct object_id before;
+       struct object_id after;
+};
+
+static struct update_ref_record *init_update_ref_record(const char *ref)
+{
+       struct update_ref_record *rec;
+
+       CALLOC_ARRAY(rec, 1);
+
+       oidcpy(&rec->before, null_oid());
+       oidcpy(&rec->after, null_oid());
+
+       /* This may fail, but that's fine, we will keep the null OID. */
+       read_ref(ref, &rec->before);
+
+       return rec;
+}
+
 static int git_sequencer_config(const char *k, const char *v, void *cb)
 {
        struct replay_opts *opts = cb;
@@ -221,6 +261,9 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)
                return ret;
        }
 
+       if (opts->action == REPLAY_REVERT && !strcmp(k, "revert.reference"))
+               opts->commit_use_reference = git_config_bool(k, v);
+
        status = git_gpg_config(k, v, NULL);
        if (status)
                return status;
@@ -494,7 +537,7 @@ static struct tree *empty_tree(struct repository *r)
 static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
 {
        if (repo_read_index_unmerged(repo))
-               return error_resolve_conflict(_(action_name(opts)));
+               return error_resolve_conflict(action_name(opts));
 
        error(_("your local changes would be overwritten by %s."),
                _(action_name(opts)));
@@ -532,7 +575,7 @@ static int fast_forward_to(struct repository *r,
        if (checkout_fast_forward(r, from, to, 1))
                return -1; /* the callee should have complained already */
 
-       strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
+       strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
 
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
@@ -919,7 +962,7 @@ static char *get_author(const char *message)
        return NULL;
 }
 
-static const char *author_date_from_env_array(const struct strvec *env)
+static const char *author_date_from_env(const struct strvec *env)
 {
        int i;
        const char *date;
@@ -1000,7 +1043,7 @@ static int run_git_commit(const char *defmsg,
        if (is_rebase_i(opts) &&
            ((opts->committer_date_is_author_date && !opts->ignore_date) ||
             !(!defmsg && (flags & AMEND_MSG))) &&
-           read_env_script(&cmd.env_array)) {
+           read_env_script(&cmd.env)) {
                const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                return error(_(staged_changes_advice),
@@ -1008,12 +1051,12 @@ static int run_git_commit(const char *defmsg,
        }
 
        if (opts->committer_date_is_author_date)
-               strvec_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
+               strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
                             opts->ignore_date ?
                             "" :
-                            author_date_from_env_array(&cmd.env_array));
+                            author_date_from_env(&cmd.env));
        if (opts->ignore_date)
-               strvec_push(&cmd.env_array, "GIT_AUTHOR_DATE=");
+               strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
 
        strvec_push(&cmd.args, "commit");
 
@@ -1220,7 +1263,7 @@ static int run_prepare_commit_msg_hook(struct repository *r,
        } else {
                arg1 = "message";
        }
-       if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name,
+       if (run_commit_hook(0, r->index_file, NULL, "prepare-commit-msg", name,
                            arg1, arg2, NULL))
                ret = error(_("'prepare-commit-msg' hook failed"));
 
@@ -1281,7 +1324,6 @@ void print_commit_summary(struct repository *r,
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
        struct ref_store *refs;
-       int resolve_errno;
 
        commit = lookup_commit(r, oid);
        if (!commit)
@@ -1328,16 +1370,12 @@ void print_commit_summary(struct repository *r,
        get_commit_format(format.buf, &rev);
        rev.always_show_header = 0;
        rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
-       rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
        refs = get_main_ref_store(the_repository);
-       head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL,
-                                      &resolve_errno);
-       if (!head) {
-               errno = resolve_errno;
-               die_errno(_("unable to resolve HEAD after creating commit"));
-       }
+       head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
+       if (!head)
+               die(_("unable to resolve HEAD after creating commit"));
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
@@ -1351,6 +1389,7 @@ void print_commit_summary(struct repository *r,
                log_tree_commit(&rev, commit);
        }
 
+       release_revisions(&rev);
        strbuf_release(&format);
 }
 
@@ -1556,7 +1595,7 @@ static int try_to_commit(struct repository *r,
                goto out;
        }
 
-       run_commit_hook(0, r->index_file, "post-commit", NULL);
+       run_commit_hook(0, r->index_file, NULL, "post-commit", NULL);
        if (flags & AMEND_MSG)
                commit_post_rewrite(r, current_head, oid);
 
@@ -1690,20 +1729,21 @@ static struct {
        char c;
        const char *str;
 } todo_command_info[] = {
-       { 'p', "pick" },
-       { 0,   "revert" },
-       { 'e', "edit" },
-       { 'r', "reword" },
-       { 'f', "fixup" },
-       { 's', "squash" },
-       { 'x', "exec" },
-       { 'b', "break" },
-       { 'l', "label" },
-       { 't', "reset" },
-       { 'm', "merge" },
-       { 0,   "noop" },
-       { 'd', "drop" },
-       { 0,   NULL }
+       [TODO_PICK] = { 'p', "pick" },
+       [TODO_REVERT] = { 0,   "revert" },
+       [TODO_EDIT] = { 'e', "edit" },
+       [TODO_REWORD] = { 'r', "reword" },
+       [TODO_FIXUP] = { 'f', "fixup" },
+       [TODO_SQUASH] = { 's', "squash" },
+       [TODO_EXEC] = { 'x', "exec" },
+       [TODO_BREAK] = { 'b', "break" },
+       [TODO_LABEL] = { 'l', "label" },
+       [TODO_RESET] = { 't', "reset" },
+       [TODO_MERGE] = { 'm', "merge" },
+       [TODO_UPDATE_REF] = { 'u', "update-ref" },
+       [TODO_NOOP] = { 0,   "noop" },
+       [TODO_DROP] = { 'd', "drop" },
+       [TODO_COMMENT] = { 0,   NULL },
 };
 
 static const char *command_to_string(const enum todo_command command)
@@ -2063,6 +2103,20 @@ static int should_edit(struct replay_opts *opts) {
        return opts->edit;
 }
 
+static void refer_to_commit(struct replay_opts *opts,
+                           struct strbuf *msgbuf, struct commit *commit)
+{
+       if (opts->commit_use_reference) {
+               struct pretty_print_context ctx = {
+                       .abbrev = DEFAULT_ABBREV,
+                       .date_mode.type = DATE_SHORT,
+               };
+               format_commit_message(commit, "%h (%s, %ad)", msgbuf, &ctx);
+       } else {
+               strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
+       }
+}
+
 static int do_pick_commit(struct repository *r,
                          struct todo_item *item,
                          struct replay_opts *opts,
@@ -2171,14 +2225,20 @@ static int do_pick_commit(struct repository *r,
                base_label = msg.label;
                next = parent;
                next_label = msg.parent_label;
-               strbuf_addstr(&msgbuf, "Revert \"");
-               strbuf_addstr(&msgbuf, msg.subject);
-               strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
-               strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
+               if (opts->commit_use_reference) {
+                       strbuf_addstr(&msgbuf,
+                               "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***");
+               } else {
+                       strbuf_addstr(&msgbuf, "Revert \"");
+                       strbuf_addstr(&msgbuf, msg.subject);
+                       strbuf_addstr(&msgbuf, "\"");
+               }
+               strbuf_addstr(&msgbuf, "\n\nThis reverts commit ");
+               refer_to_commit(opts, &msgbuf, commit);
 
                if (commit->parents && commit->parents->next) {
                        strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
-                       strbuf_addstr(&msgbuf, oid_to_hex(&parent->object.oid));
+                       refer_to_commit(opts, &msgbuf, parent);
                }
                strbuf_addstr(&msgbuf, ".\n");
        } else {
@@ -2362,7 +2422,7 @@ static int read_and_refresh_cache(struct repository *r,
        if (repo_read_index(r) < 0) {
                rollback_lock_file(&index_lock);
                return error(_("git %s: failed to read the index"),
-                       _(action_name(opts)));
+                       action_name(opts));
        }
        refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
 
@@ -2370,7 +2430,7 @@ static int read_and_refresh_cache(struct repository *r,
                if (write_locked_index(r->index, &index_lock,
                                       COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
                        return error(_("git %s: failed to refresh the index"),
-                               _(action_name(opts)));
+                               action_name(opts));
                }
        }
 
@@ -2462,7 +2522,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
                             command_to_string(item->command));
 
        if (item->command == TODO_EXEC || item->command == TODO_LABEL ||
-           item->command == TODO_RESET) {
+           item->command == TODO_RESET || item->command == TODO_UPDATE_REF) {
                item->commit = NULL;
                item->arg_offset = bol - buf;
                item->arg_len = (int)(eol - bol);
@@ -2806,7 +2866,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
                return error(_("invalid key: %s"), key);
 
        if (!error_flag)
-               return error(_("invalid value for %s: %s"), key, value);
+               return error(_("invalid value for '%s': '%s'"), key, value);
 
        return 0;
 }
@@ -3419,6 +3479,7 @@ static int make_patch(struct repository *r,
                unuse_commit_buffer(commit, commit_buffer);
        }
        strbuf_release(&buf);
+       release_revisions(&log_tree_opt);
 
        return res;
 }
@@ -3692,7 +3753,7 @@ static int do_reset(struct repository *r,
        init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
 
        if (repo_read_index_unmerged(r)) {
-               ret = error_resolve_conflict(_(action_name(opts)));
+               ret = error_resolve_conflict(action_name(opts));
                goto cleanup;
        }
 
@@ -3753,7 +3814,7 @@ static int do_merge(struct repository *r,
        int run_commit_flags = 0;
        struct strbuf ref_name = STRBUF_INIT;
        struct commit *head_commit, *merge_commit, *i;
-       struct commit_list *bases, *j, *reversed = NULL;
+       struct commit_list *bases, *j;
        struct commit_list *to_merge = NULL, **tail = &to_merge;
        const char *strategy = !opts->xopts_nr &&
                (!opts->strategy ||
@@ -3916,7 +3977,7 @@ static int do_merge(struct repository *r,
                /* Octopus merge */
                struct child_process cmd = CHILD_PROCESS_INIT;
 
-               if (read_env_script(&cmd.env_array)) {
+               if (read_env_script(&cmd.env)) {
                        const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                        ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
@@ -3924,12 +3985,12 @@ static int do_merge(struct repository *r,
                }
 
                if (opts->committer_date_is_author_date)
-                       strvec_pushf(&cmd.env_array, "GIT_COMMITTER_DATE=%s",
+                       strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s",
                                     opts->ignore_date ?
                                     "" :
-                                    author_date_from_env_array(&cmd.env_array));
+                                    author_date_from_env(&cmd.env));
                if (opts->ignore_date)
-                       strvec_push(&cmd.env_array, "GIT_AUTHOR_DATE=");
+                       strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
 
                cmd.git_cmd = 1;
                strvec_push(&cmd.args, "merge");
@@ -3988,9 +4049,7 @@ static int do_merge(struct repository *r,
                      git_path_merge_head(r), 0);
        write_message("no-ff", 5, git_path_merge_mode(r), 0);
 
-       for (j = bases; j; j = j->next)
-               commit_list_insert(j->item, &reversed);
-       free_commit_list(bases);
+       bases = reverse_commit_list(bases);
 
        repo_read_index(r);
        init_merge_options(&o, r);
@@ -4006,10 +4065,10 @@ static int do_merge(struct repository *r,
                 * update the index and working copy immediately.
                 */
                ret = merge_ort_recursive(&o,
-                                         head_commit, merge_commit, reversed,
+                                         head_commit, merge_commit, bases,
                                          &i);
        } else {
-               ret = merge_recursive(&o, head_commit, merge_commit, reversed,
+               ret = merge_recursive(&o, head_commit, merge_commit, bases,
                                      &i);
        }
        if (ret <= 0)
@@ -4063,6 +4122,221 @@ leave_merge:
        return ret;
 }
 
+static int write_update_refs_state(struct string_list *refs_to_oids)
+{
+       int result = 0;
+       struct lock_file lock = LOCK_INIT;
+       FILE *fp = NULL;
+       struct string_list_item *item;
+       char *path;
+
+       if (!refs_to_oids->nr)
+               return 0;
+
+       path = rebase_path_update_refs(the_repository->gitdir);
+
+       if (safe_create_leading_directories(path)) {
+               result = error(_("unable to create leading directories of %s"),
+                              path);
+               goto cleanup;
+       }
+
+       if (hold_lock_file_for_update(&lock, path, 0) < 0) {
+               result = error(_("another 'rebase' process appears to be running; "
+                                "'%s.lock' already exists"),
+                              path);
+               goto cleanup;
+       }
+
+       fp = fdopen_lock_file(&lock, "w");
+       if (!fp) {
+               result = error_errno(_("could not open '%s' for writing"), path);
+               rollback_lock_file(&lock);
+               goto cleanup;
+       }
+
+       for_each_string_list_item(item, refs_to_oids) {
+               struct update_ref_record *rec = item->util;
+               fprintf(fp, "%s\n%s\n%s\n", item->string,
+                       oid_to_hex(&rec->before), oid_to_hex(&rec->after));
+       }
+
+       result = commit_lock_file(&lock);
+
+cleanup:
+       free(path);
+       return result;
+}
+
+/*
+ * Parse the update-refs file for the current rebase, then remove the
+ * refs that do not appear in the todo_list (and have not had updated
+ * values stored) and add refs that are in the todo_list but not
+ * represented in the update-refs file.
+ *
+ * If there are changes to the update-refs list, then write the new state
+ * to disk.
+ */
+void todo_list_filter_update_refs(struct repository *r,
+                                 struct todo_list *todo_list)
+{
+       int i;
+       int updated = 0;
+       struct string_list update_refs = STRING_LIST_INIT_DUP;
+
+       sequencer_get_update_refs_state(r->gitdir, &update_refs);
+
+       /*
+        * For each item in the update_refs list, if it has no updated
+        * value and does not appear in the todo_list, then remove it
+        * from the update_refs list.
+        */
+       for (i = 0; i < update_refs.nr; i++) {
+               int j;
+               int found = 0;
+               const char *ref = update_refs.items[i].string;
+               size_t reflen = strlen(ref);
+               struct update_ref_record *rec = update_refs.items[i].util;
+
+               /* OID already stored as updated. */
+               if (!is_null_oid(&rec->after))
+                       continue;
+
+               for (j = 0; !found && j < todo_list->total_nr; j++) {
+                       struct todo_item *item = &todo_list->items[j];
+                       const char *arg = todo_list->buf.buf + item->arg_offset;
+
+                       if (item->command != TODO_UPDATE_REF)
+                               continue;
+
+                       if (item->arg_len != reflen ||
+                           strncmp(arg, ref, reflen))
+                               continue;
+
+                       found = 1;
+               }
+
+               if (!found) {
+                       free(update_refs.items[i].string);
+                       free(update_refs.items[i].util);
+
+                       update_refs.nr--;
+                       MOVE_ARRAY(update_refs.items + i, update_refs.items + i + 1, update_refs.nr - i);
+
+                       updated = 1;
+                       i--;
+               }
+       }
+
+       /*
+        * For each todo_item, check if its ref is in the update_refs list.
+        * If not, then add it as an un-updated ref.
+        */
+       for (i = 0; i < todo_list->total_nr; i++) {
+               struct todo_item *item = &todo_list->items[i];
+               const char *arg = todo_list->buf.buf + item->arg_offset;
+               int j, found = 0;
+
+               if (item->command != TODO_UPDATE_REF)
+                       continue;
+
+               for (j = 0; !found && j < update_refs.nr; j++) {
+                       const char *ref = update_refs.items[j].string;
+
+                       found = strlen(ref) == item->arg_len &&
+                               !strncmp(ref, arg, item->arg_len);
+               }
+
+               if (!found) {
+                       struct string_list_item *inserted;
+                       struct strbuf argref = STRBUF_INIT;
+
+                       strbuf_add(&argref, arg, item->arg_len);
+                       inserted = string_list_insert(&update_refs, argref.buf);
+                       inserted->util = init_update_ref_record(argref.buf);
+                       strbuf_release(&argref);
+                       updated = 1;
+               }
+       }
+
+       if (updated)
+               write_update_refs_state(&update_refs);
+       string_list_clear(&update_refs, 1);
+}
+
+static int do_update_ref(struct repository *r, const char *refname)
+{
+       struct string_list_item *item;
+       struct string_list list = STRING_LIST_INIT_DUP;
+
+       if (sequencer_get_update_refs_state(r->gitdir, &list))
+               return -1;
+
+       for_each_string_list_item(item, &list) {
+               if (!strcmp(item->string, refname)) {
+                       struct update_ref_record *rec = item->util;
+                       if (read_ref("HEAD", &rec->after))
+                               return -1;
+                       break;
+               }
+       }
+
+       write_update_refs_state(&list);
+       string_list_clear(&list, 1);
+       return 0;
+}
+
+static int do_update_refs(struct repository *r, int quiet)
+{
+       int res = 0;
+       struct string_list_item *item;
+       struct string_list refs_to_oids = STRING_LIST_INIT_DUP;
+       struct ref_store *refs = get_main_ref_store(r);
+       struct strbuf update_msg = STRBUF_INIT;
+       struct strbuf error_msg = STRBUF_INIT;
+
+       if ((res = sequencer_get_update_refs_state(r->gitdir, &refs_to_oids)))
+               return res;
+
+       for_each_string_list_item(item, &refs_to_oids) {
+               struct update_ref_record *rec = item->util;
+               int loop_res;
+
+               loop_res = refs_update_ref(refs, "rewritten during rebase",
+                                          item->string,
+                                          &rec->after, &rec->before,
+                                          0, UPDATE_REFS_MSG_ON_ERR);
+               res |= loop_res;
+
+               if (quiet)
+                       continue;
+
+               if (loop_res)
+                       strbuf_addf(&error_msg, "\t%s\n", item->string);
+               else
+                       strbuf_addf(&update_msg, "\t%s\n", item->string);
+       }
+
+       if (!quiet &&
+           (update_msg.len || error_msg.len)) {
+               fprintf(stderr,
+                       _("Updated the following refs with %s:\n%s"),
+                       "--update-refs",
+                       update_msg.buf);
+
+               if (res)
+                       fprintf(stderr,
+                               _("Failed to update the following refs with %s:\n%s"),
+                               "--update-refs",
+                               error_msg.buf);
+       }
+
+       string_list_clear(&refs_to_oids, 1);
+       strbuf_release(&update_msg);
+       strbuf_release(&error_msg);
+       return res;
+}
+
 static int is_final_fixup(struct todo_list *todo_list)
 {
        int i = todo_list->current;
@@ -4089,8 +4363,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
        return -1;
 }
 
-void create_autostash(struct repository *r, const char *path,
-                     const char *default_reflog_action)
+void create_autostash(struct repository *r, const char *path)
 {
        struct strbuf buf = STRBUF_INIT;
        struct lock_file lock_file = LOCK_INIT;
@@ -4105,6 +4378,7 @@ void create_autostash(struct repository *r, const char *path,
        if (has_unstaged_changes(r, 1) ||
            has_uncommitted_changes(r, 1)) {
                struct child_process stash = CHILD_PROCESS_INIT;
+               struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };
                struct object_id oid;
 
                strvec_pushl(&stash.args,
@@ -4126,11 +4400,8 @@ void create_autostash(struct repository *r, const char *path,
                            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)
+               if (reset_head(r, &ropts) < 0)
                        die(_("could not reset --hard"));
-
                if (discard_index(r->index) < 0 ||
                        repo_read_index(r) < 0)
                        die(_("could not read index"));
@@ -4215,47 +4486,26 @@ int apply_autostash_oid(const char *stash_oid)
        return apply_save_autostash_oid(stash_oid, 1);
 }
 
-static int run_git_checkout(struct repository *r, struct replay_opts *opts,
-                           const char *commit, const char *action)
-{
-       struct child_process cmd = CHILD_PROCESS_INIT;
-       int ret;
-
-       cmd.git_cmd = 1;
-
-       if (startup_info->original_cwd) {
-               cmd.dir = startup_info->original_cwd;
-               strvec_pushf(&cmd.env_array, "%s=%s",
-                            GIT_WORK_TREE_ENVIRONMENT, r->worktree);
-       }
-       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);
-       else
-               ret = run_command_silent_on_success(&cmd);
-
-       if (!ret)
-               discard_index(r->index);
-
-       return ret;
-}
-
 static int checkout_onto(struct repository *r, struct replay_opts *opts,
                         const char *onto_name, const struct object_id *onto,
                         const struct object_id *orig_head)
 {
-       const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
-
-       if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
+       struct reset_head_opts ropts = {
+               .oid = onto,
+               .orig_head = orig_head,
+               .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+                               RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+               .head_msg = reflog_message(opts, "start", "checkout %s",
+                                          onto_name),
+               .default_reflog_action = "rebase"
+       };
+       if (reset_head(r, &ropts)) {
                apply_autostash(rebase_path_autostash());
                sequencer_remove_state(opts);
                return error(_("could not detach HEAD"));
        }
 
-       return update_ref(NULL, "ORIG_HEAD", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+       return 0;
 }
 
 static int stopped_at_head(struct repository *r)
@@ -4462,6 +4712,12 @@ static int pick_commits(struct repository *r,
                                return error_with_patch(r, item->commit,
                                                        arg, item->arg_len,
                                                        opts, res, 0);
+               } else if (item->command == TODO_UPDATE_REF) {
+                       struct strbuf ref = STRBUF_INIT;
+                       strbuf_add(&ref, arg, item->arg_len);
+                       if ((res = do_update_ref(r, ref.buf)))
+                               reschedule = 1;
+                       strbuf_release(&ref);
                } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
 
@@ -4555,6 +4811,7 @@ cleanup_head_ref:
                                              &log_tree_opt.diffopt);
                                log_tree_diff_flush(&log_tree_opt);
                        }
+                       release_revisions(&log_tree_opt);
                }
                flush_rewritten_pending();
                if (!stat(rebase_path_rewritten_list(), &st) &&
@@ -4596,6 +4853,9 @@ cleanup_head_ref:
 
                strbuf_release(&buf);
                strbuf_release(&head_ref);
+
+               if (do_update_refs(r, opts->quiet))
+                       return -1;
        }
 
        /*
@@ -4994,7 +5254,8 @@ struct labels_entry {
        char label[FLEX_ARRAY];
 };
 
-static int labels_cmp(const void *fndata, const struct hashmap_entry *eptr,
+static int labels_cmp(const void *fndata UNUSED,
+                     const struct hashmap_entry *eptr,
                      const struct hashmap_entry *entry_or_key, const void *key)
 {
        const struct labels_entry *a, *b;
@@ -5381,6 +5642,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
        int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS;
        int skipped_commit = 0;
+       int ret = 0;
 
        repo_init_revisions(r, &revs, NULL);
        revs.verbose_header = 1;
@@ -5404,14 +5666,20 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        pp.fmt = revs.commit_format;
        pp.output_encoding = get_log_output_encoding();
 
-       if (setup_revisions(argc, argv, &revs, NULL) > 1)
-               return error(_("make_script: unhandled options"));
+       if (setup_revisions(argc, argv, &revs, NULL) > 1) {
+               ret = error(_("make_script: unhandled options"));
+               goto cleanup;
+       }
 
-       if (prepare_revision_walk(&revs) < 0)
-               return error(_("make_script: error preparing revisions"));
+       if (prepare_revision_walk(&revs) < 0) {
+               ret = error(_("make_script: error preparing revisions"));
+               goto cleanup;
+       }
 
-       if (rebase_merges)
-               return make_script_with_merges(&pp, &revs, out, flags);
+       if (rebase_merges) {
+               ret = make_script_with_merges(&pp, &revs, out, flags);
+               goto cleanup;
+       }
 
        while ((commit = get_revision(&revs))) {
                int is_empty = is_original_commit_empty(commit);
@@ -5435,7 +5703,9 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
        if (skipped_commit)
                advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
                                  _("use --reapply-cherry-picks to include skipped commits"));
-       return 0;
+cleanup:
+       release_revisions(&revs);
+       return ret;
 }
 
 /*
@@ -5634,10 +5904,135 @@ static int skip_unnecessary_picks(struct repository *r,
        return 0;
 }
 
+struct todo_add_branch_context {
+       struct todo_item *items;
+       size_t items_nr;
+       size_t items_alloc;
+       struct strbuf *buf;
+       struct commit *commit;
+       struct string_list refs_to_oids;
+};
+
+static int add_decorations_to_list(const struct commit *commit,
+                                  struct todo_add_branch_context *ctx)
+{
+       const struct name_decoration *decoration = get_name_decoration(&commit->object);
+       const char *head_ref = resolve_ref_unsafe("HEAD",
+                                                 RESOLVE_REF_READING,
+                                                 NULL,
+                                                 NULL);
+
+       while (decoration) {
+               struct todo_item *item;
+               const char *path;
+               size_t base_offset = ctx->buf->len;
+
+               /*
+                * If the branch is the current HEAD, then it will be
+                * updated by the default rebase behavior.
+                */
+               if (head_ref && !strcmp(head_ref, decoration->name)) {
+                       decoration = decoration->next;
+                       continue;
+               }
+
+               ALLOC_GROW(ctx->items,
+                       ctx->items_nr + 1,
+                       ctx->items_alloc);
+               item = &ctx->items[ctx->items_nr];
+               memset(item, 0, sizeof(*item));
+
+               /* If the branch is checked out, then leave a comment instead. */
+               if ((path = branch_checked_out(decoration->name))) {
+                       item->command = TODO_COMMENT;
+                       strbuf_addf(ctx->buf, "# Ref %s checked out at '%s'\n",
+                                   decoration->name, path);
+               } else {
+                       struct string_list_item *sti;
+                       item->command = TODO_UPDATE_REF;
+                       strbuf_addf(ctx->buf, "%s\n", decoration->name);
+
+                       sti = string_list_insert(&ctx->refs_to_oids,
+                                                decoration->name);
+                       sti->util = init_update_ref_record(decoration->name);
+               }
+
+               item->offset_in_buf = base_offset;
+               item->arg_offset = base_offset;
+               item->arg_len = ctx->buf->len - base_offset;
+               ctx->items_nr++;
+
+               decoration = decoration->next;
+       }
+
+       return 0;
+}
+
+/*
+ * For each 'pick' command, find out if the commit has a decoration in
+ * refs/heads/. If so, then add a 'label for-update-refs/' command.
+ */
+static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
+{
+       int i, res;
+       static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+       static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;
+       static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
+       struct decoration_filter decoration_filter = {
+               .include_ref_pattern = &decorate_refs_include,
+               .exclude_ref_pattern = &decorate_refs_exclude,
+               .exclude_ref_config_pattern = &decorate_refs_exclude_config,
+       };
+       struct todo_add_branch_context ctx = {
+               .buf = &todo_list->buf,
+               .refs_to_oids = STRING_LIST_INIT_DUP,
+       };
+
+       ctx.items_alloc = 2 * todo_list->nr + 1;
+       ALLOC_ARRAY(ctx.items, ctx.items_alloc);
+
+       string_list_append(&decorate_refs_include, "refs/heads/");
+       load_ref_decorations(&decoration_filter, 0);
+
+       for (i = 0; i < todo_list->nr; ) {
+               struct todo_item *item = &todo_list->items[i];
+
+               /* insert ith item into new list */
+               ALLOC_GROW(ctx.items,
+                          ctx.items_nr + 1,
+                          ctx.items_alloc);
+
+               ctx.items[ctx.items_nr++] = todo_list->items[i++];
+
+               if (item->commit) {
+                       ctx.commit = item->commit;
+                       add_decorations_to_list(item->commit, &ctx);
+               }
+       }
+
+       res = write_update_refs_state(&ctx.refs_to_oids);
+
+       string_list_clear(&ctx.refs_to_oids, 1);
+
+       if (res) {
+               /* we failed, so clean up the new list. */
+               free(ctx.items);
+               return res;
+       }
+
+       free(todo_list->items);
+       todo_list->items = ctx.items;
+       todo_list->nr = ctx.items_nr;
+       todo_list->alloc = ctx.items_alloc;
+
+       return 0;
+}
+
 int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
                    const char *shortrevisions, const char *onto_name,
                    struct commit *onto, const struct object_id *orig_head,
                    struct string_list *commands, unsigned autosquash,
+                   unsigned update_refs,
                    struct todo_list *todo_list)
 {
        char shortonto[GIT_MAX_HEXSZ + 1];
@@ -5656,6 +6051,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
                item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0;
        }
 
+       if (update_refs && todo_list_add_update_ref_commands(todo_list))
+               return -1;
+
        if (autosquash && todo_list_rearrange_squash(todo_list))
                return -1;
 
@@ -5734,7 +6132,7 @@ struct subject2item_entry {
        char subject[FLEX_ARRAY];
 };
 
-static int subject2item_cmp(const void *fndata,
+static int subject2item_cmp(const void *fndata UNUSED,
                            const struct hashmap_entry *eptr,
                            const struct hashmap_entry *entry_or_key,
                            const void *key)
@@ -5805,8 +6203,6 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
                        return error(_("the script was already rearranged."));
                }
 
-               *commit_todo_item_at(&commit_todo, item->commit) = item;
-
                parse_commit(item->commit);
                commit_buffer = logmsg_reencode(item->commit, NULL, "UTF-8");
                find_commit_subject(commit_buffer, &subject);
@@ -5873,6 +6269,8 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
                                        strhash(entry->subject));
                        hashmap_put(&subject2item, &entry->entry);
                }
+
+               *commit_todo_item_at(&commit_todo, item->commit) = item;
        }
 
        if (rearranged) {
@@ -5932,3 +6330,54 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence)
 
        return 0;
 }
+
+int sequencer_get_update_refs_state(const char *wt_dir,
+                                   struct string_list *refs)
+{
+       int result = 0;
+       FILE *fp = NULL;
+       struct strbuf ref = STRBUF_INIT;
+       struct strbuf hash = STRBUF_INIT;
+       struct update_ref_record *rec = NULL;
+
+       char *path = rebase_path_update_refs(wt_dir);
+
+       fp = fopen(path, "r");
+       if (!fp)
+               goto cleanup;
+
+       while (strbuf_getline(&ref, fp) != EOF) {
+               struct string_list_item *item;
+
+               CALLOC_ARRAY(rec, 1);
+
+               if (strbuf_getline(&hash, fp) == EOF ||
+                   get_oid_hex(hash.buf, &rec->before)) {
+                       warning(_("update-refs file at '%s' is invalid"),
+                                 path);
+                       result = -1;
+                       goto cleanup;
+               }
+
+               if (strbuf_getline(&hash, fp) == EOF ||
+                   get_oid_hex(hash.buf, &rec->after)) {
+                       warning(_("update-refs file at '%s' is invalid"),
+                                 path);
+                       result = -1;
+                       goto cleanup;
+               }
+
+               item = string_list_insert(refs, ref.buf);
+               item->util = rec;
+               rec = NULL;
+       }
+
+cleanup:
+       if (fp)
+               fclose(fp);
+       free(path);
+       free(rec);
+       strbuf_release(&ref);
+       strbuf_release(&hash);
+       return result;
+}