sequencer (rebase -i): copy commit notes at end
[gitweb.git] / sequencer.c
index 80b2b2a975b8f836784639cdb9249f449e8195ac..50380a15b83d7df54cdc489bfff50e33dab7afe7 100644 (file)
@@ -95,6 +95,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
  * the abbreviated commit name of the corresponding patch.
  */
 static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
+/*
+ * For the post-rewrite hook, we make a list of rewritten commits and
+ * their new sha1s.  The rewritten-pending list keeps the sha1s of
+ * commits that have been processed, but not committed yet,
+ * e.g. because they are waiting for a 'squash' command.
+ */
+static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
+static GIT_PATH_FUNC(rebase_path_rewritten_pending,
+       "rebase-merge/rewritten-pending")
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
@@ -718,6 +727,7 @@ enum todo_command {
        TODO_PICK = 0,
        TODO_REVERT,
        TODO_EDIT,
+       TODO_REWORD,
        TODO_FIXUP,
        TODO_SQUASH,
        /* commands that do something else than handling a single commit */
@@ -733,6 +743,7 @@ static struct {
        { 'p', "pick" },
        { 0,   "revert" },
        { 'e', "edit" },
+       { 'r', "reword" },
        { 'f', "fixup" },
        { 's', "squash" },
        { 'x', "exec" },
@@ -848,6 +859,44 @@ static int update_squash_messages(enum todo_command command,
        return res;
 }
 
+static void flush_rewritten_pending(void) {
+       struct strbuf buf = STRBUF_INIT;
+       unsigned char newsha1[20];
+       FILE *out;
+
+       if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
+                       !get_sha1("HEAD", newsha1) &&
+                       (out = fopen(rebase_path_rewritten_list(), "a"))) {
+               char *bol = buf.buf, *eol;
+
+               while (*bol) {
+                       eol = strchrnul(bol, '\n');
+                       fprintf(out, "%.*s %s\n", (int)(eol - bol),
+                                       bol, sha1_to_hex(newsha1));
+                       if (!*eol)
+                               break;
+                       bol = eol + 1;
+               }
+               fclose(out);
+               unlink(rebase_path_rewritten_pending());
+       }
+       strbuf_release(&buf);
+}
+
+static void record_in_rewritten(struct object_id *oid,
+               enum todo_command next_command) {
+       FILE *out = fopen(rebase_path_rewritten_pending(), "a");
+
+       if (!out)
+               return;
+
+       fprintf(out, "%s\n", oid_to_hex(oid));
+       fclose(out);
+
+       if (!is_fixup(next_command))
+               flush_rewritten_pending();
+}
+
 static int do_pick_commit(enum todo_command command, struct commit *commit,
                struct replay_opts *opts, int final_fixup)
 {
@@ -858,7 +907,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        const char *base_label, *next_label;
        struct commit_message msg = { NULL, NULL, NULL, NULL };
        struct strbuf msgbuf = STRBUF_INIT;
-       int res, unborn = 0, amend = 0, allow;
+       int res, unborn = 0, amend = 0, allow = 0;
 
        if (opts->no_commit) {
                /*
@@ -903,11 +952,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
        else
                parent = commit->parents->item;
 
+       if (get_message(commit, &msg) != 0)
+               return error(_("cannot get commit message for %s"),
+                       oid_to_hex(&commit->object.oid));
+
        if (opts->allow_ff && !is_fixup(command) &&
            ((parent && !hashcmp(parent->object.oid.hash, head)) ||
-            (!parent && unborn)))
-               return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
-
+            (!parent && unborn))) {
+               if (is_rebase_i(opts))
+                       write_author_script(msg.message);
+               res = fast_forward_to(commit->object.oid.hash, head, unborn,
+                       opts);
+               if (res || command != TODO_REWORD)
+                       goto leave;
+               edit = amend = 1;
+               msg_file = NULL;
+               goto fast_forward_edit;
+       }
        if (parent && parse_commit(parent) < 0)
                /* TRANSLATORS: The first %s will be a "todo" command like
                   "revert" or "pick", the second %s a SHA1. */
@@ -915,10 +976,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                        command_to_string(command),
                        oid_to_hex(&parent->object.oid));
 
-       if (get_message(commit, &msg) != 0)
-               return error(_("cannot get commit message for %s"),
-                       oid_to_hex(&commit->object.oid));
-
        /*
         * "commit" is an existing commit.  We would want to apply
         * the difference it introduces since its first parent "prev"
@@ -962,7 +1019,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                }
        }
 
-       if (is_fixup(command)) {
+       if (command == TODO_REWORD)
+               edit = 1;
+       else if (is_fixup(command)) {
                if (update_squash_messages(command, commit, opts))
                        return -1;
                amend = 1;
@@ -1040,6 +1099,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
                goto leave;
        }
        if (!opts->no_commit)
+fast_forward_edit:
                res = run_git_commit(msg_file, opts, allow, edit, amend,
                                     cleanup_commit_message);
 
@@ -1730,6 +1790,37 @@ static int is_final_fixup(struct todo_list *todo_list)
        return 1;
 }
 
+static enum todo_command peek_command(struct todo_list *todo_list, int offset)
+{
+       int i;
+
+       for (i = todo_list->current + offset; i < todo_list->nr; i++)
+               if (!is_noop(todo_list->items[i].command))
+                       return todo_list->items[i].command;
+
+       return -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;
+
+       va_start(ap, fmt);
+       strbuf_reset(&buf);
+       strbuf_addstr(&buf, action_name(opts));
+       if (sub_action)
+               strbuf_addf(&buf, " (%s)", sub_action);
+       if (fmt) {
+               strbuf_addstr(&buf, ": ");
+               strbuf_vaddf(&buf, fmt, ap);
+       }
+       va_end(ap);
+
+       return buf.buf;
+}
+
 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 {
        int res = 0;
@@ -1752,6 +1843,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                        unlink(rebase_path_amend());
                }
                if (item->command <= TODO_SQUASH) {
+                       if (is_rebase_i(opts))
+                               setenv("GIT_REFLOG_ACTION", reflog_message(opts,
+                                       command_to_string(item->command), NULL),
+                                       1);
                        res = do_pick_commit(item->command, item->commit,
                                        opts, is_final_fixup(todo_list));
                        if (item->command == TODO_EDIT) {
@@ -1764,12 +1859,18 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                        item->arg, item->arg_len, opts, res,
                                        !res);
                        }
+                       if (is_rebase_i(opts) && !res)
+                               record_in_rewritten(&item->commit->object.oid,
+                                       peek_command(todo_list, 1));
                        if (res && is_fixup(item->command)) {
                                if (res == 1)
                                        intend_to_amend();
                                return error_failed_squash(item->commit, opts,
                                        item->arg_len, item->arg);
-                       }
+                       } else if (res && is_rebase_i(opts))
+                               return res | error_with_patch(item->commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       item->command == TODO_REWORD);
                } else if (item->command == TODO_EXEC) {
                        char *end_of_arg = (char *)(item->arg + item->arg_len);
                        int saved = *end_of_arg;
@@ -1787,6 +1888,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 
        if (is_rebase_i(opts)) {
                struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT;
+               struct stat st;
 
                /* Stopped in the middle, as planned? */
                if (todo_list->current < todo_list->nr)
@@ -1794,6 +1896,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 
                if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
                                starts_with(head_ref.buf, "refs/")) {
+                       const char *msg;
                        unsigned char head[20], orig[20];
                        int res;
 
@@ -1809,23 +1912,21 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                res = error(_("could not read orig-head"));
                                goto cleanup_head_ref;
                        }
-                       strbuf_addf(&buf, "rebase -i (finish): %s onto ",
-                               head_ref.buf);
                        if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
                                res = error(_("could not read 'onto'"));
                                goto cleanup_head_ref;
                        }
