diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 1186 |
1 files changed, 549 insertions, 637 deletions
diff --git a/sequencer.c b/sequencer.c index e1a4dd15f1..546f281898 100644 --- a/sequencer.c +++ b/sequencer.c @@ -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 @@ 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 @@ -150,6 +149,7 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete") 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") @@ -157,7 +157,7 @@ static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") 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) { @@ -356,7 +356,8 @@ static void free_message(struct commit *commit, struct commit_message *msg) unuse_commit_buffer(commit, msg->message); } -static void print_advice(int show_hint, struct replay_opts *opts) +static void print_advice(struct repository *r, int show_hint, + struct replay_opts *opts) { char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -367,7 +368,7 @@ static void print_advice(int show_hint, struct replay_opts *opts) * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ - unlink(git_path_cherry_pick_head(the_repository)); + unlink(git_path_cherry_pick_head(r)); return; } @@ -382,8 +383,8 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -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; @@ -440,14 +441,14 @@ static int read_oneliner(struct strbuf *buf, return 1; } -static struct tree *empty_tree(void) +static struct tree *empty_tree(struct repository *r) { - return lookup_tree(the_repository, the_repository->hash_algo->empty_tree); + return lookup_tree(r, the_hash_algo->empty_tree); } -static int error_dirty_index(struct replay_opts *opts) +static int error_dirty_index(struct repository *repo, struct replay_opts *opts) { - if (read_cache_unmerged()) + if (repo_read_index_unmerged(repo)) return error_resolve_conflict(_(action_name(opts))); error(_("your local changes would be overwritten by %s."), @@ -472,15 +473,18 @@ static void update_abort_safety_file(void) write_file(git_path_abort_safety_file(), "%s", ""); } -static int fast_forward_to(const struct object_id *to, const struct object_id *from, - int unborn, struct replay_opts *opts) +static int fast_forward_to(struct repository *r, + const struct object_id *to, + const struct object_id *from, + int unborn, + struct replay_opts *opts) { struct ref_transaction *transaction; struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - read_index(&the_index); - if (checkout_fast_forward(the_repository, from, to, 1)) + repo_read_index(r); + if (checkout_fast_forward(r, from, to, 1)) return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); @@ -506,24 +510,26 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f return 0; } -void append_conflicts_hint(struct strbuf *msgbuf) +void append_conflicts_hint(struct index_state *istate, + struct strbuf *msgbuf) { int i; strbuf_addch(msgbuf, '\n'); strbuf_commented_addf(msgbuf, "Conflicts:\n"); - for (i = 0; i < active_nr;) { - const struct cache_entry *ce = active_cache[i++]; + for (i = 0; i < istate->cache_nr;) { + const struct cache_entry *ce = istate->cache[i++]; if (ce_stage(ce)) { strbuf_commented_addf(msgbuf, "\t%s\n", ce->name); - while (i < active_nr && !strcmp(ce->name, - active_cache[i]->name)) + while (i < istate->cache_nr && + !strcmp(ce->name, istate->cache[i]->name)) i++; } } } -static int do_recursive_merge(struct commit *base, struct commit *next, +static int do_recursive_merge(struct repository *r, + struct commit *base, struct commit *next, const char *base_label, const char *next_label, struct object_id *head, struct strbuf *msgbuf, struct replay_opts *opts) @@ -534,12 +540,12 @@ static int do_recursive_merge(struct commit *base, struct commit *next, 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_cache(); + 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)"; @@ -548,8 +554,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.show_rename_progress = 1; head_tree = parse_tree_indirect(head); - next_tree = next ? get_commit_tree(next) : empty_tree(); - base_tree = base ? get_commit_tree(base) : empty_tree(); + next_tree = next ? get_commit_tree(next) : empty_tree(r); + base_tree = base ? get_commit_tree(base) : empty_tree(r); for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) parse_merge_opt(&o, *xopt); @@ -566,7 +572,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, return clean; } - if (write_locked_index(&the_index, &index_lock, + if (write_locked_index(r->index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) /* * TRANSLATORS: %s will be "revert", "cherry-pick" or @@ -576,34 +582,35 @@ static int do_recursive_merge(struct commit *base, struct commit *next, _(action_name(opts))); if (!clean) - append_conflicts_hint(msgbuf); + append_conflicts_hint(r->index, msgbuf); return !clean; } -static struct object_id *get_cache_tree_oid(void) +static struct object_id *get_cache_tree_oid(struct index_state *istate) { - if (!active_cache_tree) - active_cache_tree = cache_tree(); + if (!istate->cache_tree) + istate->cache_tree = cache_tree(); - if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(&the_index, 0)) { + if (!cache_tree_fully_valid(istate->cache_tree)) + if (cache_tree_update(istate, 0)) { error(_("unable to update cache tree")); return NULL; } - return &active_cache_tree->oid; + return &istate->cache_tree->oid; } -static int is_index_unchanged(void) +static int is_index_unchanged(struct repository *r) { struct object_id head_oid, *cache_tree_oid; struct commit *head_commit; + struct index_state *istate = r->index; if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) return error(_("could not resolve HEAD commit")); - head_commit = lookup_commit(the_repository, &head_oid); + head_commit = lookup_commit(r, &head_oid); /* * If head_commit is NULL, check_commit, called from @@ -616,7 +623,7 @@ static int is_index_unchanged(void) if (parse_commit(head_commit)) return -1; - if (!(cache_tree_oid = get_cache_tree_oid())) + if (!(cache_tree_oid = get_cache_tree_oid(istate))) return -1; return oideq(cache_tree_oid, get_commit_tree_oid(head_commit)); @@ -829,7 +836,7 @@ static const char *read_author_ident(struct strbuf *buf) } 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); @@ -888,7 +895,9 @@ static int run_command_silent_on_success(struct child_process *cmd) * interactive rebase: in that case, we will want to retain the * author metadata. */ -static int run_git_commit(const char *defmsg, struct replay_opts *opts, +static int run_git_commit(struct repository *r, + const char *defmsg, + struct replay_opts *opts, unsigned int flags) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -911,7 +920,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (!defmsg) BUG("root commit without message"); - if (!(cache_tree_oid = get_cache_tree_oid())) + if (!(cache_tree_oid = get_cache_tree_oid(r->index))) res = -1; if (!res) @@ -1093,6 +1102,7 @@ static int run_rewrite_hook(const struct object_id *oldoid, proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; + proc.trace2_hook_name = "post-rewrite"; code = start_command(&proc); if (code) @@ -1106,7 +1116,8 @@ static int run_rewrite_hook(const struct object_id *oldoid, 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; @@ -1115,12 +1126,14 @@ void commit_post_rewrite(const struct commit *old_head, 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); } -static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit) +static int run_prepare_commit_msg_hook(struct repository *r, + struct strbuf *msg, + const char *commit) { struct argv_array hook_env = ARGV_ARRAY_INIT; int ret; @@ -1130,7 +1143,7 @@ static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit) if (write_message(msg->buf, msg->len, name, 0)) return -1; - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file()); + argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", r->index_file); argv_array_push(&hook_env, "GIT_EDITOR=:"); if (commit) ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name, @@ -1186,7 +1199,9 @@ static const char *implicit_ident_advice(void) } -void print_commit_summary(const char *prefix, const struct object_id *oid, +void print_commit_summary(struct repository *r, + const char *prefix, + const struct object_id *oid, unsigned int flags) { struct rev_info rev; @@ -1197,7 +1212,7 @@ void print_commit_summary(const char *prefix, const struct object_id *oid, struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; - commit = lookup_commit(the_repository, oid); + commit = lookup_commit(r, oid); if (!commit) die(_("couldn't look up newly created commit")); if (parse_commit(commit)) @@ -1230,7 +1245,7 @@ void print_commit_summary(const char *prefix, const struct object_id *oid, strbuf_release(&author_ident); strbuf_release(&committer_ident); - repo_init_revisions(the_repository, &rev, prefix); + repo_init_revisions(r, &rev, prefix); setup_revisions(0, NULL, &rev, NULL); rev.diff = 1; @@ -1264,7 +1279,7 @@ void print_commit_summary(const char *prefix, const struct object_id *oid, strbuf_release(&format); } -static int parse_head(struct commit **head) +static int parse_head(struct repository *r, struct commit **head) { struct commit *current_head; struct object_id oid; @@ -1272,7 +1287,7 @@ static int parse_head(struct commit **head) if (get_oid("HEAD", &oid)) { current_head = NULL; } else { - current_head = lookup_commit_reference(the_repository, &oid); + current_head = lookup_commit_reference(r, &oid); if (!current_head) return error(_("could not parse HEAD")); if (!oideq(&oid, ¤t_head->object.oid)) { @@ -1296,7 +1311,8 @@ static int parse_head(struct commit **head) * 0 - success * 1 - run 'git commit' */ -static int try_to_commit(struct strbuf *msg, const char *author, +static int try_to_commit(struct repository *r, + struct strbuf *msg, const char *author, struct replay_opts *opts, unsigned int flags, struct object_id *oid) { @@ -1311,7 +1327,7 @@ static int try_to_commit(struct strbuf *msg, const char *author, enum commit_msg_cleanup_mode cleanup; int res = 0; - if (parse_head(¤t_head)) + if (parse_head(r, ¤t_head)) return -1; if (flags & AMEND_MSG) { @@ -1340,7 +1356,7 @@ static int try_to_commit(struct strbuf *msg, const char *author, commit_list_insert(current_head, &parents); } - if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) { + if (write_index_as_tree(&tree, r->index, r->index_file, 0, NULL)) { res = error(_("git write-tree failed to write a tree")); goto out; } @@ -1353,7 +1369,7 @@ static int try_to_commit(struct strbuf *msg, const char *author, } if (find_hook("prepare-commit-msg")) { - res = run_prepare_commit_msg_hook(msg, hook_commit); + res = run_prepare_commit_msg_hook(r, msg, hook_commit); if (res) goto out; if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(), @@ -1391,7 +1407,7 @@ static int try_to_commit(struct strbuf *msg, const char *author, } if (flags & AMEND_MSG) - commit_post_rewrite(current_head, oid); + commit_post_rewrite(r, current_head, oid); out: free_commit_extra_headers(extra); @@ -1402,7 +1418,8 @@ out: return res; } -static int do_commit(const char *msg_file, const char *author, +static int do_commit(struct repository *r, + const char *msg_file, const char *author, struct replay_opts *opts, unsigned int flags) { int res = 1; @@ -1417,20 +1434,20 @@ static int do_commit(const char *msg_file, const char *author, "from '%s'"), msg_file); - res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags, - &oid); + res = try_to_commit(r, msg_file ? &sb : NULL, + author, opts, flags, &oid); strbuf_release(&sb); if (!res) { - unlink(git_path_cherry_pick_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_merge_msg(r)); if (!is_rebase_i(opts)) - print_commit_summary(NULL, &oid, + print_commit_summary(r, NULL, &oid, SUMMARY_SHOW_AUTHOR_DATE); return res; } } if (res == 1) - return run_git_commit(msg_file, opts, flags); + return run_git_commit(r, msg_file, opts, flags); return res; } @@ -1458,7 +1475,9 @@ static int is_original_commit_empty(struct commit *commit) /* * Do we run "git commit" with "--allow-empty"? */ -static int allow_empty(struct replay_opts *opts, struct commit *commit) +static int allow_empty(struct repository *r, + struct replay_opts *opts, + struct commit *commit) { int index_unchanged, empty_commit; @@ -1475,7 +1494,7 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) if (!opts->allow_empty) return 0; /* let "git commit" barf as necessary */ - index_unchanged = is_index_unchanged(); + index_unchanged = is_index_unchanged(r); if (index_unchanged < 0) return index_unchanged; if (!index_unchanged) @@ -1493,32 +1512,6 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) 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; @@ -1579,8 +1572,10 @@ static int is_pick_or_similar(enum todo_command command) } } -static int update_squash_messages(enum todo_command command, - struct commit *commit, struct replay_opts *opts) +static int update_squash_messages(struct repository *r, + enum todo_command command, + struct commit *commit, + struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; int res; @@ -1609,7 +1604,7 @@ static int update_squash_messages(enum todo_command command, if (get_oid("HEAD", &head)) return error(_("need a HEAD to fixup")); - if (!(head_commit = lookup_commit_reference(the_repository, &head))) + if (!(head_commit = lookup_commit_reference(r, &head))) return error(_("could not read HEAD")); if (!(head_message = get_commit_buffer(head_commit, NULL))) return error(_("could not read HEAD's commit message")); @@ -1670,7 +1665,8 @@ static int update_squash_messages(enum todo_command command, 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; @@ -1695,7 +1691,8 @@ static void flush_rewritten_pending(void) { } 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) @@ -1708,11 +1705,14 @@ static void record_in_rewritten(struct object_id *oid, flush_rewritten_pending(); } -static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts, int final_fixup) +static int do_pick_commit(struct repository *r, + enum todo_command command, + struct commit *commit, + struct replay_opts *opts, + int final_fixup) { unsigned int flags = opts->edit ? EDIT_MSG : 0; - const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository); + const char *msg_file = opts->edit ? NULL : git_path_merge_msg(r); struct object_id head; struct commit *base, *next, *parent; const char *base_label, *next_label; @@ -1728,7 +1728,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, * that represents the "current" state for merge-recursive * to work on. */ - if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL)) + if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL)) return error(_("your index file is unmerged.")); } else { unborn = get_oid("HEAD", &head); @@ -1741,11 +1741,11 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, unborn = 1; } else if (unborn) oidcpy(&head, the_hash_algo->empty_tree); - if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD", + if (index_differs_from(r, unborn ? empty_tree_oid_hex() : "HEAD", NULL, 0)) - return error_dirty_index(opts); + return error_dirty_index(r, opts); } - discard_cache(); + discard_index(r->index); if (!commit->parents) parent = NULL; @@ -1766,9 +1766,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, 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; @@ -1781,7 +1785,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, (!parent && unborn))) { if (is_rebase_i(opts)) write_author_script(msg.message); - res = fast_forward_to(&commit->object.oid, &head, unborn, + res = fast_forward_to(r, &commit->object.oid, &head, unborn, opts); if (res || command != TODO_REWORD) goto leave; @@ -1845,7 +1849,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, if (command == TODO_REWORD) flags |= EDIT_MSG | VERIFY_MSG; else if (is_fixup(command)) { - if (update_squash_messages(command, commit, opts)) + if (update_squash_messages(r, command, commit, opts)) return -1; flags |= AMEND_MSG; if (!final_fixup) @@ -1854,12 +1858,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, flags |= CLEANUP_MSG; msg_file = rebase_path_fixup_msg(); } else { - const char *dest = git_path_squash_msg(the_repository); + const char *dest = git_path_squash_msg(r); unlink(dest); if (copy_file(dest, rebase_path_squash_msg(), 0666)) return error(_("could not rename '%s' to '%s'"), rebase_path_squash_msg(), dest); - unlink(git_path_merge_msg(the_repository)); + unlink(git_path_merge_msg(r)); msg_file = dest; flags |= EDIT_MSG; } @@ -1871,23 +1875,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, if (is_rebase_i(opts) && write_author_script(msg.message) < 0) res = -1; else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, + res = do_recursive_merge(r, base, next, base_label, next_label, &head, &msgbuf, opts); if (res < 0) goto leave; res |= write_message(msgbuf.buf, msgbuf.len, - git_path_merge_msg(the_repository), 0); + git_path_merge_msg(r), 0); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; res = write_message(msgbuf.buf, msgbuf.len, - git_path_merge_msg(the_repository), 0); + git_path_merge_msg(r), 0); commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res |= try_merge_command(the_repository, opts->strategy, + res |= try_merge_command(r, opts->strategy, opts->xopts_nr, (const char **)opts->xopts, common, oid_to_hex(&head), remotes); free_commit_list(common); @@ -1915,12 +1919,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, ? _("could not revert %s... %s") : _("could not apply %s... %s"), short_commit_name(commit), msg.subject); - print_advice(res == 1, opts); - repo_rerere(the_repository, opts->allow_rerere_auto); + print_advice(r, res == 1, opts); + repo_rerere(r, opts->allow_rerere_auto); goto leave; } - allow = allow_empty(opts, commit); + allow = allow_empty(r, opts, commit); if (allow < 0) { res = allow; goto leave; @@ -1929,7 +1933,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, if (!opts->no_commit) { fast_forward_edit: if (author || command == TODO_REVERT || (flags & AMEND_MSG)) - res = do_commit(msg_file, author, opts, flags); + res = do_commit(r, msg_file, author, opts, flags); else res = error(_("unable to parse commit author")); } @@ -1965,18 +1969,19 @@ static int prepare_revs(struct replay_opts *opts) return 0; } -static int read_and_refresh_cache(struct replay_opts *opts) +static int read_and_refresh_cache(struct repository *r, + struct replay_opts *opts) { struct lock_file index_lock = LOCK_INIT; - int index_fd = hold_locked_index(&index_lock, 0); - if (read_index(&the_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))); } - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); + refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (index_fd >= 0) { - if (write_locked_index(&the_index, &index_lock, + if (write_locked_index(r->index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) { return error(_("git %s: failed to refresh the index"), _(action_name(opts))); @@ -1989,26 +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); @@ -2021,7 +2007,14 @@ static struct todo_item *append_new_todo(struct todo_list *todo_list) return todo_list->items + todo_list->nr++; } -static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) +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 *buf, const char *bol, char *eol) { struct object_id commit_oid; char *end_of_object_name; @@ -2035,7 +2028,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) 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; } @@ -2062,7 +2055,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) 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; } @@ -2074,7 +2067,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) 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; } @@ -2088,7 +2081,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) } 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; } @@ -2100,22 +2093,27 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) 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(the_repository, &commit_oid); + item->commit = lookup_commit_reference(r, &commit_oid); return !item->commit; } -static int parse_insn_buffer(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'); @@ -2126,10 +2124,13 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) item = append_new_todo(todo_list); item->offset_in_buf = p - todo_list->buf.buf; - if (parse_insn_line(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) @@ -2187,8 +2188,9 @@ static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path) return len; } -static int read_populate_todo(struct todo_list *todo_list, - struct replay_opts *opts) +static int read_populate_todo(struct repository *r, + struct todo_list *todo_list, + struct replay_opts *opts) { struct stat st; const char *todo_file = get_todo_path(opts); @@ -2203,7 +2205,7 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("could not stat '%s'"), todo_file); fill_stat_data(&todo_list->stat, &st); - res = parse_insn_buffer(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 " @@ -2234,7 +2236,7 @@ static int read_populate_todo(struct todo_list *todo_list, FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && - !parse_insn_buffer(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; @@ -2273,6 +2275,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")) @@ -2357,11 +2368,17 @@ static int read_populate_opts(struct replay_opts *opts) 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); @@ -2424,9 +2441,6 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, 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) @@ -2443,6 +2457,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, 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; } @@ -2466,7 +2482,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); @@ -2551,12 +2567,12 @@ static int reset_for_rollback(const struct object_id *oid) return run_command_v_opt(argv, RUN_GIT_CMD); } -static int rollback_single_pick(void) +static int rollback_single_pick(struct repository *r) { struct object_id head_oid; - if (!file_exists(git_path_cherry_pick_head(the_repository)) && - !file_exists(git_path_revert_head(the_repository))) + if (!file_exists(git_path_cherry_pick_head(r)) && + !file_exists(git_path_revert_head(r))) return error(_("no cherry-pick or revert in progress")); if (read_ref_full("HEAD", 0, &head_oid, NULL)) return error(_("cannot resolve HEAD")); @@ -2565,7 +2581,7 @@ static int rollback_single_pick(void) return reset_for_rollback(&head_oid); } -int sequencer_rollback(struct replay_opts *opts) +int sequencer_rollback(struct repository *r, struct replay_opts *opts) { FILE *f; struct object_id oid; @@ -2579,7 +2595,7 @@ int sequencer_rollback(struct replay_opts *opts) * If CHERRY_PICK_HEAD or REVERT_HEAD indicates * a single-cherry-pick in progress, abort that. */ - return rollback_single_pick(); + return rollback_single_pick(r); } if (!f) return error_errno(_("cannot open '%s'"), git_path_head_file()); @@ -2661,40 +2677,60 @@ 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"); return res; } -static int make_patch(struct commit *commit, struct replay_opts *opts) +static int make_patch(struct repository *r, + struct commit *commit, + struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; struct rev_info log_tree_opt; @@ -2710,7 +2746,7 @@ static int make_patch(struct commit *commit, struct replay_opts *opts) strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - repo_init_revisions(the_repository, &log_tree_opt, NULL); + repo_init_revisions(r, &log_tree_opt, NULL); log_tree_opt.abbrev = 0; log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; @@ -2750,17 +2786,19 @@ static int intend_to_amend(void) return write_message(p, strlen(p), rebase_path_amend(), 1); } -static int error_with_patch(struct commit *commit, - const char *subject, int subject_len, - struct replay_opts *opts, int exit_code, int to_amend) +static int error_with_patch(struct repository *r, + struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, + int exit_code, int to_amend) { if (commit) { - if (make_patch(commit, opts)) + if (make_patch(r, commit, opts)) return -1; } else if (copy_file(rebase_path_message(), - git_path_merge_msg(the_repository), 0666)) + git_path_merge_msg(r), 0666)) return error(_("unable to copy '%s' to '%s'"), - git_path_merge_msg(the_repository), rebase_path_message()); + git_path_merge_msg(r), rebase_path_message()); if (to_amend) { if (intend_to_amend()) @@ -2791,21 +2829,24 @@ static int error_with_patch(struct commit *commit, return exit_code; } -static int error_failed_squash(struct commit *commit, - struct replay_opts *opts, int subject_len, const char *subject) +static int error_failed_squash(struct repository *r, + struct commit *commit, + struct replay_opts *opts, + int subject_len, + const char *subject) { if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666)) return error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), rebase_path_message()); - unlink(git_path_merge_msg(the_repository)); - if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666)) + unlink(git_path_merge_msg(r)); + if (copy_file(git_path_merge_msg(r), rebase_path_message(), 0666)) return error(_("could not copy '%s' to '%s'"), rebase_path_message(), - git_path_merge_msg(the_repository)); - return error_with_patch(commit, subject, subject_len, opts, 1, 0); + git_path_merge_msg(r)); + return error_with_patch(r, commit, subject, subject_len, opts, 1, 0); } -static int do_exec(const char *command_line) +static int do_exec(struct repository *r, const char *command_line) { struct argv_array child_env = ARGV_ARRAY_INIT; const char *child_argv[] = { NULL, NULL }; @@ -2820,10 +2861,10 @@ static int do_exec(const char *command_line) child_env.argv); /* force re-reading of the cache */ - if (discard_cache() < 0 || read_cache() < 0) + if (discard_index(r->index) < 0 || repo_read_index(r) < 0) return error(_("could not read index")); - dirty = require_clean_work_tree("rebase", NULL, 1, 1); + dirty = require_clean_work_tree(r, "rebase", NULL, 1, 1); if (status) { warning(_("execution failed: %s\n%s" @@ -2889,9 +2930,9 @@ static int safe_append(const char *filename, const char *fmt, ...) return 0; } -static int do_label(const char *name, int len) +static int do_label(struct repository *r, const char *name, int len) { - struct ref_store *refs = get_main_ref_store(the_repository); + struct ref_store *refs = get_main_ref_store(r); struct ref_transaction *transaction; struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT; struct strbuf msg = STRBUF_INIT; @@ -2932,7 +2973,9 @@ static int do_label(const char *name, int len) static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...); -static int do_reset(const char *name, int len, struct replay_opts *opts) +static int do_reset(struct repository *r, + const char *name, int len, + struct replay_opts *opts) { struct strbuf ref_name = STRBUF_INIT; struct object_id oid; @@ -2942,7 +2985,7 @@ static int do_reset(const char *name, int len, struct replay_opts *opts) 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)) { @@ -2981,13 +3024,13 @@ static int do_reset(const char *name, int len, struct replay_opts *opts) memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); setup_unpack_trees_porcelain(&unpack_tree_opts, "reset"); unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = &the_index; - unpack_tree_opts.dst_index = &the_index; + unpack_tree_opts.src_index = r->index; + unpack_tree_opts.dst_index = r->index; unpack_tree_opts.fn = oneway_merge; unpack_tree_opts.merge = 1; unpack_tree_opts.update = 1; - if (read_cache_unmerged()) { + if (repo_read_index_unmerged(r)) { rollback_lock_file(&lock); strbuf_release(&ref_name); return error_resolve_conflict(_(action_name(opts))); @@ -3009,9 +3052,9 @@ static int do_reset(const char *name, int len, struct replay_opts *opts) } tree = parse_tree_indirect(&oid); - prime_cache_tree(&the_index, tree); + prime_cache_tree(r, r->index, tree); - if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0) + if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) ret = error(_("could not write index")); free((void *)desc.buffer); @@ -3044,7 +3087,9 @@ static struct commit *lookup_label(const char *label, int len, return commit; } -static int do_merge(struct commit *commit, const char *arg, int arg_len, +static int do_merge(struct repository *r, + struct commit *commit, + const char *arg, int arg_len, int flags, struct replay_opts *opts) { int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ? @@ -3058,7 +3103,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, 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; } @@ -3111,7 +3156,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, ret = error(_("octopus merge cannot be executed on " "top of a [new root]")); else - ret = fast_forward_to(&to_merge->item->object.oid, + ret = fast_forward_to(r, &to_merge->item->object.oid, &head_commit->object.oid, 0, opts); goto leave_merge; @@ -3130,11 +3175,11 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, write_author_script(message); find_commit_subject(message, &body); len = strlen(body); - ret = write_message(body, len, git_path_merge_msg(the_repository), 0); + ret = write_message(body, len, git_path_merge_msg(r), 0); unuse_commit_buffer(commit, message); if (ret) { error_errno(_("could not write '%s'"), - git_path_merge_msg(the_repository)); + git_path_merge_msg(r)); goto leave_merge; } } else { @@ -3156,11 +3201,11 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, len = buf.len; } - ret = write_message(p, len, git_path_merge_msg(the_repository), 0); + ret = write_message(p, len, git_path_merge_msg(r), 0); strbuf_release(&buf); if (ret) { error_errno(_("could not write '%s'"), - git_path_merge_msg(the_repository)); + git_path_merge_msg(r)); goto leave_merge; } } @@ -3196,7 +3241,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, if (can_fast_forward) { rollback_lock_file(&lock); - ret = fast_forward_to(&commit->object.oid, + ret = fast_forward_to(r, &commit->object.oid, &head_commit->object.oid, 0, opts); goto leave_merge; } @@ -3221,7 +3266,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, argv_array_push(&cmd.args, "--no-log"); argv_array_push(&cmd.args, "--no-stat"); argv_array_push(&cmd.args, "-F"); - argv_array_push(&cmd.args, git_path_merge_msg(the_repository)); + argv_array_push(&cmd.args, git_path_merge_msg(r)); if (opts->gpg_sign) argv_array_push(&cmd.args, opts->gpg_sign); @@ -3231,14 +3276,15 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, oid_to_hex(&j->item->object.oid)); strbuf_release(&ref_name); - unlink(git_path_cherry_pick_head(the_repository)); + unlink(git_path_cherry_pick_head(r)); rollback_lock_file(&lock); rollback_lock_file(&lock); ret = run_command(&cmd); /* force re-reading of the cache */ - if (!ret && (discard_cache() < 0 || read_cache() < 0)) + if (!ret && (discard_index(r->index) < 0 || + repo_read_index(r) < 0)) ret = error(_("could not read index")); goto leave_merge; } @@ -3253,15 +3299,15 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, } write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, - git_path_merge_head(the_repository), 0); - write_message("no-ff", 5, git_path_merge_mode(the_repository), 0); + git_path_merge_head(r), 0); + write_message("no-ff", 5, git_path_merge_mode(r), 0); for (j = bases; j; j = j->next) commit_list_insert(j->item, &reversed); free_commit_list(bases); - read_cache(); - 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; @@ -3285,23 +3331,23 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, */ ret = !ret; - if (active_cache_changed && - write_locked_index(&the_index, &lock, COMMIT_LOCK)) { + if (r->index->cache_changed && + write_locked_index(r->index, &lock, COMMIT_LOCK)) { ret = error(_("merge: Unable to write new index file")); goto leave_merge; } rollback_lock_file(&lock); if (ret) - repo_rerere(the_repository, opts->allow_rerere_auto); + repo_rerere(r, opts->allow_rerere_auto); else /* * In case of problems, we now want to return a positive * value (a negative one would indicate that the `merge` * command needs to be rescheduled). */ - ret = !!run_git_commit(git_path_merge_msg(the_repository), opts, - run_commit_flags); + ret = !!run_git_commit(r, git_path_merge_msg(r), opts, + run_commit_flags); leave_merge: strbuf_release(&ref_name); @@ -3449,14 +3495,14 @@ static int checkout_onto(struct replay_opts *opts, return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); } -static int stopped_at_head(void) +static int stopped_at_head(struct repository *r) { struct object_id head; struct commit *commit; struct commit_message message; if (get_oid("HEAD", &head) || - !(commit = lookup_commit(the_repository, &head)) || + !(commit = lookup_commit(r, &head)) || parse_commit(commit) || get_message(commit, &message)) fprintf(stderr, _("Stopped at HEAD\n")); else { @@ -3478,7 +3524,9 @@ N_("Could not execute the todo command\n" " git rebase --edit-todo\n" " git rebase --continue\n"); -static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct repository *r, + struct todo_list *todo_list, + struct replay_opts *opts) { int res = 0, reschedule = 0; @@ -3486,11 +3534,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); - if (read_and_refresh_cache(opts)) + if (read_and_refresh_cache(r, opts)) return -1; 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)) { @@ -3503,10 +3553,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) 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()); @@ -3516,14 +3567,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); if (item->command == TODO_BREAK) - return stopped_at_head(); + return stopped_at_head(r); } if (item->command <= TODO_SQUASH) { if (is_rebase_i(opts)) setenv("GIT_REFLOG_ACTION", reflog_message(opts, command_to_string(item->command), NULL), 1); - res = do_pick_commit(item->command, item->commit, + res = do_pick_commit(r, item->command, item->commit, opts, is_final_fixup(todo_list)); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ @@ -3542,10 +3593,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) fprintf(stderr, _("Stopped at %s... %.*s\n"), short_commit_name(commit), - item->arg_len, item->arg); - return error_with_patch(commit, - item->arg, item->arg_len, opts, res, - !res); + item->arg_len, arg); + return error_with_patch(r, commit, + arg, item->arg_len, opts, res, !res); } if (is_rebase_i(opts) && !res) record_in_rewritten(&item->commit->object.oid, @@ -3553,8 +3603,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (res && is_fixup(item->command)) { if (res == 1) intend_to_amend(); - return error_failed_squash(item->commit, opts, - item->arg_len, item->arg); + return error_failed_squash(r, item->commit, opts, + item->arg_len, arg); } else if (res && is_rebase_i(opts) && item->commit) { int to_amend = 0; struct object_id oid; @@ -3572,41 +3622,42 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) oideq(&opts->squash_onto, &oid)))) to_amend = 1; - return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, + return res | error_with_patch(r, item->commit, + 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(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)) { + /* Reread the todo file if it has changed. */ todo_list_release(todo_list); - if (read_populate_todo(todo_list, opts)) + if (read_populate_todo(r, todo_list, opts)) res = -1; /* message was printed */ /* `current` will be incremented below */ todo_list->current = -1; } } else if (item->command == TODO_LABEL) { - if ((res = do_label(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(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(item->commit, - item->arg, item->arg_len, + if ((res = do_merge(r, item->commit, + arg, item->arg_len, item->flags, opts)) < 0) reschedule = 1; else if (item->commit) @@ -3614,10 +3665,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) peek_command(todo_list, 1)); if (res > 0) /* failed with merge conflicts */ - return error_with_patch(item->commit, - item->arg, - item->arg_len, opts, - res, 0); + return error_with_patch(r, item->commit, + arg, item->arg_len, + opts, res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); @@ -3630,10 +3680,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (item->commit) - return error_with_patch(item->commit, - item->arg, - item->arg_len, opts, - res, 0); + return error_with_patch(r, + item->commit, + arg, item->arg_len, + opts, res, 0); } todo_list->current++; @@ -3695,7 +3745,7 @@ cleanup_head_ref: struct object_id orig, head; memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - repo_init_revisions(the_repository, &log_tree_opt, NULL); + repo_init_revisions(r, &log_tree_opt, NULL); log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; @@ -3730,6 +3780,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 */ @@ -3738,8 +3789,10 @@ cleanup_head_ref: } 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); @@ -3752,26 +3805,27 @@ cleanup_head_ref: return sequencer_remove_state(opts); } -static int continue_single_pick(void) +static int continue_single_pick(struct repository *r) { const char *argv[] = { "commit", NULL }; - if (!file_exists(git_path_cherry_pick_head(the_repository)) && - !file_exists(git_path_revert_head(the_repository))) + if (!file_exists(git_path_cherry_pick_head(r)) && + !file_exists(git_path_revert_head(r))) return error(_("no cherry-pick or revert in progress")); return run_command_v_opt(argv, RUN_GIT_CMD); } -static int commit_staged_changes(struct replay_opts *opts, +static int commit_staged_changes(struct repository *r, + struct replay_opts *opts, struct todo_list *todo_list) { unsigned int flags = ALLOW_EMPTY | EDIT_MSG; unsigned int final_fixup = 0, is_clean; - if (has_unstaged_changes(1)) + if (has_unstaged_changes(r, 1)) return error(_("cannot rebase: You have unstaged changes.")); - is_clean = !has_uncommitted_changes(0); + is_clean = !has_uncommitted_changes(r, 0); if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; @@ -3855,7 +3909,7 @@ static int commit_staged_changes(struct replay_opts *opts, struct commit *commit; const char *path = rebase_path_squash_msg(); - if (parse_head(&commit) || + if (parse_head(r, &commit) || !(p = get_commit_buffer(commit, NULL)) || write_message(p, strlen(p), path, 0)) { unuse_commit_buffer(commit, p); @@ -3871,7 +3925,7 @@ static int commit_staged_changes(struct replay_opts *opts, } if (is_clean) { - const char *cherry_pick_head = git_path_cherry_pick_head(the_repository); + const char *cherry_pick_head = git_path_cherry_pick_head(r); if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) return error(_("could not remove CHERRY_PICK_HEAD")); @@ -3879,7 +3933,7 @@ static int commit_staged_changes(struct replay_opts *opts, return 0; } - if (run_git_commit(final_fixup ? NULL : rebase_path_message(), + if (run_git_commit(r, final_fixup ? NULL : rebase_path_message(), opts, flags)) return error(_("could not commit staged changes.")); unlink(rebase_path_amend()); @@ -3900,36 +3954,36 @@ static int commit_staged_changes(struct replay_opts *opts, return 0; } -int sequencer_continue(struct replay_opts *opts) +int sequencer_continue(struct repository *r, struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; int res; - if (read_and_refresh_cache(opts)) + if (read_and_refresh_cache(r, opts)) return -1; if (read_populate_opts(opts)) return -1; if (is_rebase_i(opts)) { - if ((res = read_populate_todo(&todo_list, opts))) + if ((res = read_populate_todo(r, &todo_list, opts))) goto release_todo_list; - if (commit_staged_changes(opts, &todo_list)) + if (commit_staged_changes(r, opts, &todo_list)) return -1; } else if (!file_exists(get_todo_path(opts))) - return continue_single_pick(); - else if ((res = read_populate_todo(&todo_list, opts))) + return continue_single_pick(r); + else if ((res = read_populate_todo(r, &todo_list, opts))) goto release_todo_list; if (!is_rebase_i(opts)) { /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head(the_repository)) || - file_exists(git_path_revert_head(the_repository))) { - res = continue_single_pick(); + if (file_exists(git_path_cherry_pick_head(r)) || + file_exists(git_path_revert_head(r))) { + res = continue_single_pick(r); if (res) goto release_todo_list; } - if (index_differs_from("HEAD", NULL, 0)) { - res = error_dirty_index(opts); + if (index_differs_from(r, "HEAD", NULL, 0)) { + res = error_dirty_index(r, opts); goto release_todo_list; } todo_list.current++; @@ -3943,27 +3997,30 @@ int sequencer_continue(struct replay_opts *opts) strbuf_release(&buf); } - res = pick_commits(&todo_list, opts); + res = pick_commits(r, &todo_list, opts); release_todo_list: todo_list_release(&todo_list); return res; } -static int single_pick(struct commit *cmit, struct replay_opts *opts) +static int single_pick(struct repository *r, + struct commit *cmit, + struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(opts->action == REPLAY_PICK ? + return do_pick_commit(r, opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT, cmit, opts, 0); } -int sequencer_pick_revisions(struct replay_opts *opts) +int sequencer_pick_revisions(struct repository *r, + struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; struct object_id oid; int i, res; assert(opts->revs); - if (read_and_refresh_cache(opts)) + if (read_and_refresh_cache(r, opts)) return -1; for (i = 0; i < opts->revs->pending.nr; i++) { @@ -3975,8 +4032,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) continue; if (!get_oid(name, &oid)) { - if (!lookup_commit_reference_gently(the_repository, &oid, 1)) { - enum object_type type = oid_object_info(the_repository, + if (!lookup_commit_reference_gently(r, &oid, 1)) { + enum object_type type = oid_object_info(r, &oid, NULL); return error(_("%s: can't cherry-pick a %s"), @@ -4005,7 +4062,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) return error(_("empty commit set passed")); if (get_revision(opts->revs)) BUG("unexpected extra commit from walk"); - return single_pick(cmit, opts); + return single_pick(r, cmit, opts); } /* @@ -4024,7 +4081,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (save_opts(opts)) return -1; update_abort_safety_file(); - res = pick_commits(&todo_list, opts); + res = pick_commits(r, &todo_list, opts); todo_list_release(&todo_list); return res; } @@ -4036,8 +4093,7 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag) 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) @@ -4204,7 +4260,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; @@ -4349,7 +4405,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; @@ -4359,9 +4415,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)) { @@ -4374,8 +4430,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; @@ -4388,12 +4444,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); } } @@ -4402,11 +4458,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); } @@ -4428,19 +4484,18 @@ static int make_script_with_merges(struct pretty_print_context *pp, return 0; } -int sequencer_make_script(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; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; int rebase_merges = flags & TODO_LIST_REBASE_MERGES; - repo_init_revisions(the_repository, &revs, NULL); + repo_init_revisions(r, &revs, NULL); revs.verbose_header = 1; if (!rebase_merges) revs.max_parents = 1; @@ -4476,16 +4531,13 @@ int sequencer_make_script(FILE *out, int argc, const char **argv, 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; } @@ -4493,90 +4545,95 @@ int sequencer_make_script(FILE *out, int argc, const char **argv, * Add commands after pick and (series of) squash/fixup commands * in the todo list. */ -int sequencer_add_exec_commands(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(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 <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(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(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) { @@ -4586,181 +4643,92 @@ int transform_todos(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; + 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(void) -{ - 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(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(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 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(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)); } @@ -4769,143 +4737,109 @@ static int skip_unnecessary_picks(struct object_id *output_oid) 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 replay_opts *opts, unsigned flags, +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()) + if (autosquash && todo_list_rearrange_squash(todo_list)) return -1; - if (cmd && *cmd) - sequencer_add_exec_commands(cmd); - - if (strbuf_read_file(buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (parse_insn_buffer(buf->buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } + if (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(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()) { + 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(flags & ~(TODO_LIST_SHORTEN_IDS))) - return error(_("could not transform the todo list")); - - if (opts->allow_ff && skip_unnecessary_picks(&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; - if (require_clean_work_tree("rebase", "", 1, 1)) + if (require_clean_work_tree(r, "rebase", "", 1, 1)) return -1; - return sequencer_continue(opts); + return sequencer_continue(r, opts); } struct subject2item_entry { @@ -4932,21 +4866,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(void) +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(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); /* @@ -4959,13 +4885,13 @@ int rearrange_squash(void) * 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; @@ -4978,7 +4904,6 @@ int rearrange_squash(void) } if (is_fixup(item->command)) { - todo_list_release(&todo_list); clear_commit_todo_item(&commit_todo); return error(_("the script was already rearranged.")); } @@ -5013,7 +4938,7 @@ int rearrange_squash(void) *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++) @@ -5026,7 +4951,7 @@ int rearrange_squash(void) } 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) @@ -5044,10 +4969,8 @@ int rearrange_squash(void) } 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; /* @@ -5058,37 +4981,26 @@ int rearrange_squash(void) 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; } |