]> git.ipfire.org Git - thirdparty/git.git/commitdiff
rebase -i: rewrite complete_action() in C
authorAlban Gruin <alban.gruin@gmail.com>
Tue, 28 Aug 2018 12:10:36 +0000 (14:10 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 29 Aug 2018 20:38:18 +0000 (13:38 -0700)
This rewrites complete_action() from shell to C.

A new mode is added to rebase--helper (`--complete-action`), as well as
a new flag (`--autosquash`).

Finally, complete_action() is stripped from git-rebase--interactive.sh.

The original complete_action() would return the code 2 when the todo
list contained no actions.  This was a special case for rebase -i and
-p; git-rebase.sh would then apply the autostash, delete the state
directory, and die with the message "Nothing to do".  This cleanup is
rewritten in C instead of returning 2.  As rebase -i no longer returns
2, the comment describing this behaviour in git-rebase.sh is updated to
reflect this change.

The message "Nothing to do" is now printed with error(), and so becomes
"error: nothing to do".  Some tests in t3404 check this value, so they
are updated to fit this change.

The first check might seem useless as we write "noop" to the todo list
if it is empty.  Actually, the todo list might contain commented
commands (ie. empty commits).  In this case, complete_action() won’t
write "noop", and will abort without starting the editor.

Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/rebase--helper.c
git-rebase--interactive.sh
git-rebase.sh
sequencer.c
sequencer.h
t/t3404-rebase-interactive.sh

index bed3dd2b9519099fb339b02cc0e2f20c75e8d080..01b33339589e34e3d1c3696076b4baa51d590bb8 100644 (file)
@@ -13,13 +13,13 @@ static const char * const builtin_rebase_helper_usage[] = {
 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
 {
        struct replay_opts opts = REPLAY_OPTS_INIT;
-       unsigned flags = 0, keep_empty = 0, rebase_merges = 0;
+       unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0;
        int abbreviate_commands = 0, rebase_cousins = -1;
        enum {
                CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
                CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
                ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
-               CHECKOUT_ONTO
+               CHECKOUT_ONTO, COMPLETE_ACTION
        } command = 0;
        struct option options[] = {
                OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -29,6 +29,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
                OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
                         N_("keep original branch points of cousins")),
+               OPT_BOOL(0, "autosquash", &autosquash,
+                        N_("move commits that begin with squash!/fixup!")),
                OPT__VERBOSE(&opts.verbose, N_("be verbose")),
                OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
                                CONTINUE),
@@ -57,6 +59,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                            N_("prepare the branch to be rebased"), PREPARE_BRANCH),
                OPT_CMDMODE(0, "checkout-onto", &command,
                            N_("checkout a commit"), CHECKOUT_ONTO),
+               OPT_CMDMODE(0, "complete-action", &command,
+                           N_("complete the action"), COMPLETE_ACTION),
                OPT_END()
        };
 
@@ -110,5 +114,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
                return !!prepare_branch_to_be_rebased(&opts, argv[1]);
        if (command == CHECKOUT_ONTO && argc == 4)
                return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
+       if (command == COMPLETE_ACTION && argc == 6)
+               return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
+                                        argv[4], argv[5], autosquash);
+
        usage_with_options(builtin_rebase_helper_usage, options);
 }
index b68f108f28dafb779ceaf2b0f255fe0db291b038..59dc4888a6da11e9cd0164449e84412c9a82cccb 100644 (file)
@@ -127,54 +127,6 @@ init_revisions_and_shortrevisions () {
        fi
 }
 
