sequencer (rebase -i): show the progress
[gitweb.git] / sequencer.c
index 6e92f186ae3a44516f255ba45ab2bdb8d82c7f2b..aa31191d9aa0544f1da56029136bf6886bc4f604 100644 (file)
@@ -433,6 +433,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        o.ancestor = base ? base_label : "(empty tree)";
        o.branch1 = "HEAD";
        o.branch2 = next ? next_label : "(empty tree)";
+       if (is_rebase_i(opts))
+               o.buffer_output = 2;
 
        head_tree = parse_tree_indirect(head);
        next_tree = next ? next->tree : empty_tree();
@@ -444,6 +446,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
        clean = merge_trees(&o,
                            head_tree,
                            next_tree, base_tree, &result);
+       if (is_rebase_i(opts) && clean <= 0)
+               fputs(o.obuf.buf, stdout);
        strbuf_release(&o.obuf);
        if (clean < 0)
                return clean;
@@ -544,18 +548,17 @@ static int write_author_script(const char *message)
 }
 
 /*
- * Read the author-script file into an environment block, ready for use in
- * run_command(), that can be free()d afterwards.
+ * Read a list of environment variable assignments (such as the author-script
+ * file) into an environment block. Returns -1 on error, 0 otherwise.
  */
-static char **read_author_script(void)
+static int read_env_script(struct argv_array *env)
 {
        struct strbuf script = STRBUF_INIT;
        int i, count = 0;
-       char *p, *p2, **env;
-       size_t env_size;
+       char *p, *p2;
 
        if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
-               return NULL;
+               return -1;
 
        for (p = script.buf; *p; p++)
                if (skip_prefix(p, "'\\\\''", (const char **)&p2))
@@ -567,19 +570,12 @@ static char **read_author_script(void)
                        count++;
                }
 
-       env_size = (count + 1) * sizeof(*env);
-       strbuf_grow(&script, env_size);
-       memmove(script.buf + env_size, script.buf, script.len);
-       p = script.buf + env_size;
-       env = (char **)strbuf_detach(&script, NULL);
-
-       for (i = 0; i < count; i++) {
-               env[i] = p;
+       for (i = 0, p = script.buf; i < count; i++) {
+               argv_array_push(env, p);
                p += strlen(p) + 1;
        }
-       env[count] = NULL;
 
-       return env;
+       return 0;
 }
 
 static const char staged_changes_advice[] =