-                       if (update_ref(buf.buf, head_ref.buf, head, orig,
+                       msg = reflog_message(opts, "finish", "%s onto %s",
+                               head_ref.buf, buf.buf);
+                       if (update_ref(msg, head_ref.buf, head, orig,
                                        REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
                                res = error(_("could not update %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
                        }
-                       strbuf_reset(&buf);
-                       strbuf_addf(&buf,
-                               "rebase -i (finish): returning to %s",
+                       msg = reflog_message(opts, "finish", "returning to %s",
                                head_ref.buf);
-                       if (create_symref("HEAD", head_ref.buf, buf.buf)) {
+                       if (create_symref("HEAD", head_ref.buf, msg)) {
                                res = error(_("could not update HEAD to %s"),
                                        head_ref.buf);
                                goto cleanup_head_ref;
@@ -1852,6 +1953,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                                log_tree_diff_flush(&log_tree_opt);
                        }
                }
+               flush_rewritten_pending();
+               if (!stat(rebase_path_rewritten_list(), &st) &&
+                               st.st_size > 0) {
+                       struct child_process child = CHILD_PROCESS_INIT;
+
+                       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");
+                       /* we don't care if this copying failed */
+                       run_command(&child);
+               }
+
                strbuf_release(&buf);
                strbuf_release(&head_ref);
        }