From: Junio C Hamano Date: Thu, 21 Mar 2019 03:10:40 +0000 (+0900) Subject: Merge branch 'ag/sequencer-reduce-rewriting-todo' into pw/rebase-i-internal X-Git-Tag: v2.22.0-rc0~4^2~12 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/464c824a9b3daf49b1451746780c787279cbf79f?ds=inline;hp=-c Merge branch 'ag/sequencer-reduce-rewriting-todo' into pw/rebase-i-internal * ag/sequencer-reduce-rewriting-todo: rebase--interactive: move transform_todo_file() sequencer: use edit_todo_list() in complete_action() rebase-interactive: rewrite edit_todo_list() to handle the initial edit rebase-interactive: append_todo_help() changes rebase-interactive: use todo_list_write_to_file() in edit_todo_list() sequencer: refactor skip_unnecessary_picks() to work on a todo_list rebase--interactive: move rearrange_squash_in_todo_file() rebase--interactive: move sequencer_add_exec_commands() sequencer: change complete_action() to use the refactored functions sequencer: make sequencer_make_script() write its script to a strbuf sequencer: refactor rearrange_squash() to work on a todo_list sequencer: refactor sequencer_add_exec_commands() to work on a todo_list sequencer: refactor check_todo_list() to work on a todo_list sequencer: introduce todo_list_write_to_file() sequencer: refactor transform_todos() to work on a todo_list sequencer: remove the 'arg' field from todo_item sequencer: make the todo_list structure public sequencer: changes in parse_insn_buffer() --- 464c824a9b3daf49b1451746780c787279cbf79f diff --combined builtin/rebase--interactive.c index 888390f911,4d9c1e62bb..4535523bf5 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@@ -1,4 -1,3 +1,4 @@@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "config.h" @@@ -14,6 -13,103 +14,103 @@@ static GIT_PATH_FUNC(path_state_dir, "r static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto") static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive") + static int add_exec_commands(struct string_list *commands) + { + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_add_exec_commands(&todo_list, commands); + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; + } + + static int rearrange_squash_in_todo_file(void) + { + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_rearrange_squash(&todo_list); + if (!res) + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; + } + + static int transform_todo_file(unsigned flags) + { + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_write_to_file(the_repository, &todo_list, todo_file, + NULL, NULL, -1, flags); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; + } + + static int edit_todo_file(unsigned flags) + { + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT, + new_todo = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + strbuf_stripspace(&todo_list.buf, 1); + res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags); + if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file, + NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS))) + res = error_errno(_("could not write '%s'"), todo_file); + + todo_list_release(&todo_list); + todo_list_release(&new_todo); + + return res; + } + static int get_revision_ranges(const char *upstream, const char *onto, const char **head_hash, char **revisions, char **shortrevisions) @@@ -66,13 -162,13 +163,13 @@@ static int do_interactive_rebase(struc const char *onto, const char *onto_name, const char *squash_onto, const char *head_name, const char *restrict_revision, char *raw_strategies, - const char *cmd, unsigned autosquash) + struct string_list *commands, unsigned autosquash) { int ret; const char *head_hash = NULL; char *revisions = NULL, *shortrevisions = NULL; struct argv_array make_script_args = ARGV_ARRAY_INIT; - FILE *todo_list; + struct todo_list todo_list = TODO_LIST_INIT; if (prepare_branch_to_be_rebased(opts, switch_to)) return -1; @@@ -94,34 -190,29 +191,29 @@@ if (!upstream && squash_onto) write_file(path_squash_onto(), "%s\n", squash_onto); - todo_list = fopen(rebase_path_todo(), "w"); - if (!todo_list) { - free(revisions); - free(shortrevisions); - - return error_errno(_("could not open %s"), rebase_path_todo()); - } - argv_array_pushl(&make_script_args, "", revisions, NULL); if (restrict_revision) argv_array_push(&make_script_args, restrict_revision); - ret = sequencer_make_script(the_repository, todo_list, + ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv, flags); - fclose(todo_list); if (ret) error(_("could not generate todo list")); else { discard_cache(); - ret = complete_action(the_repository, opts, flags, - shortrevisions, onto_name, onto, - head_hash, cmd, autosquash); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) + BUG("unusable todo list"); + + ret = complete_action(the_repository, opts, flags, shortrevisions, onto_name, + onto, head_hash, commands, autosquash, &todo_list); } free(revisions); free(shortrevisions); + todo_list_release(&todo_list); argv_array_clear(&make_script_args); return ret; @@@ -140,6 -231,7 +232,7 @@@ int cmd_rebase__interactive(int argc, c const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL, *squash_onto = NULL, *upstream = NULL, *head_name = NULL, *switch_to = NULL, *cmd = NULL; + struct string_list commands = STRING_LIST_INIT_DUP; char *raw_strategies = NULL; enum { NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH, @@@ -194,8 -286,6 +287,8 @@@ OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")), OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), + OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END() }; @@@ -224,6 -314,14 +317,14 @@@ warning(_("--[no-]rebase-cousins has no effect without " "--rebase-merges")); + if (cmd && *cmd) { + string_list_split(&commands, cmd, '\n', -1); + + /* rebase.c adds a new line to cmd after every command, + * so here the last command is always empty */ + string_list_remove_empty_items(&commands, 0); + } + switch (command) { case NONE: if (!onto && !upstream) @@@ -231,7 -329,7 +332,7 @@@ ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto, onto_name, squash_onto, head_name, restrict_revision, - raw_strategies, cmd, autosquash); + raw_strategies, &commands, autosquash); break; case SKIP: { struct string_list merge_rr = STRING_LIST_INIT_DUP; @@@ -243,7 -341,7 +344,7 @@@ break; } case EDIT_TODO: - ret = edit_todo_list(the_repository, flags); + ret = edit_todo_file(flags); break; case SHOW_CURRENT_PATCH: { struct child_process cmd = CHILD_PROCESS_INIT; @@@ -256,20 -354,21 +357,21 @@@ } case SHORTEN_OIDS: case EXPAND_OIDS: - ret = transform_todos(the_repository, flags); + ret = transform_todo_file(flags); break; case CHECK_TODO_LIST: - ret = check_todo_list(the_repository); + ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: - ret = rearrange_squash(the_repository); + ret = rearrange_squash_in_todo_file(); break; case ADD_EXEC: - ret = sequencer_add_exec_commands(the_repository, cmd); + ret = add_exec_commands(&commands); break; default: BUG("invalid command '%d'", command); } + string_list_clear(&commands, 0); return !!ret; } diff --combined sequencer.c index 95dda23eee,2a0fcb1cce..281a8ade19 --- a/sequencer.c +++ b/sequencer.c @@@ -35,7 -35,7 +35,7 @@@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -const char sign_off_header[] = "Signed-off-by: "; +static const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG") @@@ -55,8 -55,7 +55,7 @@@ static GIT_PATH_FUNC(rebase_path, "reba * 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") + GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup") /* * The rebase command lines that have already been processed. A line @@@ -150,7 -149,6 +149,7 @@@ static GIT_PATH_FUNC(rebase_path_refs_t static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") @@@ -158,7 -156,7 +157,7 @@@ static GIT_PATH_FUNC(rebase_path_autost static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate") -static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") +static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") static int git_sequencer_config(const char *k, const char *v, void *cb) { @@@ -384,8 -382,8 +383,8 @@@ static void print_advice(struct reposit } } - int write_message(const void *buf, size_t len, const char *filename, - int append_eol) + static int write_message(const void *buf, size_t len, const char *filename, + int append_eol) { struct lock_file msg_file = LOCK_INIT; @@@ -447,9 -445,9 +446,9 @@@ static struct tree *empty_tree(struct r return lookup_tree(r, the_hash_algo->empty_tree); } -static int error_dirty_index(struct index_state *istate, struct replay_opts *opts) +static int error_dirty_index(struct repository *repo, struct replay_opts *opts) { - if (read_index_unmerged(istate)) + if (repo_read_index_unmerged(repo)) return error_resolve_conflict(_(action_name(opts))); error(_("your local changes would be overwritten by %s."), @@@ -484,7 -482,7 +483,7 @@@ static int fast_forward_to(struct repos struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - read_index(r->index); + repo_read_index(r); if (checkout_fast_forward(r, from, to, 1)) return -1; /* the callee should have complained already */ @@@ -541,12 -539,12 +540,12 @@@ static int do_recursive_merge(struct re char **xopt; struct lock_file index_lock = LOCK_INIT; - if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0) + if (repo_hold_locked_index(r, &index_lock, LOCK_REPORT_ON_ERROR) < 0) return -1; - read_index(r->index); + repo_read_index(r); - init_merge_options(&o); + init_merge_options(&o, r); o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; @@@ -837,7 -835,7 +836,7 @@@ static const char *read_author_ident(st } strbuf_reset(&out); - strbuf_addstr(&out, fmt_ident(name, email, date, 0)); + strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0)); strbuf_swap(buf, &out); strbuf_release(&out); free(name); @@@ -1103,7 -1101,6 +1102,7 @@@ static int run_rewrite_hook(const struc proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; + proc.trace2_hook_name = "post-rewrite"; code = start_command(&proc); if (code) @@@ -1117,8 -1114,7 +1116,8 @@@ return finish_command(&proc); } -void commit_post_rewrite(const struct commit *old_head, +void commit_post_rewrite(struct repository *r, + const struct commit *old_head, const struct object_id *new_head) { struct notes_rewrite_cfg *cfg; @@@ -1127,7 -1123,7 +1126,7 @@@ if (cfg) { /* we are amending, so old_head is not NULL */ copy_note_for_rewrite(cfg, &old_head->object.oid, new_head); - finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); + finish_copy_notes_for_rewrite(r, cfg, "Notes added by 'git commit --amend'"); } run_rewrite_hook(&old_head->object.oid, new_head); } @@@ -1408,7 -1404,7 +1407,7 @@@ static int try_to_commit(struct reposit } if (flags & AMEND_MSG) - commit_post_rewrite(current_head, oid); + commit_post_rewrite(r, current_head, oid); out: free_commit_extra_headers(extra); @@@ -1513,32 -1509,6 +1512,6 @@@ static int allow_empty(struct repositor return 1; } - /* - * Note that ordering matters in this enum. Not only must it match the mapping - * below, it is also divided into several sections that matter. When adding - * new commands, make sure you add it in the right section. - */ - enum todo_command { - /* commands that handle commits */ - TODO_PICK = 0, - TODO_REVERT, - TODO_EDIT, - TODO_REWORD, - TODO_FIXUP, - TODO_SQUASH, - /* commands that do something else than handling a single commit */ - TODO_EXEC, - TODO_BREAK, - TODO_LABEL, - TODO_RESET, - TODO_MERGE, - /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP, - TODO_DROP, - /* comments (not counted for reporting progress) */ - TODO_COMMENT - }; - static struct { char c; const char *str; @@@ -1692,8 -1662,7 +1665,8 @@@ static int update_squash_messages(struc return res; } -static void flush_rewritten_pending(void) { +static void flush_rewritten_pending(void) +{ struct strbuf buf = STRBUF_INIT; struct object_id newoid; FILE *out; @@@ -1718,8 -1687,7 +1691,8 @@@ } static void record_in_rewritten(struct object_id *oid, - enum todo_command next_command) { + enum todo_command next_command) +{ FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a"); if (!out) @@@ -1770,7 -1738,7 +1743,7 @@@ static int do_pick_commit(struct reposi oidcpy(&head, the_hash_algo->empty_tree); if (index_differs_from(r, unborn ? empty_tree_oid_hex() : "HEAD", NULL, 0)) - return error_dirty_index(r->index, opts); + return error_dirty_index(r, opts); } discard_index(r->index); @@@ -1793,13 -1761,9 +1766,13 @@@ return error(_("commit %s does not have parent %d"), oid_to_hex(&commit->object.oid), opts->mainline); parent = p->item; - } else if (0 < opts->mainline) - return error(_("mainline was specified but commit %s is not a merge."), - oid_to_hex(&commit->object.oid)); + } else if (1 < opts->mainline) + /* + * Non-first parent explicitly specified as mainline for + * non-merge commit + */ + return error(_("commit %s does not have parent %d"), + oid_to_hex(&commit->object.oid), opts->mainline); else parent = commit->parents->item; @@@ -2000,8 -1964,8 +1973,8 @@@ static int read_and_refresh_cache(struc struct replay_opts *opts) { struct lock_file index_lock = LOCK_INIT; - int index_fd = hold_locked_index(&index_lock, 0); - if (read_index(r->index) < 0) { + int index_fd = repo_hold_locked_index(r, &index_lock, 0); + if (repo_read_index(r) < 0) { rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), _(action_name(opts))); @@@ -2021,26 -1985,7 +1994,7 @@@ enum todo_item_flags TODO_EDIT_MERGE_MSG = 1 }; - struct todo_item { - enum todo_command command; - struct commit *commit; - unsigned int flags; - const char *arg; - int arg_len; - size_t offset_in_buf; - }; - - struct todo_list { - struct strbuf buf; - struct todo_item *items; - int nr, alloc, current; - int done_nr, total_nr; - struct stat_data stat; - }; - - #define TODO_LIST_INIT { STRBUF_INIT } - - static void todo_list_release(struct todo_list *todo_list) + void todo_list_release(struct todo_list *todo_list) { strbuf_release(&todo_list->buf); FREE_AND_NULL(todo_list->items); @@@ -2053,8 -1998,14 +2007,14 @@@ static struct todo_item *append_new_tod return todo_list->items + todo_list->nr++; } + const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item) + { + return todo_list->buf.buf + item->arg_offset; + } + static int parse_insn_line(struct repository *r, struct todo_item *item, - const char *bol, char *eol) + const char *buf, const char *bol, char *eol) { struct object_id commit_oid; char *end_of_object_name; @@@ -2068,7 -2019,7 +2028,7 @@@ if (bol == eol || *bol == '\r' || *bol == comment_line_char) { item->command = TODO_COMMENT; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@@ -2095,7 -2046,7 +2055,7 @@@ return error(_("%s does not accept arguments: '%s'"), command_to_string(item->command), bol); item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@@ -2107,7 -2058,7 +2067,7 @@@ if (item->command == TODO_EXEC || item->command == TODO_LABEL || item->command == TODO_RESET) { item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@@ -2121,7 -2072,7 +2081,7 @@@ } else { item->flags |= TODO_EDIT_MERGE_MSG; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@@ -2133,8 -2084,9 +2093,9 @@@ status = get_oid(bol, &commit_oid); *end_of_object_name = saved; - item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); - item->arg_len = (int)(eol - item->arg); + bol = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_offset = bol - buf; + item->arg_len = (int)(eol - bol); if (status < 0) return -1; @@@ -2143,13 -2095,15 +2104,15 @@@ return !item->commit; } - static int parse_insn_buffer(struct repository *r, char *buf, - struct todo_list *todo_list) + int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; int i, res = 0, fixup_okay = file_exists(rebase_path_done()); + todo_list->current = todo_list->nr = 0; + for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@@ -2160,10 -2114,13 +2123,13 @@@ item = append_new_todo(todo_list); item->offset_in_buf = p - todo_list->buf.buf; - if (parse_insn_line(r, item, p, eol)) { + if (parse_insn_line(r, item, buf, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = TODO_NOOP; + item->command = TODO_COMMENT + 1; + item->arg_offset = p - buf; + item->arg_len = (int)(eol - p); + item->commit = NULL; } if (fixup_okay) @@@ -2238,7 -2195,7 +2204,7 @@@ static int read_populate_todo(struct re return error(_("could not stat '%s'"), todo_file); fill_stat_data(&todo_list->stat, &st); - res = parse_insn_buffer(r, todo_list->buf.buf, todo_list); + res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list); if (res) { if (is_rebase_i(opts)) return error(_("please fix this using " @@@ -2269,7 -2226,7 +2235,7 @@@ FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && - !parse_insn_buffer(r, done.buf.buf, &done)) + !todo_list_parse_insn_buffer(r, done.buf.buf, &done)) todo_list->done_nr = count_commands(&done); else todo_list->done_nr = 0; @@@ -2392,17 -2349,11 +2358,17 @@@ static int read_populate_opts(struct re if (file_exists(rebase_path_verbose())) opts->verbose = 1; + if (file_exists(rebase_path_quiet())) + opts->quiet = 1; + if (file_exists(rebase_path_signoff())) { opts->allow_ff = 0; opts->signoff = 1; } + if (file_exists(rebase_path_reschedule_failed_exec())) + opts->reschedule_failed_exec = 1; + read_strategy_opts(opts, &buf); strbuf_release(&buf); @@@ -2465,6 -2416,9 +2431,6 @@@ int write_basic_state(struct replay_opt if (quiet) write_file(rebase_path_quiet(), "%s\n", quiet); - else - write_file(rebase_path_quiet(), "\n"); - if (opts->verbose) write_file(rebase_path_verbose(), "%s", ""); if (opts->strategy) @@@ -2481,8 -2435,6 +2447,8 @@@ write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign); if (opts->signoff) write_file(rebase_path_signoff(), "--signoff\n"); + if (opts->reschedule_failed_exec) + write_file(rebase_path_reschedule_failed_exec(), "%s", ""); return 0; } @@@ -2506,7 -2458,7 +2472,7 @@@ static int walk_revs_populate_todo(stru item->command = command; item->commit = commit; - item->arg = NULL; + item->arg_offset = 0; item->arg_len = 0; item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); @@@ -2867,7 -2819,7 +2833,7 @@@ static int do_exec(struct repository *r child_env.argv); /* force re-reading of the cache */ - if (discard_index(r->index) < 0 || read_index(r->index) < 0) + if (discard_index(r->index) < 0 || repo_read_index(r) < 0) return error(_("could not read index")); dirty = require_clean_work_tree(r, "rebase", NULL, 1, 1); @@@ -2991,7 -2943,7 +2957,7 @@@ static int do_reset(struct repository * struct unpack_trees_options unpack_tree_opts; int ret = 0; - if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) + if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) return -1; if (len == 10 && !strncmp("[new root]", name, len)) { @@@ -3036,7 -2988,7 +3002,7 @@@ unpack_tree_opts.merge = 1; unpack_tree_opts.update = 1; - if (read_index_unmerged(r->index)) { + if (repo_read_index_unmerged(r)) { rollback_lock_file(&lock); strbuf_release(&ref_name); return error_resolve_conflict(_(action_name(opts))); @@@ -3109,7 -3061,7 +3075,7 @@@ static int do_merge(struct repository * static struct lock_file lock; const char *p; - if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { ret = -1; goto leave_merge; } @@@ -3290,7 -3242,7 +3256,7 @@@ /* force re-reading of the cache */ if (!ret && (discard_index(r->index) < 0 || - read_index(r->index) < 0)) + repo_read_index(r) < 0)) ret = error(_("could not read index")); goto leave_merge; } @@@ -3312,8 -3264,8 +3278,8 @@@ commit_list_insert(j->item, &reversed); free_commit_list(bases); - read_index(r->index); - init_merge_options(&o); + repo_read_index(r); + init_merge_options(&o, r); o.branch1 = "HEAD"; o.branch2 = ref_name.buf; o.buffer_output = 2; @@@ -3545,6 -3497,8 +3511,8 @@@ static int pick_commits(struct reposito while (todo_list->current < todo_list->nr) { struct todo_item *item = todo_list->items + todo_list->current; + const char *arg = todo_item_get_arg(todo_list, item); + if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { @@@ -3557,11 -3511,10 +3525,11 @@@ fprintf(f, "%d\n", todo_list->done_nr); fclose(f); } - fprintf(stderr, "Rebasing (%d/%d)%s", - todo_list->done_nr, - todo_list->total_nr, - opts->verbose ? "\n" : "\r"); + if (!opts->quiet) + 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()); @@@ -3597,10 -3550,9 +3565,9 @@@ fprintf(stderr, _("Stopped at %s... %.*s\n"), short_commit_name(commit), - item->arg_len, item->arg); + item->arg_len, arg); return error_with_patch(r, commit, - item->arg, item->arg_len, opts, res, - !res); + arg, item->arg_len, opts, res, !res); } if (is_rebase_i(opts) && !res) record_in_rewritten(&item->commit->object.oid, @@@ -3609,7 -3561,7 +3576,7 @@@ if (res == 1) intend_to_amend(); return error_failed_squash(r, item->commit, opts, - item->arg_len, item->arg); + item->arg_len, arg); } else if (res && is_rebase_i(opts) && item->commit) { int to_amend = 0; struct object_id oid; @@@ -3628,23 -3580,22 +3595,23 @@@ to_amend = 1; return res | error_with_patch(r, item->commit, - item->arg, item->arg_len, opts, + arg, item->arg_len, opts, res, to_amend); } } else if (item->command == TODO_EXEC) { - char *end_of_arg = (char *)(item->arg + item->arg_len); + char *end_of_arg = (char *)(arg + item->arg_len); int saved = *end_of_arg; struct stat st; *end_of_arg = '\0'; - res = do_exec(r, item->arg); + res = do_exec(r, arg); *end_of_arg = saved; /* Reread the todo file if it has changed. */ - if (res) - ; /* fall through */ - else if (stat(get_todo_path(opts), &st)) + if (res) { + if (opts->reschedule_failed_exec) + reschedule = 1; + } else if (stat(get_todo_path(opts), &st)) res = error_errno(_("could not stat '%s'"), get_todo_path(opts)); else if (match_stat_data(&todo_list->stat, &st)) { @@@ -3655,14 -3606,14 +3622,14 @@@ todo_list->current = -1; } } else if (item->command == TODO_LABEL) { - if ((res = do_label(r, item->arg, item->arg_len))) + if ((res = do_label(r, arg, item->arg_len))) reschedule = 1; } else if (item->command == TODO_RESET) { - if ((res = do_reset(r, item->arg, item->arg_len, opts))) + if ((res = do_reset(r, arg, item->arg_len, opts))) reschedule = 1; } else if (item->command == TODO_MERGE) { if ((res = do_merge(r, item->commit, - item->arg, item->arg_len, + arg, item->arg_len, item->flags, opts)) < 0) reschedule = 1; else if (item->commit) @@@ -3671,9 -3622,8 +3638,8 @@@ if (res > 0) /* failed with merge conflicts */ return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); @@@ -3688,9 -3638,8 +3654,8 @@@ if (item->commit) return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } todo_list->current++; @@@ -3787,7 -3736,6 +3752,7 @@@ cleanup_head_ref hook.in = open(rebase_path_rewritten_list(), O_RDONLY); hook.stdout_to_stderr = 1; + hook.trace2_hook_name = "post-rewrite"; argv_array_push(&hook.args, post_rewrite_hook); argv_array_push(&hook.args, "rebase"); /* we don't care if this hook failed */ @@@ -3796,10 -3744,8 +3761,10 @@@ } apply_autostash(opts); - fprintf(stderr, "Successfully rebased and updated %s.\n", - head_ref.buf); + if (!opts->quiet) + fprintf(stderr, + "Successfully rebased and updated %s.\n", + head_ref.buf); strbuf_release(&buf); strbuf_release(&head_ref); @@@ -3990,7 -3936,7 +3955,7 @@@ int sequencer_continue(struct repositor goto release_todo_list; } if (index_differs_from(r, "HEAD", NULL, 0)) { - res = error_dirty_index(r->index, opts); + res = error_dirty_index(r, opts); goto release_todo_list; } todo_list.current++; @@@ -4100,7 -4046,8 +4065,7 @@@ void append_signoff(struct strbuf *msgb int has_footer; strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); + strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT)); strbuf_addch(&sob, '\n'); if (!ignore_footer) @@@ -4267,7 -4214,7 +4232,7 @@@ static const char *label_oid(struct obj } static int make_script_with_merges(struct pretty_print_context *pp, - struct rev_info *revs, FILE *out, + struct rev_info *revs, struct strbuf *out, unsigned flags) { int keep_empty = flags & TODO_LIST_KEEP_EMPTY; @@@ -4412,7 -4359,7 +4377,7 @@@ * gathering commits not yet shown, reversing the list on the fly, * then outputting that list (labeling revisions as needed). */ - fprintf(out, "%s onto\n", cmd_label); + strbuf_addf(out, "%s onto\n", cmd_label); for (iter = tips; iter; iter = iter->next) { struct commit_list *list = NULL, *iter2; @@@ -4422,9 -4369,9 +4387,9 @@@ entry = oidmap_get(&state.commit2label, &commit->object.oid); if (entry) - fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string); + strbuf_addf(out, "\n%c Branch %s\n", comment_line_char, entry->string); else - fprintf(out, "\n"); + strbuf_addch(out, '\n'); while (oidset_contains(&interesting, &commit->object.oid) && !oidset_contains(&shown, &commit->object.oid)) { @@@ -4437,8 -4384,8 +4402,8 @@@ } if (!commit) - fprintf(out, "%s %s\n", cmd_reset, - rebase_cousins ? "onto" : "[new root]"); + strbuf_addf(out, "%s %s\n", cmd_reset, + rebase_cousins ? "onto" : "[new root]"); else { const char *to = NULL; @@@ -4451,12 -4398,12 +4416,12 @@@ &state); if (!to || !strcmp(to, "onto")) - fprintf(out, "%s onto\n", cmd_reset); + strbuf_addf(out, "%s onto\n", cmd_reset); else { strbuf_reset(&oneline); pretty_print_commit(pp, commit, &oneline); - fprintf(out, "%s %s # %s\n", - cmd_reset, to, oneline.buf); + strbuf_addf(out, "%s %s # %s\n", + cmd_reset, to, oneline.buf); } } @@@ -4465,11 -4412,11 +4430,11 @@@ entry = oidmap_get(&commit2todo, oid); /* only show if not already upstream */ if (entry) - fprintf(out, "%s\n", entry->string); + strbuf_addf(out, "%s\n", entry->string); entry = oidmap_get(&state.commit2label, oid); if (entry) - fprintf(out, "%s %s\n", - cmd_label, entry->string); + strbuf_addf(out, "%s %s\n", + cmd_label, entry->string); oidset_insert(&shown, oid); } @@@ -4491,13 -4438,11 +4456,11 @@@ return 0; } - int sequencer_make_script(struct repository *r, FILE *out, - int argc, const char **argv, - unsigned flags) + int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, + const char **argv, unsigned flags) { char *format = NULL; struct pretty_print_context pp = {0}; - struct strbuf buf = STRBUF_INIT; struct rev_info revs; struct commit *commit; int keep_empty = flags & TODO_LIST_KEEP_EMPTY; @@@ -4540,16 -4485,13 +4503,13 @@@ if (!is_empty && (commit->object.flags & PATCHSAME)) continue; - strbuf_reset(&buf); if (!keep_empty && is_empty) - strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, "%s %s ", insn, + strbuf_addf(out, "%c ", comment_line_char); + strbuf_addf(out, "%s %s ", insn, oid_to_hex(&commit->object.oid)); - pretty_print_commit(&pp, commit, &buf); - strbuf_addch(&buf, '\n'); - fputs(buf.buf, out); + pretty_print_commit(&pp, commit, out); + strbuf_addch(out, '\n'); } - strbuf_release(&buf); return 0; } @@@ -4557,91 -4499,95 +4517,95 @@@ * Add commands after pick and (series of) squash/fixup commands * in the todo list. */ - int sequencer_add_exec_commands(struct repository *r, - const char *commands) + void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf *buf = &todo_list.buf; - size_t offset = 0, commands_len = strlen(commands); - int i, insert; + struct strbuf *buf = &todo_list->buf; + size_t base_offset = buf->len; + int i, insert, nr = 0, alloc = 0; + struct todo_item *items = NULL, *base_items = NULL; + + base_items = xcalloc(commands->nr, sizeof(struct todo_item)); + for (i = 0; i < commands->nr; i++) { + size_t command_len = strlen(commands->items[i].string); + + strbuf_addstr(buf, commands->items[i].string); + strbuf_addch(buf, '\n'); - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + base_items[i].command = TODO_EXEC; + base_items[i].offset_in_buf = base_offset; + base_items[i].arg_offset = base_offset + strlen("exec "); + base_items[i].arg_len = command_len - strlen("exec "); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); + base_offset += command_len + 1; } /* * Insert after every pick. Here, fixup/squash chains * are considered part of the pick, so we insert the commands *after* * those chains if there are any. + * + * As we insert the exec commands immediatly after rearranging + * any fixups and before the user edits the list, a fixup chain + * can never contain comments (any comments are empty picks that + * have been commented out because the user did not specify + * --keep-empty). So, it is safe to insert an exec command + * without looking at the command following a comment. */ - insert = -1; - for (i = 0; i < todo_list.nr; i++) { - enum todo_command command = todo_list.items[i].command; - - if (insert >= 0) { - /* skip fixup/squash chains */ - if (command == TODO_COMMENT) - continue; - else if (is_fixup(command)) { - insert = i + 1; - continue; - } - strbuf_insert(buf, - todo_list.items[insert].offset_in_buf + - offset, commands, commands_len); - offset += commands_len; - insert = -1; + insert = 0; + for (i = 0; i < todo_list->nr; i++) { + enum todo_command command = todo_list->items[i].command; + if (insert && !is_fixup(command)) { + ALLOC_GROW(items, nr + commands->nr, alloc); + COPY_ARRAY(items + nr, base_items, commands->nr); + nr += commands->nr; + + insert = 0; } + ALLOC_GROW(items, nr + 1, alloc); + items[nr++] = todo_list->items[i]; + if (command == TODO_PICK || command == TODO_MERGE) - insert = i + 1; + insert = 1; } /* insert or append final */ - if (insert >= 0 && insert < todo_list.nr) - strbuf_insert(buf, todo_list.items[insert].offset_in_buf + - offset, commands, commands_len); - else if (insert >= 0 || !offset) - strbuf_add(buf, commands, commands_len); + if (insert || nr == todo_list->nr) { + ALLOC_GROW(items, nr + commands->nr, alloc); + COPY_ARRAY(items + nr, base_items, commands->nr); + nr += commands->nr; + } - i = write_message(buf->buf, buf->len, todo_file, 0); - todo_list_release(&todo_list); - return i; + free(base_items); + FREE_AND_NULL(todo_list->items); + todo_list->items = items; + todo_list->nr = nr; + todo_list->alloc = alloc; } - int transform_todos(struct repository *r, unsigned flags) + static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, + struct strbuf *buf, int num, unsigned flags) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf buf = STRBUF_INIT; struct todo_item *item; - int i; + int i, max = todo_list->nr; - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + if (num > 0 && num < max) + max = num; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + for (item = todo_list->items, i = 0; i < max; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { - strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg); + strbuf_addf(buf, "%.*s\n", item->arg_len, + todo_item_get_arg(todo_list, item)); continue; } /* add command to the buffer */ if (flags & TODO_LIST_ABBREVIATE_CMDS) - strbuf_addch(&buf, command_to_char(item->command)); + strbuf_addch(buf, command_to_char(item->command)); else - strbuf_addstr(&buf, command_to_string(item->command)); + strbuf_addstr(buf, command_to_string(item->command)); /* add commit id */ if (item->commit) { @@@ -4651,181 -4597,92 +4615,92 @@@ if (item->command == TODO_MERGE) { if (item->flags & TODO_EDIT_MERGE_MSG) - strbuf_addstr(&buf, " -c"); + strbuf_addstr(buf, " -c"); else - strbuf_addstr(&buf, " -C"); + strbuf_addstr(buf, " -C"); } - strbuf_addf(&buf, " %s", oid); + strbuf_addf(buf, " %s", oid); } /* add all the rest */ if (!item->arg_len) - strbuf_addch(&buf, '\n'); + strbuf_addch(buf, '\n'); else - strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg); + strbuf_addf(buf, " %.*s\n", item->arg_len, + todo_item_get_arg(todo_list, item)); } - - i = write_message(buf.buf, buf.len, todo_file, 0); - todo_list_release(&todo_list); - return i; } - enum missing_commit_check_level get_missing_commit_check_level(void) + int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags) { - const char *value; + int res; + struct strbuf buf = STRBUF_INIT; - if (git_config_get_value("rebase.missingcommitscheck", &value) || - !strcasecmp("ignore", value)) - return MISSING_COMMIT_CHECK_IGNORE; - if (!strcasecmp("warn", value)) - return MISSING_COMMIT_CHECK_WARN; - if (!strcasecmp("error", value)) - return MISSING_COMMIT_CHECK_ERROR; - warning(_("unrecognized setting %s for option " - "rebase.missingCommitsCheck. Ignoring."), value); - return MISSING_COMMIT_CHECK_IGNORE; - } + todo_list_to_strbuf(r, todo_list, &buf, num, flags); + if (flags & TODO_LIST_APPEND_TODO_HELP) + append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list), + shortrevisions, shortonto, &buf); - define_commit_slab(commit_seen, unsigned char); - /* - * Check if the user dropped some commits by mistake - * Behaviour determined by rebase.missingCommitsCheck. - * Check if there is an unrecognized command or a - * bad SHA-1 in a command. - */ - int check_todo_list(struct repository *r) - { - enum missing_commit_check_level check_level = get_missing_commit_check_level(); - struct strbuf todo_file = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf missing = STRBUF_INIT; - int advise_to_edit_todo = 0, res = 0, i; - struct commit_seen commit_seen; + res = write_message(buf.buf, buf.len, file, 0); + strbuf_release(&buf); - init_commit_seen(&commit_seen); + return res; + } - strbuf_addstr(&todo_file, rebase_path_todo()); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { - res = -1; - goto leave_check; - } - advise_to_edit_todo = res = - parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + static const char edit_todo_list_advice[] = + N_("You can fix this with 'git rebase --edit-todo' " + "and then run 'git rebase --continue'.\n" + "Or you can abort the rebase with 'git rebase" + " --abort'.\n"); - if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) - goto leave_check; + int check_todo_list_from_file(struct repository *r) + { + struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT; + int res = 0; - /* Mark the commits in git-rebase-todo as seen */ - for (i = 0; i < todo_list.nr; i++) { - struct commit *commit = todo_list.items[i].commit; - if (commit) - *commit_seen_at(&commit_seen, commit) = 1; + if (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) { + res = -1; + goto out; } - todo_list_release(&todo_list); - strbuf_addstr(&todo_file, ".backup"); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { + if (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) { res = -1; - goto leave_check; - } - strbuf_release(&todo_file); - res = !!parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - - /* Find commits in git-rebase-todo.backup yet unseen */ - for (i = todo_list.nr - 1; i >= 0; i--) { - struct todo_item *item = todo_list.items + i; - struct commit *commit = item->commit; - if (commit && !*commit_seen_at(&commit_seen, commit)) { - strbuf_addf(&missing, " - %s %.*s\n", - short_commit_name(commit), - item->arg_len, item->arg); - *commit_seen_at(&commit_seen, commit) = 1; - } + goto out; } - /* Warn about missing commits */ - if (!missing.len) - goto leave_check; - - if (check_level == MISSING_COMMIT_CHECK_ERROR) - advise_to_edit_todo = res = 1; - - fprintf(stderr, - _("Warning: some commits may have been dropped accidentally.\n" - "Dropped commits (newer to older):\n")); - - /* Make the list user-friendly and display */ - fputs(missing.buf, stderr); - strbuf_release(&missing); - - fprintf(stderr, _("To avoid this message, use \"drop\" to " - "explicitly remove a commit.\n\n" - "Use 'git config rebase.missingCommitsCheck' to change " - "the level of warnings.\n" - "The possible behaviours are: ignore, warn, error.\n\n")); - - leave_check: - clear_commit_seen(&commit_seen); - strbuf_release(&todo_file); - todo_list_release(&todo_list); - - if (advise_to_edit_todo) - fprintf(stderr, - _("You can fix this with 'git rebase --edit-todo' " - "and then run 'git rebase --continue'.\n" - "Or you can abort the rebase with 'git rebase" - " --abort'.\n")); + res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo); + if (!res) + res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo); + if (!res) + res = todo_list_check(&old_todo, &new_todo); + if (res) + fprintf(stderr, _(edit_todo_list_advice)); + out: + todo_list_release(&old_todo); + todo_list_release(&new_todo); return res; } - static int rewrite_file(const char *path, const char *buf, size_t len) - { - int rc = 0; - int fd = open(path, O_WRONLY | O_TRUNC); - if (fd < 0) - return error_errno(_("could not open '%s' for writing"), path); - if (write_in_full(fd, buf, len) < 0) - rc = error_errno(_("could not write to '%s'"), path); - if (close(fd) && !rc) - rc = error_errno(_("could not close '%s'"), path); - return rc; - } - /* skip picking commits whose parents are unchanged */ - static int skip_unnecessary_picks(struct repository *r, struct object_id *output_oid) + static int skip_unnecessary_picks(struct repository *r, + struct todo_list *todo_list, + struct object_id *base_oid) { - const char *todo_file = rebase_path_todo(); - struct strbuf buf = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; struct object_id *parent_oid; - int fd, i; - - if (!read_oneliner(&buf, rebase_path_onto(), 0)) - return error(_("could not read 'onto'")); - if (get_oid(buf.buf, output_oid)) { - strbuf_release(&buf); - return error(_("need a HEAD to fixup")); - } - strbuf_release(&buf); - - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) - return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } + int i; - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; + for (i = 0; i < todo_list->nr; i++) { + struct todo_item *item = todo_list->items + i; if (item->command >= TODO_NOOP) continue; if (item->command != TODO_PICK) break; if (parse_commit(item->commit)) { - todo_list_release(&todo_list); return error(_("could not parse commit '%s'"), oid_to_hex(&item->commit->object.oid)); } @@@ -4834,135 -4691,101 +4709,101 @@@ if (item->commit->parents->next) break; /* merge commit */ parent_oid = &item->commit->parents->item->object.oid; - if (!oideq(parent_oid, output_oid)) + if (!oideq(parent_oid, base_oid)) break; - oidcpy(output_oid, &item->commit->object.oid); + oidcpy(base_oid, &item->commit->object.oid); } if (i > 0) { - int offset = get_item_line_offset(&todo_list, i); const char *done_path = rebase_path_done(); - fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - if (fd < 0) { - error_errno(_("could not open '%s' for writing"), - done_path); - todo_list_release(&todo_list); - return -1; - } - if (write_in_full(fd, todo_list.buf.buf, offset) < 0) { + if (todo_list_write_to_file(r, todo_list, done_path, NULL, NULL, i, 0)) { error_errno(_("could not write to '%s'"), done_path); - todo_list_release(&todo_list); - close(fd); return -1; } - close(fd); - if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset, - todo_list.buf.len - offset) < 0) { - todo_list_release(&todo_list); - return -1; - } + MOVE_ARRAY(todo_list->items, todo_list->items + i, todo_list->nr - i); + todo_list->nr -= i; + todo_list->current = 0; - todo_list.current = i; - if (is_fixup(peek_command(&todo_list, 0))) - record_in_rewritten(output_oid, peek_command(&todo_list, 0)); + if (is_fixup(peek_command(todo_list, 0))) + record_in_rewritten(base_oid, peek_command(todo_list, 0)); } - todo_list_release(&todo_list); - return 0; } int complete_action(struct repository *r, 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 *onto, const char *orig_head, struct string_list *commands, + unsigned autosquash, struct todo_list *todo_list) { const char *shortonto, *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf *buf = &(todo_list.buf); + struct todo_list new_todo = TODO_LIST_INIT; + struct strbuf *buf = &todo_list->buf; struct object_id oid; - struct stat st; + int res; 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 (buf->len == 0) { + struct todo_item *item = append_new_todo(todo_list); + item->command = TODO_NOOP; + item->commit = NULL; + item->arg_len = item->arg_offset = item->flags = item->offset_in_buf = 0; + } - if (autosquash && rearrange_squash(r)) + if (autosquash && todo_list_rearrange_squash(todo_list)) return -1; - if (cmd && *cmd) - sequencer_add_exec_commands(r, cmd); - - if (strbuf_read_file(buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (parse_insn_buffer(r, buf->buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } + if (commands->nr) + todo_list_add_exec_commands(todo_list, commands); - if (count_commands(&todo_list) == 0) { + 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); + res = edit_todo_list(r, todo_list, &new_todo, shortrevisions, + shortonto, flags); + if (res == -1) 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(r, flags | TODO_LIST_SHORTEN_IDS)) - return error(_("could not transform the todo list")); - - strbuf_reset(buf); - - if (launch_sequence_editor(todo_file, buf, NULL)) { + else if (res == -2) { apply_autostash(opts); sequencer_remove_state(opts); - todo_list_release(&todo_list); return -1; - } - - strbuf_stripspace(buf, 1); - if (buf->len == 0) { + } else if (res == -3) { apply_autostash(opts); sequencer_remove_state(opts); - todo_list_release(&todo_list); + todo_list_release(&new_todo); return error(_("nothing to do")); } - todo_list_release(&todo_list); - - if (check_todo_list(r)) { + if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) || + todo_list_check(todo_list, &new_todo)) { + fprintf(stderr, _(edit_todo_list_advice)); checkout_onto(opts, onto_name, onto, orig_head); + todo_list_release(&new_todo); + return -1; } - if (transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS))) - return error(_("could not transform the todo list")); - - if (opts->allow_ff && skip_unnecessary_picks(r, &oid)) + if (opts->allow_ff && skip_unnecessary_picks(r, &new_todo, &oid)) { + todo_list_release(&new_todo); return error(_("could not skip unnecessary pick commands")); + } + + if (todo_list_write_to_file(r, &new_todo, todo_file, NULL, NULL, -1, + flags & ~(TODO_LIST_SHORTEN_IDS))) { + todo_list_release(&new_todo); + return error_errno(_("could not write '%s'"), todo_file); + } + + todo_list_release(&new_todo); if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head)) return -1; @@@ -4997,21 -4820,13 +4838,13 @@@ define_commit_slab(commit_todo_item, st * message will have to be retrieved from the commit (as the oneline in the * script cannot be trusted) in order to normalize the autosquash arrangement. */ - int rearrange_squash(struct repository *r) + int todo_list_rearrange_squash(struct todo_list *todo_list) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; struct hashmap subject2item; - int res = 0, rearranged = 0, *next, *tail, i; + int rearranged = 0, *next, *tail, i, nr = 0, alloc = 0; char **subjects; struct commit_todo_item commit_todo; - - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) - return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } + struct todo_item *items = NULL; init_commit_todo_item(&commit_todo); /* @@@ -5024,13 -4839,13 +4857,13 @@@ * be moved to appear after the i'th. */ hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, - NULL, todo_list.nr); - ALLOC_ARRAY(next, todo_list.nr); - ALLOC_ARRAY(tail, todo_list.nr); - ALLOC_ARRAY(subjects, todo_list.nr); - for (i = 0; i < todo_list.nr; i++) { + NULL, todo_list->nr); + ALLOC_ARRAY(next, todo_list->nr); + ALLOC_ARRAY(tail, todo_list->nr); + ALLOC_ARRAY(subjects, todo_list->nr); + for (i = 0; i < todo_list->nr; i++) { struct strbuf buf = STRBUF_INIT; - struct todo_item *item = todo_list.items + i; + struct todo_item *item = todo_list->items + i; const char *commit_buffer, *subject, *p; size_t subject_len; int i2 = -1; @@@ -5043,7 -4858,6 +4876,6 @@@ } if (is_fixup(item->command)) { - todo_list_release(&todo_list); clear_commit_todo_item(&commit_todo); return error(_("the script was already rearranged.")); } @@@ -5078,7 -4892,7 +4910,7 @@@ *commit_todo_item_at(&commit_todo, commit2)) /* found by commit name */ i2 = *commit_todo_item_at(&commit_todo, commit2) - - todo_list.items; + - todo_list->items; else { /* copy can be a prefix of the commit subject */ for (i2 = 0; i2 < i; i2++) @@@ -5091,7 -4905,7 +4923,7 @@@ } if (i2 >= 0) { rearranged = 1; - todo_list.items[i].command = + todo_list->items[i].command = starts_with(subject, "fixup!") ? TODO_FIXUP : TODO_SQUASH; if (next[i2] < 0) @@@ -5109,10 -4923,8 +4941,8 @@@ } if (rearranged) { - struct strbuf buf = STRBUF_INIT; - - for (i = 0; i < todo_list.nr; i++) { - enum todo_command command = todo_list.items[i].command; + for (i = 0; i < todo_list->nr; i++) { + enum todo_command command = todo_list->items[i].command; int cur = i; /* @@@ -5123,37 -4935,26 +4953,26 @@@ continue; while (cur >= 0) { - const char *bol = - get_item_line(&todo_list, cur); - const char *eol = - get_item_line(&todo_list, cur + 1); - - /* replace 'pick', by 'fixup' or 'squash' */ - command = todo_list.items[cur].command; - if (is_fixup(command)) { - strbuf_addstr(&buf, - todo_command_info[command].str); - bol += strcspn(bol, " \t"); - } - - strbuf_add(&buf, bol, eol - bol); - + ALLOC_GROW(items, nr + 1, alloc); + items[nr++] = todo_list->items[cur]; cur = next[cur]; } } - res = rewrite_file(todo_file, buf.buf, buf.len); - strbuf_release(&buf); + FREE_AND_NULL(todo_list->items); + todo_list->items = items; + todo_list->nr = nr; + todo_list->alloc = alloc; } free(next); free(tail); - for (i = 0; i < todo_list.nr; i++) + for (i = 0; i < todo_list->nr; i++) free(subjects[i]); free(subjects); hashmap_free(&subject2item, 1); - todo_list_release(&todo_list); clear_commit_todo_item(&commit_todo); - return res; + + return 0; } diff --combined sequencer.h index 4d505b3590,7cca49eff2..a515ee4457 --- a/sequencer.h +++ b/sequencer.h @@@ -10,6 -10,7 +10,7 @@@ struct repository const char *git_path_commit_editmsg(void); const char *git_path_seq_dir(void); const char *rebase_path_todo(void); + const char *rebase_path_todo_backup(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) @@@ -40,8 -41,6 +41,8 @@@ struct replay_opts int allow_empty_message; int keep_redundant_commits; int verbose; + int quiet; + int reschedule_failed_exec; int mainline; @@@ -66,14 -65,60 +67,60 @@@ }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } - enum missing_commit_check_level { - MISSING_COMMIT_CHECK_IGNORE = 0, - MISSING_COMMIT_CHECK_WARN, - MISSING_COMMIT_CHECK_ERROR + /* + * Note that ordering matters in this enum. Not only must it match the mapping + * of todo_command_info (in sequencer.c), it is also divided into several + * sections that matter. When adding new commands, make sure you add it in the + * right section. + */ + enum todo_command { + /* commands that handle commits */ + TODO_PICK = 0, + TODO_REVERT, + TODO_EDIT, + TODO_REWORD, + TODO_FIXUP, + TODO_SQUASH, + /* commands that do something else than handling a single commit */ + TODO_EXEC, + TODO_BREAK, + TODO_LABEL, + TODO_RESET, + TODO_MERGE, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP, + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; - int write_message(const void *buf, size_t len, const char *filename, - int append_eol); + struct todo_item { + enum todo_command command; + struct commit *commit; + unsigned int flags; + int arg_len; + /* The offset of the command and its argument in the strbuf */ + size_t offset_in_buf, arg_offset; + }; + + struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; + int done_nr, total_nr; + struct stat_data stat; + }; + + #define TODO_LIST_INIT { STRBUF_INIT } + + int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list); + int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags); + void todo_list_release(struct todo_list *todo_list); + const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item); /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); @@@ -93,20 -138,22 +140,20 @@@ int sequencer_remove_state(struct repla * commits should be rebased onto the new base, this flag needs to be passed. */ #define TODO_LIST_REBASE_COUSINS (1U << 4) - int sequencer_make_script(struct repository *repo, FILE *out, - int argc, const char **argv, - unsigned flags); - - int sequencer_add_exec_commands(struct repository *r, const char *command); - int transform_todos(struct repository *r, unsigned flags); - enum missing_commit_check_level get_missing_commit_check_level(void); - int check_todo_list(struct repository *r); + #define TODO_LIST_APPEND_TODO_HELP (1U << 5) + + int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, + const char **argv, unsigned flags); + + void todo_list_add_exec_commands(struct todo_list *todo_list, + struct string_list *commands); + int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, 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(struct repository *r); + const char *onto, const char *orig_head, struct string_list *commands, + unsigned autosquash, struct todo_list *todo_list); + int todo_list_rearrange_squash(struct todo_list *todo_list); -extern const char sign_off_header[]; - /* * Append a signoff to the commit message in "msgbuf". The ignore_footer * parameter specifies the number of bytes at the end of msgbuf that should @@@ -124,8 -171,7 +171,8 @@@ int update_head_with_reflog(const struc const struct object_id *new_head, const char *action, const struct strbuf *msg, struct strbuf *err); -void commit_post_rewrite(const struct commit *current_head, +void commit_post_rewrite(struct repository *r, + const struct commit *current_head, const struct object_id *new_head); int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);