@@ -612,14 +608,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                          int allow_empty, int edit, int amend,
                          int cleanup_commit_message)
 {
-       char **env = NULL;
-       struct argv_array array;
-       int rc;
+       struct child_process cmd = CHILD_PROCESS_INIT;
        const char *value;
 
+       cmd.git_cmd = 1;
+
        if (is_rebase_i(opts)) {
-               env = read_author_script();
-               if (!env) {
+               if (!edit) {
+                       cmd.stdout_to_stderr = 1;
+                       cmd.err = -1;
+               }
+
+               if (read_env_script(&cmd.env_array)) {
                        const char *gpg_opt = gpg_sign_opt_quoted(opts);
 
                        return error(_(staged_changes_advice),
@@ -627,39 +627,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
                }
        }
 
-       argv_array_init(&array);
-       argv_array_push(&array, "commit");
-       argv_array_push(&array, "-n");
+       argv_array_push(&cmd.args, "commit");
+       argv_array_push(&cmd.args, "-n");
 
        if (amend)
-               argv_array_push(&array, "--amend");
+               argv_array_push(&cmd.args, "--amend");
        if (opts->gpg_sign)
-               argv_array_pushf(&array, "-S%s", opts->gpg_sign);
+               argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
        if (opts->signoff)
-               argv_array_push(&array, "-s");
+               argv_array_push(&cmd.args, "-s");
        if (defmsg)
-               argv_array_pushl(&array, "-F", defmsg, NULL);
+               argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
        if (cleanup_commit_message)
-               argv_array_push(&array, "--cleanup=strip");
+               argv_array_push(&cmd.args, "--cleanup=strip");
        if (edit)
-               argv_array_push(&array, "-e");
+               argv_array_push(&cmd.args, "-e");
        else if (!cleanup_commit_message &&
                 !opts->signoff && !opts->record_origin &&
                 git_config_get_value("commit.cleanup", &value))
-               argv_array_push(&array, "--cleanup=verbatim");
+               argv_array_push(&cmd.args, "--cleanup=verbatim");
 
        if (allow_empty)
-               argv_array_push(&array, "--allow-empty");
+               argv_array_push(&cmd.args, "--allow-empty");
 
        if (opts->allow_empty_message)
-               argv_array_push(&array, "--allow-empty-message");
+               argv_array_push(&cmd.args, "--allow-empty-message");
 
-       rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL,
-                       (const char *const *)env);
-       argv_array_clear(&array);
-       free(env);
+       if (cmd.err == -1) {
+               /* hide stderr on success */
+               struct strbuf buf = STRBUF_INIT;
+               int rc = pipe_command(&cmd,
+                                     NULL, 0,
+                                     /* stdout is already redirected */
+                                     NULL, 0,
+                                     &buf, 0);
+               if (rc)
+                       fputs(buf.buf, stderr);
+               strbuf_release(&buf);
+               return rc;
+       }
 
-       return rc;
+       return run_command(&cmd);
 }
 
 static int is_original_commit_empty(struct commit *commit)
@@ -737,7 +745,9 @@ enum todo_command {
        TODO_EXEC,
        /* commands that do nothing but are counted for reporting progress */
        TODO_NOOP,
-       TODO_DROP
+       TODO_DROP,
+       /* comments (not counted for reporting progress) */
+       TODO_COMMENT
 };
 
 static struct {
@@ -752,12 +762,13 @@ static struct {
        { 's', "squash" },
        { 'x', "exec" },
        { 0,   "noop" },
-       { 'd', "drop" }
+       { 'd', "drop" },
+       { 0,   NULL }
 };
 
 static const char *command_to_string(const enum todo_command command)
 {
-       if ((size_t)command < ARRAY_SIZE(todo_command_info))
+       if (command < TODO_COMMENT)
                return todo_command_info[command].str;
        die("Unknown command: %d", command);
 }
@@ -1170,6 +1181,7 @@ struct todo_list {
        struct strbuf buf;
        struct todo_item *items;
        int nr, alloc, current;
+       int done_nr, total_nr;
 };
 
 #define TODO_LIST_INIT { STRBUF_INIT }
@@ -1198,14 +1210,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
        bol += strspn(bol, " \t");
 
        if (bol == eol || *bol == '\r' || *bol == comment_line_char) {
-               item->command = TODO_NOOP;
+               item->command = TODO_COMMENT;
                item->commit = NULL;
                item->arg = bol;
                item->arg_len = eol - bol;
                return 0;
        }
 
-       for (i = 0; i < ARRAY_SIZE(todo_command_info); i++)
+       for (i = 0; i < TODO_COMMENT; i++)
                if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
                        item->command = i;
                        break;
@@ -1214,7 +1226,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
                        item->command = i;
                        break;
                }
-       if (i >= ARRAY_SIZE(todo_command_info))
+       if (i >= TODO_COMMENT)
                return -1;
 
        if (item->command == TODO_NOOP) {
@@ -1286,6 +1298,17 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
        return res;
 }
 
+static int count_commands(struct todo_list *todo_list)
+{
+       int count = 0, i;
+
+       for (i = 0; i < todo_list->nr; i++)
+               if (todo_list->items[i].command != TODO_COMMENT)
+                       count++;
+
+       return count;
+}
+
 static int read_populate_todo(struct todo_list *todo_list,
                        struct replay_opts *opts)
 {
@@ -1303,8 +1326,12 @@ static int read_populate_todo(struct todo_list *todo_list,
        close(fd);
 
        res = parse_insn_buffer(todo_list->buf.buf, todo_list);
-       if (res)
+       if (res) {
+               if (is_rebase_i(opts))
+                       return error(_("please fix this using "
+                                      "'git rebase --edit-todo'."));
                return error(_("unusable instruction sheet: '%s'"), todo_file);
+       }
 
        if (!todo_list->nr &&
            (!is_rebase_i(opts) || !file_exists(rebase_path_done())))
@@ -1324,6 +1351,21 @@ static int read_populate_todo(struct todo_list *todo_list,
                                return error(_("cannot revert during a cherry-pick."));
        }
 
+       if (is_rebase_i(opts)) {
+               struct todo_list done = TODO_LIST_INIT;
+
+               if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
+                               !parse_insn_buffer(done.buf.buf, &done))
+                       todo_list->done_nr = count_commands(&done);
+               else
+                       todo_list->done_nr = 0;
+
+               todo_list->total_nr = todo_list->done_nr
+                       + count_commands(todo_list);
+
+               todo_list_release(&done);
+       }
+
        return 0;
 }
 
@@ -1905,6 +1947,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
                if (save_todo(todo_list, opts))
                        return -1;
                if (is_rebase_i(opts)) {
+                       if (item->command != TODO_COMMENT)
+                               fprintf(stderr, "Rebasing (%d/%d)%s",
+                                       ++(todo_list->done_nr),
+                                       todo_list->total_nr,
+                                       opts->verbose ? "\n" : "\r");
                        unlink(rebase_path_message());
                        unlink(rebase_path_author_script());
                        unlink(rebase_path_stopped_sha());