diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 783 |
1 files changed, 675 insertions, 108 deletions
diff --git a/sequencer.c b/sequencer.c index c31d48b..a35a0e6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,10 +1,10 @@ #include "cache.h" #include "config.h" #include "lockfile.h" -#include "sequencer.h" #include "dir.h" #include "object.h" #include "commit.h" +#include "sequencer.h" #include "tag.h" #include "run-command.h" #include "exec_cmd.h" @@ -21,12 +21,16 @@ #include "log-tree.h" #include "wt-status.h" #include "hashmap.h" +#include "notes-utils.h" +#include "sigchain.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" 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") + GIT_PATH_FUNC(git_path_seq_dir, "sequencer") static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") @@ -130,6 +134,51 @@ 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 int git_sequencer_config(const char *k, const char *v, void *cb) +{ + struct replay_opts *opts = cb; + int status; + + if (!strcmp(k, "commit.cleanup")) { + const char *s; + + status = git_config_string(&s, k, v); + if (status) + return status; + + if (!strcmp(s, "verbatim")) + opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE; + else if (!strcmp(s, "whitespace")) + opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE; + 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 + warning(_("invalid commit message cleanup mode '%s'"), + s); + + return status; + } + + if (!strcmp(k, "commit.gpgsign")) { + opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL; + return 0; + } + + status = git_gpg_config(k, v, NULL); + if (status) + return status; + + return git_diff_basic_config(k, v, NULL); +} + +void sequencer_init_config(struct replay_opts *opts) +{ + opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE; + git_config(git_sequencer_config, opts); +} + static inline int is_rebase_i(const struct replay_opts *opts) { return opts->action == REPLAY_INTERACTIVE_REBASE; @@ -347,7 +396,7 @@ static int read_oneliner(struct strbuf *buf, static struct tree *empty_tree(void) { - return lookup_tree(&empty_tree_oid); + return lookup_tree(the_hash_algo->empty_tree); } static int error_dirty_index(struct replay_opts *opts) @@ -393,7 +442,7 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, "HEAD", - to->hash, unborn ? null_sha1 : from->hash, + to, unborn ? &null_oid : from, 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); @@ -438,7 +487,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, char **xopt; static struct lock_file index_lock; - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; read_cache(); @@ -448,6 +498,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.branch2 = next ? next_label : "(empty tree)"; if (is_rebase_i(opts)) o.buffer_output = 2; + o.show_rename_progress = 1; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -462,6 +513,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, 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) return clean; @@ -475,9 +527,6 @@ static int do_recursive_merge(struct commit *base, struct commit *next, _(action_name(opts))); rollback_lock_file(&index_lock); - if (opts->signoff) - append_signoff(msgbuf, 0, 0); - if (!clean) append_conflicts_hint(msgbuf); @@ -489,8 +538,8 @@ static int is_index_unchanged(void) struct object_id head_oid; struct commit *head_commit; - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) - return error(_("could not resolve HEAD commit\n")); + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) + return error(_("could not resolve HEAD commit")); head_commit = lookup_commit(&head_oid); @@ -510,7 +559,7 @@ static int is_index_unchanged(void) if (!cache_tree_fully_valid(active_cache_tree)) if (cache_tree_update(&the_index, 0)) - return error(_("unable to update cache tree\n")); + return error(_("unable to update cache tree")); return !oidcmp(&active_cache_tree->oid, &head_commit->tree->object.oid); @@ -593,6 +642,18 @@ static int read_env_script(struct argv_array *env) return 0; } +static char *get_author(const char *message) +{ + size_t len; + const char *a; + + a = find_commit_header(message, "author", &len); + if (a) + return xmemdupz(a, len); + + return NULL; +} + 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" @@ -655,8 +716,6 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); - if (opts->signoff) - argv_array_push(&cmd.args, "-s"); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); if ((flags & CLEANUP_MSG)) @@ -691,21 +750,476 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, return run_command(&cmd); } +static int rest_is_empty(const struct strbuf *sb, int start) +{ + int i, eol; + const char *nl; + + /* Check if the rest is just whitespace and Signed-off-by's. */ + for (i = start; i < sb->len; i++) { + nl = memchr(sb->buf + i, '\n', sb->len - i); + if (nl) + eol = nl - sb->buf; + else + eol = sb->len; + + if (strlen(sign_off_header) <= eol - i && + starts_with(sb->buf + i, sign_off_header)) { + i = eol; + continue; + } + while (i < eol) + if (!isspace(sb->buf[i++])) + return 0; + } + + return 1; +} + +/* + * Find out if the message in the strbuf contains only whitespace and + * Signed-off-by lines. + */ +int message_is_empty(const struct strbuf *sb, + enum commit_msg_cleanup_mode cleanup_mode) +{ + if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len) + return 0; + return rest_is_empty(sb, 0); +} + +/* + * See if the user edited the message in the editor or left what + * was in the template intact + */ +int template_untouched(const struct strbuf *sb, const char *template_file, + enum commit_msg_cleanup_mode cleanup_mode) +{ + struct strbuf tmpl = STRBUF_INIT; + const char *start; + + if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len) + return 0; + + if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) + return 0; + + strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); + if (!skip_prefix(sb->buf, tmpl.buf, &start)) + start = sb->buf; + strbuf_release(&tmpl); + return rest_is_empty(sb, start - sb->buf); +} + +int update_head_with_reflog(const struct commit *old_head, + const struct object_id *new_head, + const char *action, const struct strbuf *msg, + struct strbuf *err) +{ + struct ref_transaction *transaction; + struct strbuf sb = STRBUF_INIT; + const char *nl; + int ret = 0; + + if (action) { + strbuf_addstr(&sb, action); + strbuf_addstr(&sb, ": "); + } + + nl = strchr(msg->buf, '\n'); + if (nl) { + strbuf_add(&sb, msg->buf, nl + 1 - msg->buf); + } else { + strbuf_addbuf(&sb, msg); + strbuf_addch(&sb, '\n'); + } + + transaction = ref_transaction_begin(err); + if (!transaction || + ref_transaction_update(transaction, "HEAD", new_head, + old_head ? &old_head->object.oid : &null_oid, + 0, sb.buf, err) || + ref_transaction_commit(transaction, err)) { + ret = -1; + } + ref_transaction_free(transaction); + strbuf_release(&sb); + + return ret; +} + +static int run_rewrite_hook(const struct object_id *oldoid, + const struct object_id *newoid) +{ + struct child_process proc = CHILD_PROCESS_INIT; + const char *argv[3]; + int code; + struct strbuf sb = STRBUF_INIT; + + argv[0] = find_hook("post-rewrite"); + if (!argv[0]) + return 0; + + argv[1] = "amend"; + argv[2] = NULL; + + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + + code = start_command(&proc); + if (code) + return code; + strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); + sigchain_push(SIGPIPE, SIG_IGN); + write_in_full(proc.in, sb.buf, sb.len); + close(proc.in); + strbuf_release(&sb); + sigchain_pop(SIGPIPE); + return finish_command(&proc); +} + +void commit_post_rewrite(const struct commit *old_head, + const struct object_id *new_head) +{ + struct notes_rewrite_cfg *cfg; + + cfg = init_copy_notes_for_rewrite("amend"); + 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'"); + } + run_rewrite_hook(&old_head->object.oid, new_head); +} + +static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit) +{ + struct argv_array hook_env = ARGV_ARRAY_INIT; + int ret; + const char *name; + + 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) + ret = error(_("'prepare-commit-msg' hook failed")); + argv_array_clear(&hook_env); + + return ret; +} + +static const char implicit_ident_advice_noconfig[] = +N_("Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly. Run the\n" +"following command and follow the instructions in your editor to edit\n" +"your configuration file:\n" +"\n" +" git config --global --edit\n" +"\n" +"After doing this, you may fix the identity used for this commit with:\n" +"\n" +" git commit --amend --reset-author\n"); + +static const char implicit_ident_advice_config[] = +N_("Your name and email address were configured automatically based\n" +"on your username and hostname. Please check that they are accurate.\n" +"You can suppress this message by setting them explicitly:\n" +"\n" +" git config --global user.name \"Your Name\"\n" +" git config --global user.email you@example.com\n" +"\n" +"After doing this, you may fix the identity used for this commit with:\n" +"\n" +" git commit --amend --reset-author\n"); + +static const char *implicit_ident_advice(void) +{ + char *user_config = expand_user_path("~/.gitconfig", 0); + char *xdg_config = xdg_config_home("config"); + int config_exists = file_exists(user_config) || file_exists(xdg_config); + + free(user_config); + free(xdg_config); + + if (config_exists) + return _(implicit_ident_advice_config); + else + return _(implicit_ident_advice_noconfig); + +} + +void print_commit_summary(const char *prefix, const struct object_id *oid, + unsigned int flags) +{ + struct rev_info rev; + struct commit *commit; + struct strbuf format = STRBUF_INIT; + const char *head; + struct pretty_print_context pctx = {0}; + struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; + + commit = lookup_commit(oid); + if (!commit) + die(_("couldn't look up newly created commit")); + if (parse_commit(commit)) + die(_("could not parse newly created commit")); + + strbuf_addstr(&format, "format:%h] %s"); + + format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); + format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); + if (strbuf_cmp(&author_ident, &committer_ident)) { + strbuf_addstr(&format, "\n Author: "); + strbuf_addbuf_percentquote(&format, &author_ident); + } + if (flags & SUMMARY_SHOW_AUTHOR_DATE) { + struct strbuf date = STRBUF_INIT; + + format_commit_message(commit, "%ad", &date, &pctx); + strbuf_addstr(&format, "\n Date: "); + strbuf_addbuf_percentquote(&format, &date); + strbuf_release(&date); + } + if (!committer_ident_sufficiently_given()) { + strbuf_addstr(&format, "\n Committer: "); + strbuf_addbuf_percentquote(&format, &committer_ident); + if (advice_implicit_identity) { + strbuf_addch(&format, '\n'); + strbuf_addstr(&format, implicit_ident_advice()); + } + } + strbuf_release(&author_ident); + strbuf_release(&committer_ident); + + init_revisions(&rev, prefix); + setup_revisions(0, NULL, &rev, NULL); + + rev.diff = 1; + rev.diffopt.output_format = + DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; + + rev.verbose_header = 1; + rev.show_root_diff = 1; + get_commit_format(format.buf, &rev); + rev.always_show_header = 0; + rev.diffopt.detect_rename = DIFF_DETECT_RENAME; + rev.diffopt.break_opt = 0; + diff_setup_done(&rev.diffopt); + + head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + if (!head) + die_errno(_("unable to resolve HEAD after creating commit")); + if (!strcmp(head, "HEAD")) + head = _("detached HEAD"); + else + skip_prefix(head, "refs/heads/", &head); + printf("[%s%s ", head, (flags & SUMMARY_INITIAL_COMMIT) ? + _(" (root-commit)") : ""); + + if (!log_tree_commit(&rev, commit)) { + rev.always_show_header = 1; + rev.use_terminator = 1; + log_tree_commit(&rev, commit); + } + + strbuf_release(&format); +} + +static int parse_head(struct commit **head) +{ + struct commit *current_head; + struct object_id oid; + + if (get_oid("HEAD", &oid)) { + current_head = NULL; + } else { + current_head = lookup_commit_reference(&oid); + if (!current_head) + return error(_("could not parse HEAD")); + if (oidcmp(&oid, ¤t_head->object.oid)) { + warning(_("HEAD %s is not a commit!"), + oid_to_hex(&oid)); + } + if (parse_commit(current_head)) + return error(_("could not parse HEAD commit")); + } + *head = current_head; + + return 0; +} + +/* + * Try to commit without forking 'git commit'. In some cases we need + * to run 'git commit' to display an error message + * + * Returns: + * -1 - error unable to commit + * 0 - success + * 1 - run 'git commit' + */ +static int try_to_commit(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_list *parents = NULL; + struct commit_extra_header *extra = NULL; + struct strbuf err = STRBUF_INIT; + struct strbuf commit_msg = STRBUF_INIT; + char *amend_author = NULL; + const char *hook_commit = NULL; + enum commit_msg_cleanup_mode cleanup; + int res = 0; + + if (parse_head(¤t_head)) + return -1; + + if (flags & AMEND_MSG) { + const char *exclude_gpgsig[] = { "gpgsig", NULL }; + const char *out_enc = get_commit_output_encoding(); + const char *message = logmsg_reencode(current_head, NULL, + out_enc); + + if (!msg) { + const char *orig_message = NULL; + + find_commit_subject(message, &orig_message); + msg = &commit_msg; + strbuf_addstr(msg, orig_message); + hook_commit = "HEAD"; + } + author = amend_author = get_author(message); + unuse_commit_buffer(current_head, message); + if (!author) { + res = error(_("unable to parse commit author")); + goto out; + } + parents = copy_commit_list(current_head->parents); + extra = read_commit_extra_headers(current_head, exclude_gpgsig); + } else if (current_head) { + commit_list_insert(current_head, &parents); + } + + if (write_cache_as_tree(tree.hash, 0, NULL)) { + res = error(_("git write-tree failed to write a tree")); + goto out; + } + + if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ? + ¤t_head->tree->object.oid : + &empty_tree_oid, &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); + if (res) + goto out; + if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(), + 2048) < 0) { + res = error_errno(_("unable to read commit message " + "from '%s'"), + git_path_commit_editmsg()); + goto out; + } + msg = &commit_msg; + } + + cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL : + opts->default_msg_cleanup; + + if (cleanup != COMMIT_MSG_CLEANUP_NONE) + strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL); + if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) { + res = 1; /* run 'git commit' to display error message */ + goto out; + } + + if (commit_tree_extended(msg->buf, msg->len, &tree, parents, + oid, author, opts->gpg_sign, extra)) { + res = error(_("failed to write commit object")); + goto out; + } + + if (update_head_with_reflog(current_head, oid, + getenv("GIT_REFLOG_ACTION"), msg, &err)) { + res = error("%s", err.buf); + goto out; + } + + if (flags & AMEND_MSG) + commit_post_rewrite(current_head, oid); + +out: + free_commit_extra_headers(extra); + strbuf_release(&err); + strbuf_release(&commit_msg); + free(amend_author); + + return res; +} + +static int do_commit(const char *msg_file, const char *author, + struct replay_opts *opts, unsigned int flags) +{ + int res = 1; + + if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) { + struct object_id oid; + struct strbuf sb = STRBUF_INIT; + + if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0) + return error_errno(_("unable to read commit message " + "from '%s'"), + msg_file); + + res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags, + &oid); + strbuf_release(&sb); + if (!res) { + unlink(git_path_cherry_pick_head()); + unlink(git_path_merge_msg()); + if (!is_rebase_i(opts)) + print_commit_summary(NULL, &oid, + SUMMARY_SHOW_AUTHOR_DATE); + return res; + } + } + if (res == 1) + return run_git_commit(msg_file, opts, flags); + + return res; +} + static int is_original_commit_empty(struct commit *commit) { const struct object_id *ptree_oid; if (parse_commit(commit)) - return error(_("could not parse commit %s\n"), + return error(_("could not parse commit %s"), oid_to_hex(&commit->object.oid)); if (commit->parents) { struct commit *parent = commit->parents->item; if (parse_commit(parent)) - return error(_("could not parse parent commit %s\n"), + return error(_("could not parse parent commit %s"), oid_to_hex(&parent->object.oid)); ptree_oid = &parent->tree->object.oid; } else { - ptree_oid = &empty_tree_oid; /* commit is root */ + ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } return !oidcmp(ptree_oid, &commit->tree->object.oid); @@ -794,6 +1308,13 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static char command_to_char(const enum todo_command command) +{ + if (command < TODO_COMMENT && todo_command_info[command].c) + return todo_command_info[command].c; + return comment_line_char; +} + static int is_noop(const enum todo_command command) { return TODO_NOOP <= command; @@ -942,6 +1463,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, 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; @@ -958,8 +1480,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } else { unborn = get_oid("HEAD", &head); if (unborn) - oidcpy(&head, &empty_tree_oid); - if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0)) + oidcpy(&head, the_hash_algo->empty_tree); + if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", + NULL, 0)) return error_dirty_index(opts); } discard_cache(); @@ -1002,9 +1525,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, opts); if (res || command != TODO_REWORD) goto leave; - flags |= EDIT_MSG | AMEND_MSG; - if (command == TODO_REWORD) - flags |= VERIFY_MSG; + flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG; msg_file = NULL; goto fast_forward_edit; } @@ -1057,6 +1578,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid)); strbuf_addstr(&msgbuf, ")\n"); } + if (!is_fixup(command)) + author = get_author(msg.message); } if (command == TODO_REWORD) @@ -1082,6 +1605,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } + if (opts->signoff) + append_signoff(&msgbuf, 0, 0); + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) res = -1; else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { @@ -1115,12 +1641,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, * write it at all. */ if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) && - update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) && - update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; if (res) { @@ -1139,9 +1665,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } else if (allow) flags |= ALLOW_EMPTY; - if (!opts->no_commit) + if (!opts->no_commit) { fast_forward_edit: - res = run_git_commit(msg_file, opts, flags); + if (author || command == TODO_REVERT || (flags & AMEND_MSG)) + res = do_commit(msg_file, author, opts, flags); + else + res = error(_("unable to parse commit author")); + } if (!res && final_fixup) { unlink(rebase_path_fixup_msg()); @@ -1150,6 +1680,7 @@ fast_forward_edit: leave: free_message(commit, &msg); + free(author); update_abort_safety_file(); return res; @@ -1184,7 +1715,6 @@ static int read_and_refresh_cache(struct replay_opts *opts) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { - rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), _(action_name(opts))); } @@ -1253,20 +1783,26 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= TODO_COMMENT) return -1; + /* Eat up extra spaces/ tabs before object name */ + padding = strspn(bol, " \t"); + bol += padding; + if (item->command == TODO_NOOP) { + if (bol != eol) + return error(_("%s does not accept arguments: '%s'"), + command_to_string(item->command), bol); item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - /* Eat up extra spaces/ tabs before object name */ - padding = strspn(bol, " \t"); if (!padding) - return -1; - bol += padding; + return error(_("missing arguments for %s"), + command_to_string(item->command)); if (item->command == TODO_EXEC) { + item->commit = NULL; item->arg = bol; item->arg_len = (int)(eol - bol); return 0; @@ -1639,7 +2175,7 @@ static int rollback_single_pick(void) if (!file_exists(git_path_cherry_pick_head()) && !file_exists(git_path_revert_head())) return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", 0, head_oid.hash, NULL)) + 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")); @@ -1787,6 +2323,9 @@ static int make_patch(struct commit *commit, struct replay_opts *opts) p = short_commit_name(commit); if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) return -1; + if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid, + NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) + res |= error(_("could not update %s"), "REBASE_HEAD"); strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); @@ -1871,12 +2410,15 @@ static int error_failed_squash(struct commit *commit, static int do_exec(const char *command_line) { + struct argv_array child_env = ARGV_ARRAY_INIT; const char *child_argv[] = { NULL, NULL }; int dirty, status; fprintf(stderr, "Executing: %s\n", command_line); child_argv[0] = command_line; - status = run_command_v_opt(child_argv, RUN_USING_SHELL); + argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir())); + status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL, + child_env.argv); /* force re-reading of the cache */ if (discard_cache() < 0 || read_cache() < 0) @@ -1906,6 +2448,8 @@ static int do_exec(const char *command_line) status = 1; } + argv_array_clear(&child_env); + return status; } @@ -2033,6 +2577,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); unlink(rebase_path_amend()); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); } if (item->command <= TODO_SQUASH) { if (is_rebase_i(opts)) @@ -2133,8 +2678,8 @@ cleanup_head_ref: } msg = reflog_message(opts, "finish", "%s onto %s", head_ref.buf, buf.buf); - if (update_ref(msg, head_ref.buf, head.hash, orig.hash, - REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { + if (update_ref(msg, head_ref.buf, &head, &orig, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { res = error(_("could not update %s"), head_ref.buf); goto cleanup_head_ref; @@ -2288,7 +2833,7 @@ int sequencer_continue(struct replay_opts *opts) if (res) goto release_todo_list; } - if (index_differs_from("HEAD", 0, 0)) { + if (index_differs_from("HEAD", NULL, 0)) { res = error_dirty_index(opts); goto release_todo_list; } @@ -2446,14 +2991,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) strbuf_release(&sob); } -int sequencer_make_script(int keep_empty, FILE *out, - int argc, const char **argv) +int sequencer_make_script(FILE *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"; init_revisions(&revs, NULL); revs.verbose_header = 1; @@ -2486,7 +3033,8 @@ int sequencer_make_script(int keep_empty, FILE *out, strbuf_reset(&buf); if (!keep_empty && is_original_commit_empty(commit)) strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid)); + strbuf_addf(&buf, "%s %s ", insn, + oid_to_hex(&commit->object.oid)); pretty_print_commit(&pp, commit, &buf); strbuf_addch(&buf, '\n'); fputs(buf.buf, out); @@ -2495,61 +3043,93 @@ int sequencer_make_script(int keep_empty, FILE *out, return 0; } - -int transform_todo_ids(int shorten_ids) +/* + * Add commands after pick and (series of) squash/fixup commands + * in the todo list. + */ +int sequencer_add_exec_commands(const char *commands) { const char *todo_file = rebase_path_todo(); struct todo_list todo_list = TODO_LIST_INIT; - int fd, res, i; - FILE *out; + struct todo_item *item; + struct strbuf *buf = &todo_list.buf; + size_t offset = 0, commands_len = strlen(commands); + int i, first; - strbuf_reset(&todo_list.buf); - fd = open(todo_file, O_RDONLY); - if (fd < 0) - return error_errno(_("could not open '%s'"), todo_file); - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - } - close(fd); - res = parse_insn_buffer(todo_list.buf.buf, &todo_list); - if (res) { + if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } - out = fopen(todo_file, "w"); - if (!out) { + first = 1; + /* insert <commands> before every pick except the first one */ + for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + if (item->command == TODO_PICK && !first) { + strbuf_insert(buf, item->offset_in_buf + offset, + commands, commands_len); + offset += commands_len; + } + first = 0; + } + + /* append final <commands> */ + strbuf_add(buf, commands, commands_len); + + i = write_message(buf->buf, buf->len, todo_file, 0); + todo_list_release(&todo_list); + return i; +} + +int transform_todos(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); + + if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); - return error(_("unable to open '%s' for writing"), todo_file); + return error(_("unusable todo list: '%s'"), todo_file); } - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; - int bol = item->offset_in_buf; - const char *p = todo_list.buf.buf + bol; - int eol = i + 1 < todo_list.nr ? - todo_list.items[i + 1].offset_in_buf : - todo_list.buf.len; - - if (item->command >= TODO_EXEC && item->command != TODO_DROP) - fwrite(p, eol - bol, 1, out); - else { - const char *id = shorten_ids ? - short_commit_name(item->commit) : - oid_to_hex(&item->commit->object.oid); - int len; - - p += strspn(p, " \t"); /* left-trim command */ - len = strcspn(p, " \t"); /* length of command */ - - fprintf(out, "%.*s %s %.*s\n", - len, p, id, item->arg_len, item->arg); + + for (item = todo_list.items, i = 0; i < todo_list.nr; 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); + continue; + } + + /* add command to the buffer */ + if (flags & TODO_LIST_ABBREVIATE_CMDS) + strbuf_addch(&buf, command_to_char(item->command)); + else + strbuf_addstr(&buf, command_to_string(item->command)); + + /* add commit id */ + if (item->commit) { + const char *oid = flags & TODO_LIST_SHORTEN_IDS ? + short_commit_name(item->commit) : + oid_to_hex(&item->commit->object.oid); + + strbuf_addf(&buf, " %s", oid); } + /* add all the rest */ + if (!item->arg_len) + strbuf_addch(&buf, '\n'); + else + strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg); } - fclose(out); + + i = write_message(buf.buf, buf.len, todo_file, 0); todo_list_release(&todo_list); - return 0; + return i; } enum check_level { @@ -2660,6 +3240,19 @@ leave_check: 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 */ int skip_unnecessary_picks(void) { @@ -2725,29 +3318,11 @@ int skip_unnecessary_picks(void) } close(fd); - fd = open(rebase_path_todo(), O_WRONLY, 0666); - if (fd < 0) { - error_errno(_("could not open '%s' for writing"), - rebase_path_todo()); - todo_list_release(&todo_list); - return -1; - } - if (write_in_full(fd, todo_list.buf.buf + offset, - todo_list.buf.len - offset) < 0) { - error_errno(_("could not write to '%s'"), - rebase_path_todo()); - close(fd); - todo_list_release(&todo_list); - return -1; - } - if (ftruncate(fd, todo_list.buf.len - offset) < 0) { - error_errno(_("could not truncate '%s'"), - rebase_path_todo()); + if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset, + todo_list.buf.len - offset) < 0) { todo_list_release(&todo_list); - close(fd); return -1; } - close(fd); todo_list.current = i; if (is_fixup(peek_command(&todo_list, 0))) @@ -2926,15 +3501,7 @@ int rearrange_squash(void) } } - fd = open(todo_file, O_WRONLY); - if (fd < 0) - res = error_errno(_("could not open '%s'"), todo_file); - else if (write(fd, buf.buf, buf.len) < 0) - res = error_errno(_("could not write to '%s'"), todo_file); - else if (ftruncate(fd, buf.len) < 0) - res = error_errno(_("could not truncate '%s'"), - todo_file); - close(fd); + res = rewrite_file(todo_file, buf.buf, buf.len); strbuf_release(&buf); } |