rebase -i: rewrite complete_action() in C

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>

Alban Gruin committed Aug 28, 2018 at 14:10 UTC b97e187364990fb8410355ff8b4365d0e37bbbbe
6 files changed +124 -53
builtin/rebase--helper.c
+10 -2
@@ -13,13 +13,13 @@ static const char * const builtin_rebase_helper_usage[] = {
13 int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
14 {
15 struct replay_opts opts = REPLAY_OPTS_INIT;
16 - unsigned flags = 0, keep_empty = 0, rebase_merges = 0;
16 + unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0;
17 int abbreviate_commands = 0, rebase_cousins = -1;
18 enum {
19 CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
20 CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
21 ADD_EXEC, APPEND_TODO_HELP, EDIT_TODO, PREPARE_BRANCH,
22 - CHECKOUT_ONTO
22 + CHECKOUT_ONTO, COMPLETE_ACTION
23 } command = 0;
24 struct option options[] = {
25 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)
29 OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
30 OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
31 N_("keep original branch points of cousins")),
32 + OPT_BOOL(0, "autosquash", &autosquash,
33 + N_("move commits that begin with squash!/fixup!")),
34 OPT__VERBOSE(&opts.verbose, N_("be verbose")),
35 OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
36 CONTINUE),
@@ -57,6 +59,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
59 N_("prepare the branch to be rebased"), PREPARE_BRANCH),
60 OPT_CMDMODE(0, "checkout-onto", &command,
61 N_("checkout a commit"), CHECKOUT_ONTO),
62 + OPT_CMDMODE(0, "complete-action", &command,
63 + N_("complete the action"), COMPLETE_ACTION),
64 OPT_END()
65 };
66
@@ -110,5 +114,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
114 return !!prepare_branch_to_be_rebased(&opts, argv[1]);
115 if (command == CHECKOUT_ONTO && argc == 4)
116 return !!checkout_onto(&opts, argv[1], argv[2], argv[3]);
117 + if (command == COMPLETE_ACTION && argc == 6)
118 + return !!complete_action(&opts, flags, argv[1], argv[2], argv[3],
119 + argv[4], argv[5], autosquash);
120 +
121 usage_with_options(builtin_rebase_helper_usage, options);
122 }
git-rebase--interactive.sh
+4 -49
@@ -127,54 +127,6 @@ init_revisions_and_shortrevisions () {
127 fi
128 }
129
130 -complete_action() {
131 - test -s "$todo" || echo noop >> "$todo"
132 - test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
133 - test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
134 -
135 - todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
136 - todocount=${todocount##* }
137 -
138 -cat >>"$todo" <<EOF
139 -
140 -$comment_char $(eval_ngettext \
141 - "Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
142 - "Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
143 - "$todocount")
144 -EOF
145 - git rebase--helper --append-todo-help ${keep_empty:+--keep-empty}
146 -
147 - has_action "$todo" ||
148 - return 2
149 -
150 - cp "$todo" "$todo".backup
151 - collapse_todo_ids
152 - git_sequence_editor "$todo" ||
153 - die_abort "$(gettext "Could not execute editor")"
154 -
155 - has_action "$todo" ||
156 - return 2
157 -
158 - git rebase--helper --check-todo-list || {
159 - ret=$?
160 - git rebase--helper --checkout-onto "$onto_name" "$onto" \
161 - "$orig_head" ${verbose:+--verbose}
162 - exit $ret
163 - }
164 -
165 - expand_todo_ids
166 -
167 - test -n "$force_rebase" ||
168 - onto="$(git rebase--helper --skip-unnecessary-picks)" ||
169 - die "Could not skip unnecessary pick commands"
170 -
171 - git rebase--helper --checkout-onto "$onto_name" "$onto" "$orig_head" \
172 - ${verbose:+--verbose}
173 - require_clean_work_tree "rebase"
174 - exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
175 - --continue
176 -}
177 -
130 git_rebase__interactive () {
131 initiate_action "$action"
132 ret=$?
@@ -193,5 +145,8 @@ git_rebase__interactive () {
145 $revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
146 die "$(gettext "Could not generate todo list")"
147
196 - complete_action
148 + exec git rebase--helper --complete-action "$shortrevisions" "$onto_name" \
149 + "$shortonto" "$orig_head" "$cmd" $allow_empty_message \
150 + ${autosquash:+--autosquash} ${keep_empty:+--keep-empty} \
151 + ${verbose:+--verbose} ${force_rebase:+--no-ff}
152 }
git-rebase.sh
+1 -1
@@ -219,7 +219,7 @@ run_specific_rebase () {
219 if test $ret -eq 0
220 then
221 finish_rebase
222 - elif test $ret -eq 2 # special exit status for rebase -i
222 + elif test $ret -eq 2 # special exit status for rebase -p
223 then
224 apply_autostash &&
225 rm -rf "$state_dir" &&
sequencer.c
+104
@@ -30,6 +30,7 @@
30 #include "oidset.h"
31 #include "commit-slab.h"
32 #include "alias.h"
33 +#include "rebase-interactive.h"
34
35 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
36
@@ -53,6 +54,9 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
54 * file and written to the tail of 'done'.
55 */
56 GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
57 +static GIT_PATH_FUNC(rebase_path_todo_backup,
58 + "rebase-merge/git-rebase-todo.backup")
59 +
60 /*
61 * The rebase command lines that have already been processed. A line
62 * 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)
4499 return 0;
4500 }
4501
4502 +int complete_action(struct replay_opts *opts, unsigned flags,
4503 + const char *shortrevisions, const char *onto_name,
4504 + const char *onto, const char *orig_head, const char *cmd,
4505 + unsigned autosquash)
4506 +{
4507 + const char *shortonto, *todo_file = rebase_path_todo();
4508 + struct todo_list todo_list = TODO_LIST_INIT;
4509 + struct strbuf *buf = &(todo_list.buf);
4510 + struct object_id oid;
4511 + struct stat st;
4512 +
4513 + get_oid(onto, &oid);
4514 + shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
4515 +
4516 + if (!lstat(todo_file, &st) && st.st_size == 0 &&
4517 + write_message("noop\n", 5, todo_file, 0))
4518 + return -1;
4519 +
4520 + if (autosquash && rearrange_squash())
4521 + return -1;
4522 +
4523 + if (cmd && *cmd)
4524 + sequencer_add_exec_commands(cmd);
4525 +
4526 + if (strbuf_read_file(buf, todo_file, 0) < 0)
4527 + return error_errno(_("could not read '%s'."), todo_file);
4528 +
4529 + if (parse_insn_buffer(buf->buf, &todo_list)) {
4530 + todo_list_release(&todo_list);
4531 + return error(_("unusable todo list: '%s'"), todo_file);
4532 + }
4533 +
4534 + if (count_commands(&todo_list) == 0) {
4535 + apply_autostash(opts);
4536 + sequencer_remove_state(opts);
4537 + todo_list_release(&todo_list);
4538 +
4539 + return error(_("nothing to do"));
4540 + }
4541 +
4542 + strbuf_addch(buf, '\n');
4543 + strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
4544 + "Rebase %s onto %s (%d commands)",
4545 + count_commands(&todo_list)),
4546 + shortrevisions, shortonto, count_commands(&todo_list));
4547 + append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
4548 +
4549 + if (write_message(buf->buf, buf->len, todo_file, 0)) {
4550 + todo_list_release(&todo_list);
4551 + return -1;
4552 + }
4553 +
4554 + if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
4555 + return error(_("could not copy '%s' to '%s'."), todo_file,
4556 + rebase_path_todo_backup());
4557 +
4558 + if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
4559 + return error(_("could not transform the todo list"));
4560 +
4561 + strbuf_reset(buf);
4562 +
4563 + if (launch_sequence_editor(todo_file, buf, NULL)) {
4564 + apply_autostash(opts);
4565 + sequencer_remove_state(opts);
4566 + todo_list_release(&todo_list);
4567 +
4568 + return -1;
4569 + }
4570 +
4571 + strbuf_stripspace(buf, 1);
4572 + if (buf->len == 0) {
4573 + apply_autostash(opts);
4574 + sequencer_remove_state(opts);
4575 + todo_list_release(&todo_list);
4576 +
4577 + return error(_("nothing to do"));
4578 + }
4579 +
4580 + todo_list_release(&todo_list);
4581 +
4582 + if (check_todo_list()) {
4583 + checkout_onto(opts, onto_name, onto, orig_head);
4584 + return -1;
4585 + }
4586 +
4587 + if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
4588 + return error(_("could not transform the todo list"));
4589 +
4590 + if (opts->allow_ff && skip_unnecessary_picks(&oid))
4591 + return error(_("could not skip unnecessary pick commands"));
4592 +
4593 + if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
4594 + return -1;
4595 +;
4596 + if (require_clean_work_tree("rebase", "", 1, 1))
4597 + return -1;
4598 +
4599 + return sequencer_continue(opts);
4600 +}
4601 +
4602 struct subject2item_entry {
4603 struct hashmap_entry entry;
4604 int i;
sequencer.h
+4
@@ -92,6 +92,10 @@ int transform_todos(unsigned flags);
92 enum missing_commit_check_level get_missing_commit_check_level(void);
93 int check_todo_list(void);
94 int skip_unnecessary_picks(struct object_id *output_oid);
95 +int complete_action(struct replay_opts *opts, unsigned flags,
96 + const char *shortrevisions, const char *onto_name,
97 + const char *onto, const char *orig_head, const char *cmd,
98 + unsigned autosquash);
99 int rearrange_squash(void);
100
101 extern const char sign_off_header[];
t/t3404-rebase-interactive.sh
+1 -1
@@ -76,7 +76,7 @@ test_expect_success 'rebase --keep-empty' '
76 '
77
78 cat > expect <<EOF
79 -Nothing to do
79 +error: nothing to do
80 EOF
81
82 test_expect_success 'rebase -i with empty HEAD' '