diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 2045 |
1 files changed, 1134 insertions, 911 deletions
diff --git a/sequencer.c b/sequencer.c index e1a4dd15f1..e528225e78 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,9 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") -static GIT_PATH_FUNC(rebase_path_todo_backup, - "rebase-merge/git-rebase-todo.backup") +GIT_PATH_FUNC(rebase_path_todo_backup, "rebase-merge/git-rebase-todo.backup") + +GIT_PATH_FUNC(rebase_path_dropped, "rebase-merge/dropped") /* * The rebase command lines that have already been processed. A line @@ -132,7 +133,7 @@ static GIT_PATH_FUNC(rebase_path_rewritten_pending, "rebase-merge/rewritten-pending") /* - * The path of the file containig the OID of the "squash onto" commit, i.e. + * The path of the file containing the OID of the "squash onto" commit, i.e. * the dummy commit used for `reset [new root]`. */ static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto") @@ -150,6 +151,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 +159,9 @@ 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 GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") static int git_sequencer_config(const char *k, const char *v, void *cb) { @@ -171,17 +175,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; @@ -274,7 +283,7 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts) int sequencer_remove_state(struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; - int i; + int i, ret = 0; if (is_rebase_i(opts) && strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) { @@ -283,8 +292,10 @@ int sequencer_remove_state(struct replay_opts *opts) char *eol = strchr(p, '\n'); if (eol) *eol = '\0'; - if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) + if (delete_ref("(rebase) cleanup", p, NULL, 0) < 0) { warning(_("could not delete '%s'"), p); + ret = -1; + } if (!eol) break; p = eol + 1; @@ -300,10 +311,11 @@ int sequencer_remove_state(struct replay_opts *opts) strbuf_reset(&buf); strbuf_addstr(&buf, get_dir(opts)); - remove_dir_recursively(&buf, 0); + if (remove_dir_recursively(&buf, 0)) + ret = error(_("could not remove '%s'"), buf.buf); strbuf_release(&buf); - return 0; + return ret; } static const char *action_name(const struct replay_opts *opts) @@ -314,7 +326,7 @@ static const char *action_name(const struct replay_opts *opts) case REPLAY_PICK: return N_("cherry-pick"); case REPLAY_INTERACTIVE_REBASE: - return N_("rebase -i"); + return N_("rebase"); } die(_("unknown action: %d"), opts->action); } @@ -356,7 +368,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 +380,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 +395,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 +453,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 +485,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,40 +522,85 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f return 0; } -void append_conflicts_hint(struct strbuf *msgbuf) +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, 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 < 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) { struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; + struct tree *next_tree, *base_tree, *head_tree; int clean; - char **xopt; + int i; 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,62 +609,63 @@ 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); + for (i = 0; i < opts->xopts_nr; i++) + parse_merge_opt(&o, opts->xopts[i]); clean = merge_trees(&o, head_tree, - next_tree, base_tree, &result); + next_tree, base_tree); if (is_rebase_i(opts) && clean <= 0) fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); - diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0); if (clean < 0) { rollback_lock_file(&index_lock); 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 - * "rebase -i". + * "rebase". */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); if (!clean) - append_conflicts_hint(msgbuf); + append_conflicts_hint(r->index, msgbuf, + opts->default_msg_cleanup); 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 +678,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)); @@ -711,7 +773,7 @@ static int parse_key_value_squoted(char *buf, struct string_list *list) * GIT_AUTHOR_DATE='$author_date' * * where $author_name, $author_email and $author_date are quoted. We are strict - * with our parsing, as the file was meant to be eval'd in the old + * with our parsing, as the file was meant to be eval'd in the now-removed * git-am.sh/git-rebase--interactive.sh scripts, and thus if the file differs * from what this function expects, it is better to bail out than to do * something that the user does not expect. @@ -810,34 +872,6 @@ static char *get_author(const char *message) return NULL; } -/* Read author-script and return an ident line (author <email> timestamp) */ -static const char *read_author_ident(struct strbuf *buf) -{ - struct strbuf out = STRBUF_INIT; - char *name, *email, *date; - - if (read_author_script(rebase_path_author_script(), - &name, &email, &date, 0)) - return NULL; - - /* validate date since fmt_ident() will die() on bad value */ - if (parse_date(date, &out)){ - warning(_("invalid date format '%s' in '%s'"), - date, rebase_path_author_script()); - strbuf_release(&out); - return NULL; - } - - strbuf_reset(&out); - strbuf_addstr(&out, fmt_ident(name, email, date, 0)); - strbuf_swap(buf, &out); - strbuf_release(&out); - free(name); - free(email); - free(date); - return buf->buf; -} - static const char staged_changes_advice[] = N_("you have staged changes in your working tree\n" "If these changes are meant to be squashed into the previous commit, run:\n" @@ -888,52 +922,12 @@ 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; - const char *value; - - if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) { - struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; - const char *author = NULL; - struct object_id root_commit, *cache_tree_oid; - int res = 0; - - if (is_rebase_i(opts)) { - author = read_author_ident(&script); - if (!author) { - strbuf_release(&script); - return -1; - } - } - - if (!defmsg) - BUG("root commit without message"); - - if (!(cache_tree_oid = get_cache_tree_oid())) - res = -1; - - if (!res) - res = strbuf_read_file(&msg, defmsg, 0); - - if (res <= 0) - res = error_errno(_("could not read '%s'"), defmsg); - else - res = commit_tree(msg.buf, msg.len, cache_tree_oid, - NULL, &root_commit, author, - opts->gpg_sign); - - strbuf_release(&msg); - strbuf_release(&script); - if (!res) { - update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL, - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR); - res = update_ref(NULL, "HEAD", &root_commit, NULL, 0, - UPDATE_REFS_MSG_ON_ERR); - } - return res < 0 ? error(_("writing root commit")) : 0; - } cmd.git_cmd = 1; @@ -962,7 +956,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, 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)) @@ -1003,6 +997,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. @@ -1093,6 +1097,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 +1111,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,32 +1121,31 @@ 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; - const char *name; + int ret = 0; + const char *name, *arg1 = NULL, *arg2 = NULL; name = git_path_commit_editmsg(); 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_push(&hook_env, "GIT_EDITOR=:"); - if (commit) - ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name, - "commit", commit, NULL); - else - ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name, - "message", NULL); - if (ret) + if (commit) { + arg1 = "commit"; + arg2 = commit; + } else { + arg1 = "message"; + } + if (run_commit_hook(0, r->index_file, "prepare-commit-msg", name, + arg1, arg2, NULL)) ret = error(_("'prepare-commit-msg' hook failed")); - argv_array_clear(&hook_env); return ret; } @@ -1186,7 +1191,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 +1204,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 +1237,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 +1271,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 +1279,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,12 +1303,13 @@ 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) { struct object_id tree; - struct commit *current_head; + struct commit *current_head = NULL; struct commit_list *parents = NULL; struct commit_extra_header *extra = NULL; struct strbuf err = STRBUF_INIT; @@ -1311,7 +1319,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) { @@ -1336,24 +1344,41 @@ static int try_to_commit(struct strbuf *msg, const char *author, } parents = copy_commit_list(current_head->parents); extra = read_commit_extra_headers(current_head, exclude_gpgsig); - } else if (current_head) { + } else if (current_head && + (!(flags & CREATE_ROOT_COMMIT) || (flags & AMEND_MSG))) { 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; } - if (!(flags & ALLOW_EMPTY) && oideq(current_head ? - get_commit_tree_oid(current_head) : - the_hash_algo->empty_tree, &tree)) { - res = 1; /* run 'git commit' to display error message */ - goto out; + if (!(flags & ALLOW_EMPTY)) { + struct commit *first_parent = current_head; + + if (flags & AMEND_MSG) { + if (current_head->parents) { + first_parent = current_head->parents->item; + if (repo_parse_commit(r, first_parent)) { + res = error(_("could not parse HEAD commit")); + goto out; + } + } else { + first_parent = NULL; + } + } + if (oideq(first_parent + ? get_commit_tree_oid(first_parent) + : the_hash_algo->empty_tree, + &tree)) { + res = 1; /* run 'git commit' to display error message */ + goto out; + } } 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(), @@ -1366,8 +1391,13 @@ static int try_to_commit(struct strbuf *msg, const char *author, 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); @@ -1390,8 +1420,9 @@ static int try_to_commit(struct strbuf *msg, const char *author, goto out; } + run_commit_hook(0, r->index_file, "post-commit", NULL); if (flags & AMEND_MSG) - commit_post_rewrite(current_head, oid); + commit_post_rewrite(r, current_head, oid); out: free_commit_extra_headers(extra); @@ -1402,13 +1433,13 @@ 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; - if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) && - !(flags & CREATE_ROOT_COMMIT)) { + if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) { struct object_id oid; struct strbuf sb = STRBUF_INIT; @@ -1417,20 +1448,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; } @@ -1456,26 +1487,35 @@ static int is_original_commit_empty(struct commit *commit) } /* - * Do we run "git commit" with "--allow-empty"? + * Should empty commits be allowed? Return status: + * <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit) + * 0: Halt on empty commit + * 1: Allow empty commit + * 2: Drop empty commit */ -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; + int index_unchanged, originally_empty; /* - * Three cases: + * Four cases: * * (1) we do not allow empty at all and error out. * - * (2) we allow ones that were initially empty, but - * forbid the ones that become empty; + * (2) we allow ones that were initially empty, and + * just drop the ones that become empty * - * (3) we allow both. + * (3) we allow ones that were initially empty, but + * halt for the ones that become empty; + * + * (4) we allow both. */ 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) @@ -1484,41 +1524,17 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) if (opts->keep_redundant_commits) return 1; - empty_commit = is_original_commit_empty(commit); - if (empty_commit < 0) - return empty_commit; - if (!empty_commit) - return 0; - else + originally_empty = is_original_commit_empty(commit); + if (originally_empty < 0) + return originally_empty; + if (originally_empty) return 1; + else if (opts->drop_redundant_commits) + return 2; + else + return 0; } -/* - * 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,12 +1595,15 @@ 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; const char *message, *body; + const char *encoding = get_commit_output_encoding(); if (opts->current_fixup_count > 0) { struct strbuf header = STRBUF_INIT; @@ -1609,9 +1628,9 @@ 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))) + if (!(head_message = logmsg_reencode(head_commit, NULL, encoding))) return error(_("could not read HEAD's commit message")); find_commit_subject(head_message, &body); @@ -1632,7 +1651,7 @@ static int update_squash_messages(enum todo_command command, unuse_commit_buffer(head_commit, head_message); } - if (!(message = get_commit_buffer(commit, NULL))) + if (!(message = logmsg_reencode(commit, NULL, encoding))) return error(_("could not read commit message of %s"), oid_to_hex(&commit->object.oid)); find_commit_subject(message, &body); @@ -1670,7 +1689,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 +1715,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,18 +1729,21 @@ 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, int *check_todo) { 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; char *author = NULL; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res, unborn = 0, reword = 0, allow, drop_commit; if (opts->no_commit) { /* @@ -1728,7 +1752,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 +1765,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 +1790,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,11 +1809,11 @@ 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; - flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG; + reword = 1; msg_file = NULL; goto fast_forward_edit; } @@ -1843,9 +1871,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (command == TODO_REWORD) - flags |= EDIT_MSG | VERIFY_MSG; + reword = 1; 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 +1882,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 +1899,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,25 +1943,42 @@ 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); + drop_commit = 0; + allow = allow_empty(r, opts, commit); if (allow < 0) { res = allow; goto leave; - } else if (allow) + } else if (allow == 1) { flags |= ALLOW_EMPTY; - if (!opts->no_commit) { -fast_forward_edit: + } else if (allow == 2) { + drop_commit = 1; + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_merge_msg(r)); + fprintf(stderr, + _("dropping %s %s -- patch contents already upstream\n"), + oid_to_hex(&commit->object.oid), msg.subject); + } /* else allow == 0 and there's nothing special to do */ + if (!opts->no_commit && !drop_commit) { 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")); + *check_todo = !!(flags & EDIT_MSG); + if (!res && reword) { +fast_forward_edit: + res = run_git_commit(r, NULL, opts, EDIT_MSG | + VERIFY_MSG | AMEND_MSG | + (flags & ALLOW_EMPTY)); + *check_todo = 1; + } } + if (!res && final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); @@ -1965,18 +2010,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 +2035,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); @@ -2018,10 +2045,30 @@ static void todo_list_release(struct todo_list *todo_list) static struct todo_item *append_new_todo(struct todo_list *todo_list) { ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc); + todo_list->total_nr++; 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 is_command(enum todo_command command, const char **bol) +{ + const char *str = todo_command_info[command].str; + const char nick = todo_command_info[command].c; + const char *p = *bol + 1; + + return skip_prefix(*bol, str, bol) || + ((nick && **bol == nick) && + (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || !*p) && + (*bol = p)); +} + +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,18 +2082,13 @@ 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; } for (i = 0; i < TODO_COMMENT; i++) - if (skip_prefix(bol, todo_command_info[i].str, &bol)) { - item->command = i; - break; - } else if ((bol + 1 == eol || bol[1] == ' ') && - *bol == todo_command_info[i].c) { - bol++; + if (is_command(i, &bol)) { item->command = i; break; } @@ -2062,7 +2104,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 +2116,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 +2130,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; } @@ -2098,24 +2140,57 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) saved = *end_of_object_name; *end_of_object_name = '\0'; status = get_oid(bol, &commit_oid); + if (status < 0) + error(_("could not parse '%s'"), bol); /* return later */ *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 status; + + item->commit = lookup_commit_reference(r, &commit_oid); + return item->commit ? 0 : -1; +} + +int sequencer_get_last_command(struct repository *r, enum replay_action *action) +{ + const char *todo_file, *bol; + struct strbuf buf = STRBUF_INIT; + int ret = 0; + + todo_file = git_path_todo_file(); + if (strbuf_read_file(&buf, todo_file, 0) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return -1; + else + return error_errno("unable to open '%s'", todo_file); + } + bol = buf.buf + strspn(buf.buf, " \t\r\n"); + if (is_command(TODO_PICK, &bol) && (*bol == ' ' || *bol == '\t')) + *action = REPLAY_PICK; + else if (is_command(TODO_REVERT, &bol) && + (*bol == ' ' || *bol == '\t')) + *action = REPLAY_REVERT; + else + ret = -1; - item->commit = lookup_commit_reference(the_repository, &commit_oid); - return !item->commit; + strbuf_release(&buf); + + return ret; } -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 +2201,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 +2265,72 @@ 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 have_finished_the_last_pick(void) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + const char *todo_path = git_path_todo_file(); + int ret = 0; + + if (strbuf_read_file(&buf, todo_path, 0) < 0) { + if (errno == ENOENT) { + return 0; + } else { + error_errno("unable to open '%s'", todo_path); + return 0; + } + } + /* If there is only one line then we are done */ + eol = strchr(buf.buf, '\n'); + if (!eol || !eol[1]) + ret = 1; + + strbuf_release(&buf); + + return ret; +} + +void sequencer_post_commit_cleanup(struct repository *r, int verbose) +{ + struct replay_opts opts = REPLAY_OPTS_INIT; + int need_cleanup = 0; + + if (file_exists(git_path_cherry_pick_head(r))) { + if (!unlink(git_path_cherry_pick_head(r)) && verbose) + warning(_("cancelling a cherry picking in progress")); + opts.action = REPLAY_PICK; + need_cleanup = 1; + } + + if (file_exists(git_path_revert_head(r))) { + if (!unlink(git_path_revert_head(r)) && verbose) + warning(_("cancelling a revert in progress")); + opts.action = REPLAY_REVERT; + need_cleanup = 1; + } + + if (!need_cleanup) + return; + + if (!have_finished_the_last_pick()) + return; + + sequencer_remove_state(&opts); +} + +static void todo_list_write_total_nr(struct todo_list *todo_list) +{ + FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); + + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } +} + +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 +2345,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 " @@ -2231,10 +2373,9 @@ static int read_populate_todo(struct todo_list *todo_list, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; - 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; @@ -2243,10 +2384,7 @@ static int read_populate_todo(struct todo_list *todo_list, + count_commands(todo_list); todo_list_release(&done); - if (f) { - fprintf(f, "%d\n", todo_list->total_nr); - fclose(f); - } + todo_list_write_total_nr(todo_list); } return 0; @@ -2273,6 +2411,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")) @@ -2292,7 +2439,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) @@ -2357,11 +2507,23 @@ 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; + + if (file_exists(rebase_path_drop_redundant_commits())) + opts->drop_redundant_commits = 1; + + if (file_exists(rebase_path_keep_redundant_commits())) + opts->keep_redundant_commits = 1; + read_strategy_opts(opts, &buf); strbuf_release(&buf); @@ -2411,22 +2573,18 @@ static void write_strategy_opts(struct replay_opts *opts) } int write_basic_state(struct replay_opts *opts, const char *head_name, - const char *onto, const char *orig_head) + struct commit *onto, const char *orig_head) { - const char *quiet = getenv("GIT_QUIET"); - if (head_name) write_file(rebase_path_head_name(), "%s\n", head_name); if (onto) - write_file(rebase_path_onto(), "%s\n", onto); + write_file(rebase_path_onto(), "%s\n", + oid_to_hex(&onto->object.oid)); if (orig_head) write_file(rebase_path_orig_head(), "%s\n", orig_head); - if (quiet) - write_file(rebase_path_quiet(), "%s\n", quiet); - else - write_file(rebase_path_quiet(), "\n"); - + if (opts->quiet) + write_file(rebase_path_quiet(), "%s", ""); if (opts->verbose) write_file(rebase_path_verbose(), "%s", ""); if (opts->strategy) @@ -2443,6 +2601,12 @@ 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->drop_redundant_commits) + write_file(rebase_path_drop_redundant_commits(), "%s", ""); + if (opts->keep_redundant_commits) + write_file(rebase_path_keep_redundant_commits(), "%s", ""); + if (opts->reschedule_failed_exec) + write_file(rebase_path_reschedule_failed_exec(), "%s", ""); return 0; } @@ -2453,20 +2617,23 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; const char *command_string = todo_command_info[command].str; + const char *encoding; struct commit *commit; if (prepare_revs(opts)) return -1; + encoding = get_log_output_encoding(); + while ((commit = get_revision(opts->revs))) { struct todo_item *item = append_new_todo(todo_list); - const char *commit_buffer = get_commit_buffer(commit, NULL); + const char *commit_buffer = logmsg_reencode(commit, NULL, encoding); const char *subject; int subject_len; 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); @@ -2481,15 +2648,41 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, return 0; } -static int create_seq_dir(void) +static int create_seq_dir(struct repository *r) { - if (file_exists(git_path_seq_dir())) { - error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + enum replay_action action; + const char *in_progress_error = NULL; + const char *in_progress_advice = NULL; + unsigned int advise_skip = file_exists(git_path_revert_head(r)) || + file_exists(git_path_cherry_pick_head(r)); + + if (!sequencer_get_last_command(r, &action)) { + switch (action) { + case REPLAY_REVERT: + in_progress_error = _("revert is already in progress"); + in_progress_advice = + _("try \"git revert (--continue | %s--abort | --quit)\""); + break; + case REPLAY_PICK: + in_progress_error = _("cherry-pick is already in progress"); + in_progress_advice = + _("try \"git cherry-pick (--continue | %s--abort | --quit)\""); + break; + default: + BUG("unexpected action in create_seq_dir"); + } + } + if (in_progress_error) { + error("%s", in_progress_error); + if (advice_sequencer_in_use) + advise(in_progress_advice, + advise_skip ? "--skip | " : ""); return -1; - } else if (mkdir(git_path_seq_dir(), 0777) < 0) + } + if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); + return 0; } @@ -2540,32 +2733,46 @@ static int rollback_is_safe(void) return oideq(&actual_head, &expected_head); } -static int reset_for_rollback(const struct object_id *oid) +static int reset_merge(const struct object_id *oid) { - const char *argv[4]; /* reset --merge <arg> + NULL */ + int ret; + struct argv_array argv = ARGV_ARRAY_INIT; - argv[0] = "reset"; - argv[1] = "--merge"; - argv[2] = oid_to_hex(oid); - argv[3] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); + argv_array_pushl(&argv, "reset", "--merge", NULL); + + if (!is_null_oid(oid)) + argv_array_push(&argv, oid_to_hex(oid)); + + ret = run_command_v_opt(argv.argv, RUN_GIT_CMD); + argv_array_clear(&argv); + + return ret; } -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")); if (is_null_oid(&head_oid)) return error(_("cannot abort from a branch yet to be born")); - return reset_for_rollback(&head_oid); + return reset_merge(&head_oid); +} + +static int skip_single_pick(void) +{ + struct object_id head; + + if (read_ref_full("HEAD", 0, &head, NULL)) + return error(_("cannot resolve HEAD")); + return reset_merge(&head); } -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 +2786,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()); @@ -2605,7 +2812,7 @@ int sequencer_rollback(struct replay_opts *opts) warning(_("You seem to have moved HEAD. " "Not rewinding, check your HEAD!")); } else - if (reset_for_rollback(&oid)) + if (reset_merge(&oid)) goto fail; strbuf_release(&buf); return sequencer_remove_state(opts); @@ -2614,6 +2821,70 @@ fail: return -1; } +int sequencer_skip(struct repository *r, struct replay_opts *opts) +{ + enum replay_action action = -1; + sequencer_get_last_command(r, &action); + + /* + * Check whether the subcommand requested to skip the commit is actually + * in progress and that it's safe to skip the commit. + * + * opts->action tells us which subcommand requested to skip the commit. + * If the corresponding .git/<ACTION>_HEAD exists, we know that the + * action is in progress and we can skip the commit. + * + * Otherwise we check that the last instruction was related to the + * particular subcommand we're trying to execute and barf if that's not + * the case. + * + * Finally we check that the rollback is "safe", i.e., has the HEAD + * moved? In this case, it doesn't make sense to "reset the merge" and + * "skip the commit" as the user already handled this by committing. But + * we'd not want to barf here, instead give advice on how to proceed. We + * only need to check that when .git/<ACTION>_HEAD doesn't exist because + * it gets removed when the user commits, so if it still exists we're + * sure the user can't have committed before. + */ + switch (opts->action) { + case REPLAY_REVERT: + if (!file_exists(git_path_revert_head(r))) { + if (action != REPLAY_REVERT) + return error(_("no revert in progress")); + if (!rollback_is_safe()) + goto give_advice; + } + break; + case REPLAY_PICK: + if (!file_exists(git_path_cherry_pick_head(r))) { + if (action != REPLAY_PICK) + return error(_("no cherry-pick in progress")); + if (!rollback_is_safe()) + goto give_advice; + } + break; + default: + BUG("unexpected action in sequencer_skip"); + } + + if (skip_single_pick()) + return error(_("failed to skip the commit")); + if (!is_directory(git_path_seq_dir())) + return 0; + + return sequencer_continue(r, opts); + +give_advice: + error(_("there is nothing to skip")); + + if (advice_resolve_conflict) { + advise(_("have you committed already?\n" + "try \"git %s --continue\""), + action == REPLAY_REVERT ? "revert" : "cherry-pick"); + } + return -1; +} + static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) { struct lock_file todo_lock = LOCK_INIT; @@ -2661,40 +2932,65 @@ 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; } -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 +3006,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; @@ -2728,7 +3024,8 @@ static int make_patch(struct commit *commit, struct replay_opts *opts) strbuf_addf(&buf, "%s/message", get_dir(opts)); if (!file_exists(buf.buf)) { - const char *commit_buffer = get_commit_buffer(commit, NULL); + const char *encoding = get_commit_output_encoding(); + const char *commit_buffer = logmsg_reencode(commit, NULL, encoding); find_commit_subject(commit_buffer, &subject); res |= write_message(subject, strlen(subject), buf.buf, 1); unuse_commit_buffer(commit, commit_buffer); @@ -2750,17 +3047,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 +3090,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 +3122,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 +3191,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; @@ -2902,7 +3204,7 @@ static int do_label(const char *name, int len) return error(_("illegal label name: '%.*s'"), len, name); strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); - strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name); + strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); transaction = ref_store_transaction_begin(refs, &err); if (!transaction) { @@ -2932,7 +3234,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 +3246,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,19 +3285,19 @@ 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))); } - if (!fill_tree_descriptor(&desc, &oid)) { + if (!fill_tree_descriptor(r, &desc, &oid)) { error(_("failed to find tree of %s"), oid_to_hex(&oid)); rollback_lock_file(&lock); free((void *)desc.buffer); @@ -3009,9 +3313,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 +3348,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) ? @@ -3053,12 +3359,15 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, struct commit *head_commit, *merge_commit, *i; struct commit_list *bases, *j, *reversed = NULL; struct commit_list *to_merge = NULL, **tail = &to_merge; + const char *strategy = !opts->xopts_nr && + (!opts->strategy || !strcmp(opts->strategy, "recursive")) ? + NULL : opts->strategy; struct merge_options o; int merge_arg_len, oneline_offset, can_fast_forward, ret, k; 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,14 +3420,15 @@ 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; } if (commit) { - const char *message = get_commit_buffer(commit, NULL); + const char *encoding = get_commit_output_encoding(); + const char *message = logmsg_reencode(commit, NULL, encoding); const char *body; int len; @@ -3130,11 +3440,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 +3466,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,12 +3506,16 @@ 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); + if (flags & TODO_EDIT_MERGE_MSG) { + run_commit_flags |= AMEND_MSG; + goto fast_forward_edit; + } goto leave_merge; } - if (to_merge->next) { + if (strategy || to_merge->next) { /* Octopus merge */ struct child_process cmd = CHILD_PROCESS_INIT; @@ -3215,13 +3529,20 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, cmd.git_cmd = 1; argv_array_push(&cmd.args, "merge"); argv_array_push(&cmd.args, "-s"); - argv_array_push(&cmd.args, "octopus"); + if (!strategy) + argv_array_push(&cmd.args, "octopus"); + else { + argv_array_push(&cmd.args, strategy); + for (k = 0; k < opts->xopts_nr; k++) + argv_array_pushf(&cmd.args, + "-X%s", opts->xopts[k]); + } argv_array_push(&cmd.args, "--no-edit"); argv_array_push(&cmd.args, "--no-ff"); 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 +3552,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; } @@ -3252,16 +3574,16 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, goto leave_merge; } - 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); + write_message(oid_to_hex(&merge_commit->object.oid), the_hash_algo->hexsz, + 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 +3607,24 @@ 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); + fast_forward_edit: + ret = !!run_git_commit(r, git_path_merge_msg(r), opts, + run_commit_flags); leave_merge: strbuf_release(&ref_name); @@ -3400,10 +3723,11 @@ static const char *reflog_message(struct replay_opts *opts, return buf.buf; } -static int run_git_checkout(struct replay_opts *opts, const char *commit, - const char *action) +static int run_git_checkout(struct repository *r, struct replay_opts *opts, + const char *commit, const char *action) { struct child_process cmd = CHILD_PROCESS_INIT; + int ret; cmd.git_cmd = 1; @@ -3412,26 +3736,18 @@ static int run_git_checkout(struct replay_opts *opts, const char *commit, argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action); if (opts->verbose) - return run_command(&cmd); + ret = run_command(&cmd); else - return run_command_silent_on_success(&cmd); -} + ret = run_command_silent_on_success(&cmd); -int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit) -{ - const char *action; - - if (commit && *commit) { - action = reflog_message(opts, "start", "checkout %s", commit); - if (run_git_checkout(opts, commit, action)) - return error(_("could not checkout %s"), commit); - } + if (!ret) + discard_index(r->index); - return 0; + return ret; } -static int checkout_onto(struct replay_opts *opts, - const char *onto_name, const char *onto, +static int checkout_onto(struct repository *r, struct replay_opts *opts, + const char *onto_name, const struct object_id *onto, const char *orig_head) { struct object_id oid; @@ -3440,7 +3756,7 @@ static int checkout_onto(struct replay_opts *opts, if (get_oid(orig_head, &oid)) return error(_("%s: not a valid OID"), orig_head); - if (run_git_checkout(opts, onto, action)) { + if (run_git_checkout(r, opts, oid_to_hex(onto), action)) { apply_autostash(opts); sequencer_remove_state(opts); return error(_("could not detach HEAD")); @@ -3449,14 +3765,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 +3794,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 +3804,14 @@ 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); + int check_todo = 0; + if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { @@ -3503,28 +3824,33 @@ 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()); unlink(rebase_path_stopped_sha()); unlink(rebase_path_amend()); - unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_head(r)); delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - if (item->command == TODO_BREAK) - return stopped_at_head(); + if (item->command == TODO_BREAK) { + if (!opts->verbose) + term_clear_line(); + 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, - opts, is_final_fixup(todo_list)); + res = do_pick_commit(r, item->command, item->commit, + opts, is_final_fixup(todo_list), + &check_todo); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ advise(_(rescheduled_advice), @@ -3538,14 +3864,16 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (item->command == TODO_EDIT) { struct commit *commit = item->commit; - if (!res) + if (!res) { + if (!opts->verbose) + term_clear_line(); 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 +3881,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 +3900,34 @@ 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; + if (!opts->verbose) + term_clear_line(); *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)) - res = error_errno(_("could not stat '%s'"), - get_todo_path(opts)); - else if (match_stat_data(&todo_list->stat, &st)) { - todo_list_release(todo_list); - if (read_populate_todo(todo_list, opts)) - res = -1; /* message was printed */ - /* `current` will be incremented below */ - todo_list->current = -1; + if (res) { + if (opts->reschedule_failed_exec) + reschedule = 1; } + check_todo = 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 +3935,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 +3950,24 @@ 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); + } else if (is_rebase_i(opts) && check_todo && !res) { + struct stat st; + + 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(r, todo_list, opts)) + res = -1; /* message was printed */ + /* `current` will be incremented below */ + todo_list->current = -1; + } } todo_list->current++; @@ -3695,7 +4029,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 +4064,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 +4073,13 @@ cleanup_head_ref: } apply_autostash(opts); - fprintf(stderr, "Successfully rebased and updated %s.\n", - head_ref.buf); + if (!opts->quiet) { + if (!opts->verbose) + term_clear_line(); + fprintf(stderr, + "Successfully rebased and updated %s.\n", + head_ref.buf); + } strbuf_release(&buf); strbuf_release(&head_ref); @@ -3752,26 +4092,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; @@ -3854,9 +4195,10 @@ static int commit_staged_changes(struct replay_opts *opts, */ struct commit *commit; const char *path = rebase_path_squash_msg(); + const char *encoding = get_commit_output_encoding(); - if (parse_head(&commit) || - !(p = get_commit_buffer(commit, NULL)) || + if (parse_head(r, &commit) || + !(p = logmsg_reencode(commit, NULL, encoding)) || write_message(p, strlen(p), path, 0)) { unuse_commit_buffer(commit, p); return error(_("could not write file: " @@ -3871,7 +4213,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,11 +4221,11 @@ 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()); - unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_head(r)); if (final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); @@ -3900,36 +4242,46 @@ 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)) - return -1; + + if (file_exists(rebase_path_dropped())) { + if ((res = todo_list_check_against_backup(r, &todo_list))) + goto release_todo_list; + + unlink(rebase_path_dropped()); + } + + if (commit_staged_changes(r, opts, &todo_list)) { + res = -1; + goto release_todo_list; + } } 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 +4295,33 @@ 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) { + int check_todo; + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts, 0); + return do_pick_commit(r, opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT, cmit, opts, 0, + &check_todo); } -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 +4333,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 +4363,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); } /* @@ -4015,7 +4373,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) */ if (walk_revs_populate_todo(&todo_list, opts) || - create_seq_dir() < 0) + create_seq_dir(r) < 0) return -1; if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT)) return error(_("can't revert as initial commit")); @@ -4024,7 +4382,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 +4394,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) @@ -4095,9 +4452,14 @@ struct labels_entry { char label[FLEX_ARRAY]; }; -static int labels_cmp(const void *fndata, const struct labels_entry *a, - const struct labels_entry *b, const void *key) +static int labels_cmp(const void *fndata, const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *key) { + const struct labels_entry *a, *b; + + a = container_of(eptr, const struct labels_entry, entry); + b = container_of(entry_or_key, const struct labels_entry, entry); + return key ? strcmp(a->label, key) : strcmp(a->label, b->label); } @@ -4118,7 +4480,6 @@ static const char *label_oid(struct object_id *oid, const char *label, struct labels_entry *labels_entry; struct string_entry *string_entry; struct object_id dummy; - size_t len; int i; string_entry = oidmap_get(&state->commit2label, oid); @@ -4138,11 +4499,11 @@ static const char *label_oid(struct object_id *oid, const char *label, * abbreviation for any uninteresting commit's names that does not * clash with any other label. */ + strbuf_reset(&state->buf); if (!label) { char *p; - strbuf_reset(&state->buf); - strbuf_grow(&state->buf, GIT_SHA1_HEXSZ); + strbuf_grow(&state->buf, GIT_MAX_HEXSZ); label = p = state->buf.buf; find_unique_abbrev_r(p, oid, default_abbrev); @@ -4155,7 +4516,7 @@ static const char *label_oid(struct object_id *oid, const char *label, size_t i = strlen(p) + 1; oid_to_hex_r(p, oid); - for (; i < GIT_SHA1_HEXSZ; i++) { + for (; i < the_hash_algo->hexsz; i++) { char save = p[i]; p[i] = '\0'; if (!hashmap_get_from_hash(&state->labels, @@ -4164,37 +4525,60 @@ static const char *label_oid(struct object_id *oid, const char *label, p[i] = save; } } - } else if (((len = strlen(label)) == the_hash_algo->hexsz && - !get_oid_hex(label, &dummy)) || - (len == 1 && *label == '#') || - hashmap_get_from_hash(&state->labels, - strihash(label), label)) { + } else { + struct strbuf *buf = &state->buf; + /* - * If the label already exists, or if the label is a valid full - * OID, or the label is a '#' (which we use as a separator - * between merge heads and oneline), we append a dash and a - * number to make it unique. + * Sanitize labels by replacing non-alpha-numeric characters + * (including white-space ones) by dashes, as they might be + * illegal in file names (and hence in ref names). + * + * Note that we retain non-ASCII UTF-8 characters (identified + * via the most significant bit). They should be all acceptable + * in file names. We do not validate the UTF-8 here, that's not + * the job of this function. */ - struct strbuf *buf = &state->buf; + for (; *label; label++) + if ((*label & 0x80) || isalnum(*label)) + strbuf_addch(buf, *label); + /* avoid leading dash and double-dashes */ + else if (buf->len && buf->buf[buf->len - 1] != '-') + strbuf_addch(buf, '-'); + if (!buf->len) { + strbuf_addstr(buf, "rev-"); + strbuf_add_unique_abbrev(buf, oid, default_abbrev); + } + label = buf->buf; - strbuf_reset(buf); - strbuf_add(buf, label, len); + if ((buf->len == the_hash_algo->hexsz && + !get_oid_hex(label, &dummy)) || + (buf->len == 1 && *label == '#') || + hashmap_get_from_hash(&state->labels, + strihash(label), label)) { + /* + * If the label already exists, or if the label is a + * valid full OID, or the label is a '#' (which we use + * as a separator between merge heads and oneline), we + * append a dash and a number to make it unique. + */ + size_t len = buf->len; - for (i = 2; ; i++) { - strbuf_setlen(buf, len); - strbuf_addf(buf, "-%d", i); - if (!hashmap_get_from_hash(&state->labels, - strihash(buf->buf), - buf->buf)) - break; - } + for (i = 2; ; i++) { + strbuf_setlen(buf, len); + strbuf_addf(buf, "-%d", i); + if (!hashmap_get_from_hash(&state->labels, + strihash(buf->buf), + buf->buf)) + break; + } - label = buf->buf; + label = buf->buf; + } } FLEX_ALLOC_STR(labels_entry, label, label); - hashmap_entry_init(labels_entry, strihash(label)); - hashmap_add(&state->labels, labels_entry); + hashmap_entry_init(&labels_entry->entry, strihash(label)); + hashmap_add(&state->labels, &labels_entry->entry); FLEX_ALLOC_STR(string_entry, string, label); oidcpy(&string_entry->entry.oid, oid); @@ -4204,11 +4588,11 @@ 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; int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS; + int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO; struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT; struct strbuf label = STRBUF_INIT; struct commit_list *commits = NULL, **tail = &commits, *iter; @@ -4228,14 +4612,19 @@ static int make_script_with_merges(struct pretty_print_context *pp, oidmap_init(&commit2todo, 0); oidmap_init(&state.commit2label, 0); - hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0); + hashmap_init(&state.labels, labels_cmp, NULL, 0); strbuf_init(&state.buf, 32); if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) { + struct labels_entry *onto_label_entry; struct object_id *oid = &revs->cmdline.rev[0].item->oid; FLEX_ALLOC_STR(entry, string, "onto"); oidcpy(&entry->entry.oid, oid); oidmap_put(&state.commit2label, entry); + + FLEX_ALLOC_STR(onto_label_entry, label, "onto"); + hashmap_entry_init(&onto_label_entry->entry, strihash("onto")); + hashmap_add(&state.labels, &onto_label_entry->entry); } /* @@ -4264,8 +4653,6 @@ static int make_script_with_merges(struct pretty_print_context *pp, if (!to_merge) { /* non-merge commit: easy case */ strbuf_reset(&buf); - if (!keep_empty && is_empty) - strbuf_addf(&buf, "%c ", comment_line_char); strbuf_addf(&buf, "%s %s %s", cmd_pick, oid_to_hex(&commit->object.oid), oneline.buf); @@ -4290,10 +4677,6 @@ static int make_script_with_merges(struct pretty_print_context *pp, else strbuf_addbuf(&label, &oneline); - for (p1 = label.buf; *p1; p1++) - if (isspace(*p1)) - *(char *)p1 = '-'; - strbuf_reset(&buf); strbuf_addf(&buf, "%s -C %s", cmd_merge, oid_to_hex(&commit->object.oid)); @@ -4336,7 +4719,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, label_oid(oid, "branch-point", &state); } - /* Add HEAD as implict "tip of branch" */ + /* Add HEAD as implicit "tip of branch" */ if (!iter->next) tips_tail = &commit_list_insert(iter->item, tips_tail)->next; @@ -4349,7 +4732,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 +4742,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 +4757,9 @@ 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 || root_with_onto ? + "onto" : "[new root]"); else { const char *to = NULL; @@ -4388,12 +4772,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 +4786,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); } @@ -4422,25 +4806,23 @@ static int make_script_with_merges(struct pretty_print_context *pp, oidmap_free(&commit2todo, 1); oidmap_free(&state.commit2label, 1); - hashmap_free(&state.labels, 1); + hashmap_free_entries(&state.labels, struct labels_entry, entry); strbuf_release(&state.buf); 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; @@ -4472,20 +4854,15 @@ int sequencer_make_script(FILE *out, int argc, const char **argv, return make_script_with_merges(&pp, &revs, out, flags); while ((commit = get_revision(&revs))) { - int is_empty = is_original_commit_empty(commit); + int is_empty = is_original_commit_empty(commit); 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, "%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 +4870,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); - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); + strbuf_addstr(buf, commands->items[i].string); + strbuf_addch(buf, '\n'); - if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%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 "); + + 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 immediately 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; - - 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(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) { @@ -4586,181 +4968,57 @@ 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) -{ - 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; } -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) +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) { - 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; - - init_commit_seen(&commit_seen); - - 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); - - if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) - goto leave_check; - - /* 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; - } - - todo_list_release(&todo_list); - strbuf_addstr(&todo_file, ".backup"); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 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; - } - } - - /* 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")); + int res; + struct strbuf buf = STRBUF_INIT; -leave_check: - clear_commit_seen(&commit_seen); - strbuf_release(&todo_file); - todo_list_release(&todo_list); + todo_list_to_strbuf(r, todo_list, &buf, num, flags); + if (flags & TODO_LIST_APPEND_TODO_HELP) + append_todo_help(count_commands(todo_list), + shortrevisions, shortonto, &buf); - 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 = write_message(buf.buf, buf.len, file, 0); + strbuf_release(&buf); 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 +5027,121 @@ 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->done_nr += i; - 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) + struct commit *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 object_id oid; - struct stat st; + struct todo_list new_todo = TODO_LIST_INIT; + struct strbuf *buf = &todo_list->buf, buf2 = STRBUF_INIT; + struct object_id oid = onto->object.oid; + 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 (commands->nr) + todo_list_add_exec_commands(todo_list, commands); - if (strbuf_read_file(buf, todo_file, 0) < 0) - return error_errno(_("could not read '%s'."), todo_file); - - if (parse_insn_buffer(buf->buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - if (count_commands(&todo_list) == 0) { + 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); + } else if (res == -4) { + checkout_onto(r, opts, onto_name, &onto->object.oid, orig_head); + todo_list_release(&new_todo); - if (check_todo_list()) { - checkout_onto(opts, onto_name, onto, orig_head); return -1; } - if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS))) - return error(_("could not transform the todo list")); + /* Expand the commit IDs */ + todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0); + strbuf_swap(&new_todo.buf, &buf2); + strbuf_release(&buf2); + new_todo.total_nr -= new_todo.nr; + if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0) + BUG("invalid todo list after expanding IDs:\n%s", + new_todo.buf.buf); - 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 (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head)) - return -1; + 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); + } - if (require_clean_work_tree("rebase", "", 1, 1)) - return -1; + res = -1; + + if (checkout_onto(r, opts, onto_name, &oid, orig_head)) + goto cleanup; + + if (require_clean_work_tree(r, "rebase", "", 1, 1)) + goto cleanup; + + todo_list_write_total_nr(&new_todo); + res = pick_commits(r, &new_todo, opts); - return sequencer_continue(opts); +cleanup: + todo_list_release(&new_todo); + + return res; } struct subject2item_entry { @@ -4915,9 +5151,15 @@ struct subject2item_entry { }; static int subject2item_cmp(const void *fndata, - const struct subject2item_entry *a, - const struct subject2item_entry *b, const void *key) + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *key) { + const struct subject2item_entry *a, *b; + + a = container_of(eptr, const struct subject2item_entry, entry); + b = container_of(entry_or_key, const struct subject2item_entry, entry); + return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); } @@ -4932,21 +5174,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); /* @@ -4958,14 +5192,13 @@ int rearrange_squash(void) * In that case, last[i] will indicate the index of the latest item to * 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++) { + hashmap_init(&subject2item, 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++) { 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 +5211,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.")); } @@ -4986,7 +5218,7 @@ int rearrange_squash(void) *commit_todo_item_at(&commit_todo, item->commit) = item; parse_commit(item->commit); - commit_buffer = get_commit_buffer(item->commit, NULL); + commit_buffer = logmsg_reencode(item->commit, NULL, "UTF-8"); find_commit_subject(commit_buffer, &subject); format_subject(&buf, subject, " "); subject = subjects[i] = strbuf_detach(&buf, &subject_len); @@ -5003,8 +5235,11 @@ int rearrange_squash(void) break; } - if ((entry = hashmap_get_from_hash(&subject2item, - strhash(p), p))) + entry = hashmap_get_entry_from_hash(&subject2item, + strhash(p), p, + struct subject2item_entry, + entry); + if (entry) /* found by title */ i2 = entry->i; else if (!strchr(p, ' ') && @@ -5013,7 +5248,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 +5261,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) @@ -5038,16 +5273,15 @@ int rearrange_squash(void) strhash(subject), subject)) { FLEX_ALLOC_MEM(entry, subject, subject, subject_len); entry->i = i; - hashmap_entry_init(entry, strhash(entry->subject)); - hashmap_put(&subject2item, entry); + hashmap_entry_init(&entry->entry, + strhash(entry->subject)); + hashmap_put(&subject2item, &entry->entry); } } 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 +5292,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); + hashmap_free_entries(&subject2item, struct subject2item_entry, entry); clear_commit_todo_item(&commit_todo); - return res; + + return 0; } |