diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 812 |
1 files changed, 365 insertions, 447 deletions
diff --git a/sequencer.c b/sequencer.c index 95dda23eee..4407a3f978 100644 --- a/sequencer.c +++ b/sequencer.c @@ -55,8 +55,7 @@ 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") +GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup") /* * The rebase command lines that have already been processed. A line @@ -172,17 +171,22 @@ static int git_sequencer_config(const char *k, const char *v, void *cb) if (status) return status; - if (!strcmp(s, "verbatim")) + if (!strcmp(s, "verbatim")) { opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE; - else if (!strcmp(s, "whitespace")) + opts->explicit_cleanup = 1; + } else if (!strcmp(s, "whitespace")) { opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE; - else if (!strcmp(s, "strip")) + opts->explicit_cleanup = 1; + } else if (!strcmp(s, "strip")) { opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL; - else if (!strcmp(s, "scissors")) - opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE; - else + opts->explicit_cleanup = 1; + } else if (!strcmp(s, "scissors")) { + opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SCISSORS; + opts->explicit_cleanup = 1; + } else { warning(_("invalid commit message cleanup mode '%s'"), s); + } free((char *)s); return status; @@ -384,8 +388,8 @@ static void print_advice(struct repository *r, int show_hint, } } -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; @@ -511,11 +515,54 @@ static int fast_forward_to(struct repository *r, return 0; } +enum commit_msg_cleanup_mode get_cleanup_mode(const char *cleanup_arg, + int use_editor) +{ + if (!cleanup_arg || !strcmp(cleanup_arg, "default")) + return use_editor ? COMMIT_MSG_CLEANUP_ALL : + COMMIT_MSG_CLEANUP_SPACE; + else if (!strcmp(cleanup_arg, "verbatim")) + return COMMIT_MSG_CLEANUP_NONE; + else if (!strcmp(cleanup_arg, "whitespace")) + return COMMIT_MSG_CLEANUP_SPACE; + else if (!strcmp(cleanup_arg, "strip")) + return COMMIT_MSG_CLEANUP_ALL; + else if (!strcmp(cleanup_arg, "scissors")) + return use_editor ? COMMIT_MSG_CLEANUP_SCISSORS : + COMMIT_MSG_CLEANUP_SPACE; + else + die(_("Invalid cleanup mode %s"), cleanup_arg); +} + +/* + * NB using int rather than enum cleanup_mode to stop clang's + * -Wtautological-constant-out-of-range-compare complaining that the comparison + * is always true. + */ +static const char *describe_cleanup_mode(int cleanup_mode) +{ + static const char *modes[] = { "whitespace", + "verbatim", + "scissors", + "strip" }; + + if (cleanup_mode < ARRAY_SIZE(modes)) + return modes[cleanup_mode]; + + BUG("invalid cleanup_mode provided (%d)", cleanup_mode); +} + void append_conflicts_hint(struct index_state *istate, - struct strbuf *msgbuf) + struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode) { int i; + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { + strbuf_addch(msgbuf, '\n'); + wt_status_append_cut_line(msgbuf); + strbuf_addch(msgbuf, comment_line_char); + } + strbuf_addch(msgbuf, '\n'); strbuf_commented_addf(msgbuf, "Conflicts:\n"); for (i = 0; i < istate->cache_nr;) { @@ -583,7 +630,8 @@ static int do_recursive_merge(struct repository *r, _(action_name(opts))); if (!clean) - append_conflicts_hint(r->index, msgbuf); + append_conflicts_hint(r->index, msgbuf, + opts->default_msg_cleanup); return !clean; } @@ -902,7 +950,6 @@ static int run_git_commit(struct repository *r, unsigned int flags) { struct child_process cmd = CHILD_PROCESS_INIT; - const char *value; if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) { struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; @@ -972,7 +1019,7 @@ static int run_git_commit(struct repository *r, argv_array_push(&cmd.args, "-e"); else if (!(flags & CLEANUP_MSG) && !opts->signoff && !opts->record_origin && - git_config_get_value("commit.cleanup", &value)) + !opts->explicit_cleanup) argv_array_push(&cmd.args, "--cleanup=verbatim"); if ((flags & ALLOW_EMPTY)) @@ -1013,6 +1060,16 @@ static int rest_is_empty(const struct strbuf *sb, int start) return 1; } +void cleanup_message(struct strbuf *msgbuf, + enum commit_msg_cleanup_mode cleanup_mode, int verbose) +{ + if (verbose || /* Truncate the message just before the diff, if any. */ + cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) + strbuf_setlen(msgbuf, wt_status_locate_end(msgbuf->buf, msgbuf->len)); + if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE) + strbuf_stripspace(msgbuf, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); +} + /* * Find out if the message in the strbuf contains only whitespace and * Signed-off-by lines. @@ -1383,8 +1440,13 @@ static int try_to_commit(struct repository *r, msg = &commit_msg; } - cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL : - opts->default_msg_cleanup; + if (flags & CLEANUP_MSG) + cleanup = COMMIT_MSG_CLEANUP_ALL; + else if ((opts->signoff || opts->record_origin) && + !opts->explicit_cleanup) + cleanup = COMMIT_MSG_CLEANUP_SPACE; + else + cleanup = opts->default_msg_cleanup; if (cleanup != COMMIT_MSG_CLEANUP_NONE) strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL); @@ -1513,32 +1575,6 @@ static int allow_empty(struct repository *r, 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; @@ -2021,26 +2057,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 +2070,14 @@ static struct todo_item *append_new_todo(struct todo_list *todo_list) 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 +2091,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, 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 +2118,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, 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 +2130,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, 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 +2144,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, } 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,23 +2156,27 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, 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; + return error(_("could not parse '%.*s'"), + (int)(end_of_object_name - bol), bol); item->commit = lookup_commit_reference(r, &commit_oid); 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 +2187,13 @@ static int parse_insn_buffer(struct repository *r, char *buf, 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 +2268,7 @@ static int read_populate_todo(struct repository *r, 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 +2299,7 @@ static int read_populate_todo(struct repository *r, 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; @@ -2308,6 +2338,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data) opts->no_commit = git_config_bool_or_int(key, value, &error_flag); else if (!strcmp(key, "options.edit")) opts->edit = git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.allow-empty")) + opts->allow_empty = + git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.allow-empty-message")) + opts->allow_empty_message = + git_config_bool_or_int(key, value, &error_flag); + else if (!strcmp(key, "options.keep-redundant-commits")) + opts->keep_redundant_commits = + git_config_bool_or_int(key, value, &error_flag); else if (!strcmp(key, "options.signoff")) opts->signoff = git_config_bool_or_int(key, value, &error_flag); else if (!strcmp(key, "options.record-origin")) @@ -2327,7 +2366,10 @@ static int populate_opts_cb(const char *key, const char *value, void *data) opts->allow_rerere_auto = git_config_bool_or_int(key, value, &error_flag) ? RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE; - else + else if (!strcmp(key, "options.default-msg-cleanup")) { + opts->explicit_cleanup = 1; + opts->default_msg_cleanup = get_cleanup_mode(value, 1); + } else return error(_("invalid key: %s"), key); if (!error_flag) @@ -2506,7 +2548,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, 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); @@ -2701,36 +2743,59 @@ static int save_opts(struct replay_opts *opts) int res = 0; if (opts->no_commit) - res |= git_config_set_in_file_gently(opts_file, "options.no-commit", "true"); + res |= git_config_set_in_file_gently(opts_file, + "options.no-commit", "true"); if (opts->edit) - res |= git_config_set_in_file_gently(opts_file, "options.edit", "true"); + res |= git_config_set_in_file_gently(opts_file, + "options.edit", "true"); + if (opts->allow_empty) + res |= git_config_set_in_file_gently(opts_file, + "options.allow-empty", "true"); + if (opts->allow_empty_message) + res |= git_config_set_in_file_gently(opts_file, + "options.allow-empty-message", "true"); + if (opts->keep_redundant_commits) + res |= git_config_set_in_file_gently(opts_file, + "options.keep-redundant-commits", "true"); if (opts->signoff) - res |= git_config_set_in_file_gently(opts_file, "options.signoff", "true"); + res |= git_config_set_in_file_gently(opts_file, + "options.signoff", "true"); if (opts->record_origin) - res |= git_config_set_in_file_gently(opts_file, "options.record-origin", "true"); + res |= git_config_set_in_file_gently(opts_file, + "options.record-origin", "true"); if (opts->allow_ff) - res |= git_config_set_in_file_gently(opts_file, "options.allow-ff", "true"); + res |= git_config_set_in_file_gently(opts_file, + "options.allow-ff", "true"); if (opts->mainline) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%d", opts->mainline); - res |= git_config_set_in_file_gently(opts_file, "options.mainline", buf.buf); + res |= git_config_set_in_file_gently(opts_file, + "options.mainline", buf.buf); strbuf_release(&buf); } if (opts->strategy) - res |= git_config_set_in_file_gently(opts_file, "options.strategy", opts->strategy); + res |= git_config_set_in_file_gently(opts_file, + "options.strategy", opts->strategy); if (opts->gpg_sign) - res |= git_config_set_in_file_gently(opts_file, "options.gpg-sign", opts->gpg_sign); + res |= git_config_set_in_file_gently(opts_file, + "options.gpg-sign", opts->gpg_sign); if (opts->xopts) { int i; for (i = 0; i < opts->xopts_nr; i++) res |= git_config_set_multivar_in_file_gently(opts_file, - "options.strategy-option", - opts->xopts[i], "^$", 0); + "options.strategy-option", + opts->xopts[i], "^$", 0); } if (opts->allow_rerere_auto) - res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto", - opts->allow_rerere_auto == RERERE_AUTOUPDATE ? - "true" : "false"); + res |= git_config_set_in_file_gently(opts_file, + "options.allow-rerere-auto", + opts->allow_rerere_auto == RERERE_AUTOUPDATE ? + "true" : "false"); + + if (opts->explicit_cleanup) + res |= git_config_set_in_file_gently(opts_file, + "options.default-msg-cleanup", + describe_cleanup_mode(opts->default_msg_cleanup)); return res; } @@ -3545,6 +3610,8 @@ static int pick_commits(struct repository *r, 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)) { @@ -3597,10 +3664,9 @@ static int pick_commits(struct repository *r, 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 +3675,7 @@ static int pick_commits(struct repository *r, 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,19 +3694,18 @@ static int pick_commits(struct repository *r, 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) { if (opts->reschedule_failed_exec) reschedule = 1; @@ -3648,6 +3713,7 @@ static int pick_commits(struct repository *r, res = error_errno(_("could not stat '%s'"), get_todo_path(opts)); else if (match_stat_data(&todo_list->stat, &st)) { + /* Reread the todo file if it has changed. */ todo_list_release(todo_list); if (read_populate_todo(r, todo_list, opts)) res = -1; /* message was printed */ @@ -3655,14 +3721,14 @@ static int pick_commits(struct repository *r, 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 +3737,8 @@ static int pick_commits(struct repository *r, 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 +3753,8 @@ static int pick_commits(struct repository *r, 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++; @@ -4267,7 +4331,7 @@ static const char *label_oid(struct object_id *oid, const char *label, } 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 +4476,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, * 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 +4486,9 @@ static int make_script_with_merges(struct pretty_print_context *pp, 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 +4501,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, } 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 +4515,12 @@ static int make_script_with_merges(struct pretty_print_context *pp, &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 +4529,11 @@ static int make_script_with_merges(struct pretty_print_context *pp, 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 +4555,11 @@ static int make_script_with_merges(struct pretty_print_context *pp, 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 +4602,13 @@ int sequencer_make_script(struct repository *r, FILE *out, 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 +4616,95 @@ int sequencer_make_script(struct repository *r, FILE *out, * 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; - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + 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); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); + strbuf_addstr(buf, commands->items[i].string); + strbuf_addch(buf, '\n'); + + 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 "); + + base_offset += command_len + 1; } /* * Insert <commands> 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 <commands> */ - 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; - - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + int i, max = todo_list->nr; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } + if (num > 0 && num < max) + max = num; - 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 +4714,92 @@ int transform_todos(struct repository *r, unsigned flags) 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; - - 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; -} + int res; + struct strbuf buf = STRBUF_INIT; -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; + 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); - init_commit_seen(&commit_seen); + res = write_message(buf.buf, buf.len, file, 0); + strbuf_release(&buf); - 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); + return res; +} - if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) - goto leave_check; +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"); - /* 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; - } +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; - 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(&new_todo.buf, rebase_path_todo()) < 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 (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) { + res = -1; + goto out; + } - 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 +4808,101 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output 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 +4937,13 @@ define_commit_slab(commit_todo_item, struct todo_item *); * 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 +4956,13 @@ int rearrange_squash(struct repository *r) * 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 +4975,6 @@ int rearrange_squash(struct repository *r) } 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 +5009,7 @@ int rearrange_squash(struct repository *r) *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 +5022,7 @@ int rearrange_squash(struct repository *r) } 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 +5040,8 @@ int rearrange_squash(struct repository *r) } 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 +5052,26 @@ int rearrange_squash(struct repository *r) 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; } |