-complete_action() {
-       test -s "$todo" || echo noop >> "$todo"
-       test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
-       test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
-
-       todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
-       todocount=${todocount##* }
-
-cat >>"$todo" <<EOF
-
-$comment_char $(eval_ngettext \
-       "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
-       "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
-       "$todocount")
-EOF
-       git rebase--helper --append-todo-help ${keep_empty:+--keep-empty}
-
-       has_action "$todo" ||
-               return 2
-
-       cp "$todo" "$todo".backup
-       collapse_todo_ids
-       git_sequence_editor "$todo" ||
-               die_abort "$(gettext "Could not execute editor")"
-
-       has_action "$todo" ||
-               return 2
-
-       git rebase--helper --check-todo-list || {
-               ret=$?
-               git rebase--helper --checkout-onto "$onto_name" "$onto" \
-                   "$orig_head" ${verbose:+--verbose}
-               exit $ret
-       }
-
-       expand_todo_ids
-
-       test -n "$force_rebase" ||
-       onto="$(git rebase--helper --skip-unnecessary-picks)" ||
-       die "Could not skip unnecessary pick commands"
-
-       git rebase--helper --checkout-onto "$onto_name" "$onto" "$orig_head" \
-           ${verbose:+--verbose}
-       require_clean_work_tree "rebase"
-       exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
-            --continue
-}
-
 git_rebase__interactive () {
        initiate_action "$action"
        ret=$?
@@ -193,5 +145,8 @@ git_rebase__interactive () {
                $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
        die "$(gettext "Could not generate todo list")"
 
-       complete_action
+       exec git rebase--helper --complete-action "$shortrevisions" "$onto_name" \
+               "$shortonto" "$orig_head" "$cmd" $allow_empty_message \
+               ${autosquash:+--autosquash} ${keep_empty:+--keep-empty} \
+               ${verbose:+--verbose} ${force_rebase:+--no-ff}
 }
index f3b10c7f62d1aaed445bf2b08ff926f253a49925..86da3816beec2d317b8cec04766bb1961aceb1f4 100755 (executable)
@@ -219,7 +219,7 @@ run_specific_rebase () {
        if test $ret -eq 0
        then
                finish_rebase
-       elif test $ret -eq 2 # special exit status for rebase -i
+       elif test $ret -eq 2 # special exit status for rebase -p
        then
                apply_autostash &&
                rm -rf "$state_dir" &&
index cd2374ee1ae985bdade8606cc6cd9a92ea23da6c..98aacc802641de20407140ba45ce3bba4542857a 100644 (file)
@@ -30,6 +30,7 @@
 #include "oidset.h"
 #include "commit-slab.h"
 #include "alias.h"
+#include "rebase-interactive.h"
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
@@ -53,6 +54,9 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
  * file and written to the tail of 'done'.
  */
 GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+static GIT_PATH_FUNC(rebase_path_todo_backup,
+                    "rebase-merge/git-rebase-todo.backup")
+
 /*
  * The rebase command lines that have already been processed. A line
  * is moved here when it is first handled, before any associated user
@@ -4495,6 +4499,106 @@ int skip_unnecessary_picks(struct object_id *output_oid)
        return 0;
 }
 
+int complete_action(struct replay_opts *opts, unsigned flags,
+                   const char *shortrevisions, const char *onto_name,
+                   const char *onto, const char *orig_head, const char *cmd,
+                   unsigned autosquash)
+{
+       const char *shortonto, *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       struct strbuf *buf = &(todo_list.buf);
+       struct object_id oid;
+       struct stat st;
+
+       get_oid(onto, &oid);
+       shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
+
+       if (!lstat(todo_file, &st) && st.st_size == 0 &&
+           write_message("noop\n", 5, todo_file, 0))
+               return -1;
+
+       if (autosquash && rearrange_squash())
+               return -1;
+
+       if (cmd && *cmd)
+               sequencer_add_exec_commands(cmd);
+
+       if (strbuf_read_file(buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+
+       if (parse_insn_buffer(buf->buf, &todo_list)) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+
+       if (count_commands(&todo_list) == 0) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+
+               return error(_("nothing to do"));
+       }
+
+       strbuf_addch(buf, '\n');
+       strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
+                                     "Rebase %s onto %s (%d commands)",
+                                     count_commands(&todo_list)),
+                             shortrevisions, shortonto, count_commands(&todo_list));
+       append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
+
+       if (write_message(buf->buf, buf->len, todo_file, 0)) {
+               todo_list_release(&todo_list);
+               return -1;
+       }
+
+       if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
+               return error(_("could not copy '%s' to '%s'."), todo_file,
+                            rebase_path_todo_backup());
+
+       if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
+               return error(_("could not transform the todo list"));
+
+       strbuf_reset(buf);
+
+       if (launch_sequence_editor(todo_file, buf, NULL)) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+
+               return -1;
+       }
+
+       strbuf_stripspace(buf, 1);
+       if (buf->len == 0) {
+               apply_autostash(opts);
+               sequencer_remove_state(opts);
+               todo_list_release(&todo_list);
+
+               return error(_("nothing to do"));
+       }
+
+       todo_list_release(&todo_list);
+
+       if (check_todo_list()) {
+               checkout_onto(opts, onto_name, onto, orig_head);
+               return -1;
+       }
+
+       if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
+               return error(_("could not transform the todo list"));
+
+       if (opts->allow_ff && skip_unnecessary_picks(&oid))
+               return error(_("could not skip unnecessary pick commands"));
+
+       if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
+               return -1;
+;
+       if (require_clean_work_tree("rebase", "", 1, 1))
+               return -1;
+
+       return sequencer_continue(opts);
+}
+
 struct subject2item_entry {
        struct hashmap_entry entry;
        int i;
index fcbcd246c23e96450523b0fa30978f550fe2bae7..d58766c6d78fc6e0a0e2073b6e11d6f2ac144c43 100644 (file)
@@ -92,6 +92,10 @@ int transform_todos(unsigned flags);
 enum missing_commit_check_level get_missing_commit_check_level(void);
 int check_todo_list(void);
 int skip_unnecessary_picks(struct object_id *output_oid);
+int complete_action(struct replay_opts *opts, unsigned flags,
+                   const char *shortrevisions, const char *onto_name,
+                   const char *onto, const char *orig_head, const char *cmd,
+                   unsigned autosquash);
 int rearrange_squash(void);
 
 extern const char sign_off_header[];
index 7755b6809276d954d4b3131ce2b9668b44c4f683..90c3ca70e6b9f6b4d8bfaa28db90c8ec19c4cf85 100755 (executable)
@@ -76,7 +76,7 @@ test_expect_success 'rebase --keep-empty' '
 '
 
 cat > expect <<EOF
-Nothing to do
+error: nothing to do
 EOF
 
 test_expect_success 'rebase -i with empty HEAD' '