]> git.ipfire.org Git - thirdparty/git.git/commitdiff
sequencer (rebase -i): implement the 'edit' command
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Mon, 2 Jan 2017 15:26:43 +0000 (16:26 +0100)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Jan 2017 22:57:29 +0000 (14:57 -0800)
This patch is a straight-forward reimplementation of the `edit`
operation of the interactive rebase command.

Well, not *quite* straight-forward: when stopping, the `edit`
command wants to write the `patch` file (which is not only the
patch, but includes the commit message and author information). To
that end, this patch requires the earlier work that taught the
log-tree machinery to respect the `file` setting of
rev_info->diffopt to write to a file stream different than stdout.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
sequencer.c

index 84f18e64e9ee68e204db90a22eb4821ba2395489..b138a3906cf386d3335aba01beadcdb447cd8721 100644 (file)
@@ -17,6 +17,7 @@
 #include "argv-array.h"
 #include "quote.h"
 #include "trailer.h"
+#include "log-tree.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -44,6 +45,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
  * being rebased.
  */
 static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
+/*
+ * When an "edit" rebase command is being processed, the SHA1 of the
+ * commit to be edited is recorded in this file.  When "git rebase
+ * --continue" is executed, if there are any staged changes then they
+ * will be amended to the HEAD commit, but only provided the HEAD
+ * commit is still the commit to be edited.  When any other rebase
+ * command is processed, this file is deleted.
+ */
+static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
+/*
+ * When we stop at a given patch via the "edit" command, this file contains
+ * the abbreviated commit name of the corresponding patch.
+ */
+static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
 /*
  * The following files are written by git-rebase just after parsing the
  * command-line (and are only consumed, not modified, by the sequencer).
@@ -616,6 +631,7 @@ enum todo_command {
        /* commands that handle commits */
        TODO_PICK = 0,
        TODO_REVERT,
+       TODO_EDIT,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP
 };
@@ -623,6 +639,7 @@ enum todo_command {
 static const char *todo_command_strings[] = {
        "pick",
        "revert",
+       "edit",
        "noop"
 };
 
@@ -1302,9 +1319,87 @@ static int save_opts(struct replay_opts *opts)
        return res;
 }
 
+static int make_patch(struct commit *commit, struct replay_opts *opts)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct rev_info log_tree_opt;
+       const char *subject, *p;
+       int res = 0;
+
+       p = short_commit_name(commit);
+       if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
+               return -1;
+
+       strbuf_addf(&buf, "%s/patch", get_dir(opts));
+       memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+       init_revisions(&log_tree_opt, NULL);
+       log_tree_opt.abbrev = 0;
+       log_tree_opt.diff = 1;
+       log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
+       log_tree_opt.disable_stdin = 1;
+       log_tree_opt.no_commit_id = 1;
+       log_tree_opt.diffopt.file = fopen(buf.buf, "w");
+       log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
+       if (!log_tree_opt.diffopt.file)
+               res |= error_errno(_("could not open '%s'"), buf.buf);
+       else {
+               res |= log_tree_commit(&log_tree_opt, commit);
+               fclose(log_tree_opt.diffopt.file);
+       }
+       strbuf_reset(&buf);
+
+       strbuf_addf(&buf, "%s/message", get_dir(opts));
+       if (!file_exists(buf.buf)) {
+               const char *commit_buffer = get_commit_buffer(commit, NULL);
+               find_commit_subject(commit_buffer, &subject);
+               res |= write_message(subject, strlen(subject), buf.buf, 1);
+               unuse_commit_buffer(commit, commit_buffer);
+       }
+       strbuf_release(&buf);
+
+       return res;
+}
+
+static int intend_to_amend(void)
+{
+       unsigned char head[20];
+       char *p;
+
+       if (get_sha1("HEAD", head))
+               return error(_("cannot read HEAD"));
+
+       p = sha1_to_hex(head);
+       return write_message(p, strlen(p), rebase_path_amend(), 1);
+}
+
+static int error_with_patch(struct commit *commit,
+       const char *subject, int subject_len,
+       struct replay_opts *opts, int exit_code, int to_amend)
+{
+       if (make_patch(commit, opts))
+               return -1;
+
+       if (to_amend) {
+               if (intend_to_amend())
+                       return -1;
+
+               fprintf(stderr, "You can amend the commit now, with\n"
+                       "\n"
+                       "  git commit --amend %s\n"
+                       "\n"
+                       "Once you are satisfied with your changes, run\n"
+                       "\n"
+                       "  git rebase --continue\n", gpg_sign_opt_quoted(opts));
+       } else if (exit_code)
+               fprintf(stderr, "Could not apply %s... %.*s\n",
+                       short_commit_name(commit), subject_len, subject);
+
+       return exit_code;
+}
+
 static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
 {
-       int res;
+       int res = 0;
 
        setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
        if (opts->allow_ff)
@@ -1317,10 +1412,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                struct todo_item *item = todo_list->items + todo_list->current;
                if (save_todo(todo_list, opts))
                        return -1;
-               if (item->command <= TODO_REVERT)
+               if (item->command <= TODO_EDIT) {
                        res = do_pick_commit(item->command, item->commit,
                                        opts);
-               else if (!is_noop(item->command))
+                       if (item->command == TODO_EDIT) {
+                               struct commit *commit = item->commit;
+                               if (!res)
+                                       warning(_("stopped at %s... %.*s"),
+                                               short_commit_name(commit),
+                                               item->arg_len, item->arg);
+                               return error_with_patch(commit,
+                                       item->arg, item->arg_len, opts, res,
+                                       !res);
+                       }
+               } else if (!is_noop(item->command))
                        return error(_("unknown command %d"), item->command);
 
                todo_list->current++;
@@ -1328,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                        return res;
        }
 
+       if (is_rebase_i(opts)) {
+               /* Stopped in the middle, as planned? */
+               if (todo_list->current < todo_list->nr)
+                       return 0;
+       }
+
        /*
         * Sequence of picks finished successfully; cleanup by
         * removing the .git/sequencer directory