diff options
Diffstat (limited to 'sequencer.c')
-rw-r--r-- | sequencer.c | 279 |
1 files changed, 212 insertions, 67 deletions
diff --git a/sequencer.c b/sequencer.c index e528225e78..fd7701c88a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -32,6 +32,7 @@ #include "alias.h" #include "commit-reach.h" #include "rebase-interactive.h" +#include "reset.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -40,7 +41,7 @@ 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_seq_dir, "sequencer") static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") @@ -419,25 +420,15 @@ static int write_message(const void *buf, size_t len, const char *filename, return 0; } -/* - * Reads a file that was presumably written by a shell script, i.e. with an - * end-of-line marker that needs to be stripped. - * - * Note that only the last end-of-line marker is stripped, consistent with the - * behavior of "$(cat path)" in a shell script. - * - * Returns 1 if the file was read, 0 if it could not be read or does not exist. - */ -static int read_oneliner(struct strbuf *buf, - const char *path, int skip_if_empty) +int read_oneliner(struct strbuf *buf, + const char *path, unsigned flags) { int orig_len = buf->len; - if (!file_exists(path)) - return 0; - if (strbuf_read_file(buf, path, 0) < 0) { - warning_errno(_("could not read '%s'"), path); + if ((flags & READ_ONELINER_WARN_MISSING) || + (errno != ENOENT && errno != ENOTDIR)) + warning_errno(_("could not read '%s'"), path); return 0; } @@ -447,7 +438,7 @@ static int read_oneliner(struct strbuf *buf, buf->buf[buf->len] = '\0'; } - if (skip_if_empty && buf->len == orig_len) + if ((flags & READ_ONELINER_SKIP_IF_EMPTY) && buf->len == orig_len) return 0; return 1; @@ -946,6 +937,8 @@ static int run_git_commit(struct repository *r, argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); + else + argv_array_push(&cmd.args, "--no-gpg-sign"); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); else if (!(flags & EDIT_MSG)) @@ -1323,7 +1316,7 @@ static int try_to_commit(struct repository *r, return -1; if (flags & AMEND_MSG) { - const char *exclude_gpgsig[] = { "gpgsig", NULL }; + const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL }; const char *out_enc = get_commit_output_encoding(); const char *message = logmsg_reencode(current_head, NULL, out_enc); @@ -1433,9 +1426,19 @@ out: return res; } +static int write_rebase_head(struct object_id *oid) +{ + if (update_ref("rebase", "REBASE_HEAD", oid, + NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) + return error(_("could not update %s"), "REBASE_HEAD"); + + return 0; +} + static int do_commit(struct repository *r, const char *msg_file, const char *author, - struct replay_opts *opts, unsigned int flags) + struct replay_opts *opts, unsigned int flags, + struct object_id *oid) { int res = 1; @@ -1460,8 +1463,12 @@ static int do_commit(struct repository *r, return res; } } - if (res == 1) + if (res == 1) { + if (is_rebase_i(opts) && oid) + if (write_rebase_head(oid)) + return -1; return run_git_commit(r, msg_file, opts, flags); + } return res; } @@ -1564,7 +1571,7 @@ static const char *command_to_string(const enum todo_command command) static char command_to_char(const enum todo_command command) { - if (command < TODO_COMMENT && todo_command_info[command].c) + if (command < TODO_COMMENT) return todo_command_info[command].c; return comment_line_char; } @@ -1929,7 +1936,9 @@ static int do_pick_commit(struct repository *r, * However, if the merge did not even start, then we don't want to * write it at all. */ - if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) && + if ((command == TODO_PICK || command == TODO_REWORD || + command == TODO_EDIT) && !opts->no_commit && + (res == 0 || res == 1) && update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; @@ -1965,7 +1974,8 @@ static int do_pick_commit(struct repository *r, } /* 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(r, msg_file, author, opts, flags); + res = do_commit(r, msg_file, author, opts, flags, + commit? &commit->object.oid : NULL); else res = error(_("unable to parse commit author")); *check_todo = !!(flags & EDIT_MSG); @@ -2485,8 +2495,10 @@ static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { struct strbuf buf = STRBUF_INIT; + int ret = 0; - if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) { + if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), + READ_ONELINER_SKIP_IF_EMPTY)) { if (!starts_with(buf.buf, "-S")) strbuf_reset(&buf); else { @@ -2496,7 +2508,8 @@ static int read_populate_opts(struct replay_opts *opts) strbuf_reset(&buf); } - if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) { + if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), + READ_ONELINER_SKIP_IF_EMPTY)) { if (!strcmp(buf.buf, "--rerere-autoupdate")) opts->allow_rerere_auto = RERERE_AUTOUPDATE; else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) @@ -2525,10 +2538,11 @@ static int read_populate_opts(struct replay_opts *opts) opts->keep_redundant_commits = 1; read_strategy_opts(opts, &buf); - strbuf_release(&buf); + strbuf_reset(&buf); if (read_oneliner(&opts->current_fixups, - rebase_path_current_fixups(), 1)) { + rebase_path_current_fixups(), + READ_ONELINER_SKIP_IF_EMPTY)) { const char *p = opts->current_fixups.buf; opts->current_fixup_count = 1; while ((p = strchr(p, '\n'))) { @@ -2538,12 +2552,16 @@ static int read_populate_opts(struct replay_opts *opts) } if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) { - if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) - return error(_("unusable squash-onto")); + if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) { + ret = error(_("unusable squash-onto")); + goto done_rebase_i; + } opts->have_squash_onto = 1; } - return 0; +done_rebase_i: + strbuf_release(&buf); + return ret; } if (!file_exists(git_path_opts_file())) @@ -3000,9 +3018,7 @@ static int make_patch(struct repository *r, 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"); + res |= write_rebase_head(&commit->object.oid); strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); @@ -3113,7 +3129,7 @@ static int do_exec(struct repository *r, const char *command_line) const char *child_argv[] = { NULL, NULL }; int dirty, status; - fprintf(stderr, "Executing: %s\n", command_line); + fprintf(stderr, _("Executing: %s\n"), command_line); child_argv[0] = command_line; argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir())); argv_array_pushf(&child_env, "GIT_WORK_TREE=%s", @@ -3290,6 +3306,7 @@ static int do_reset(struct repository *r, unpack_tree_opts.fn = oneway_merge; unpack_tree_opts.merge = 1; unpack_tree_opts.update = 1; + init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL); if (repo_read_index_unmerged(r)) { rollback_lock_file(&lock); @@ -3659,25 +3676,71 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } -static int apply_autostash(struct replay_opts *opts) +void create_autostash(struct repository *r, const char *path, + const char *default_reflog_action) +{ + struct strbuf buf = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = repo_hold_locked_index(r, &lock_file, 0); + refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL); + if (0 <= fd) + repo_update_index_if_able(r, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(r, 1) || + has_uncommitted_changes(r, 1)) { + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(path)) + die(_("Could not create directory for '%s'"), + path); + write_file(path, "%s", oid_to_hex(&oid)); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(r, NULL, "reset --hard", + NULL, RESET_HEAD_HARD, NULL, NULL, + default_reflog_action) < 0) + die(_("could not reset --hard")); + + if (discard_index(r->index) < 0 || + repo_read_index(r) < 0) + die(_("could not read index")); + } + strbuf_release(&buf); +} + +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) { - struct strbuf stash_sha1 = STRBUF_INIT; struct child_process child = CHILD_PROCESS_INIT; int ret = 0; - if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { - strbuf_release(&stash_sha1); - return 0; + if (attempt_apply) { + child.git_cmd = 1; + child.no_stdout = 1; + child.no_stderr = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_oid); + ret = run_command(&child); } - strbuf_trim(&stash_sha1); - child.git_cmd = 1; - child.no_stdout = 1; - child.no_stderr = 1; - argv_array_push(&child.args, "stash"); - argv_array_push(&child.args, "apply"); - argv_array_push(&child.args, stash_sha1.buf); - if (!run_command(&child)) + if (attempt_apply && !ret) fprintf(stderr, _("Applied autostash.\n")); else { struct child_process store = CHILD_PROCESS_INIT; @@ -3688,30 +3751,67 @@ static int apply_autostash(struct replay_opts *opts) argv_array_push(&store.args, "-m"); argv_array_push(&store.args, "autostash"); argv_array_push(&store.args, "-q"); - argv_array_push(&store.args, stash_sha1.buf); + argv_array_push(&store.args, stash_oid); if (run_command(&store)) - ret = error(_("cannot store %s"), stash_sha1.buf); + ret = error(_("cannot store %s"), stash_oid); else fprintf(stderr, - _("Applying autostash resulted in conflicts.\n" + _("%s\n" "Your changes are safe in the stash.\n" "You can run \"git stash pop\" or" - " \"git stash drop\" at any time.\n")); + " \"git stash drop\" at any time.\n"), + attempt_apply ? + _("Applying autostash resulted in conflicts.") : + _("Autostash exists; creating a new stash entry.")); } - strbuf_release(&stash_sha1); return ret; } +static int apply_save_autostash(const char *path, int attempt_apply) +{ + struct strbuf stash_oid = STRBUF_INIT; + int ret = 0; + + if (!read_oneliner(&stash_oid, path, + READ_ONELINER_SKIP_IF_EMPTY)) { + strbuf_release(&stash_oid); + return 0; + } + strbuf_trim(&stash_oid); + + ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply); + + unlink(path); + strbuf_release(&stash_oid); + return ret; +} + +int save_autostash(const char *path) +{ + return apply_save_autostash(path, 0); +} + +int apply_autostash(const char *path) +{ + return apply_save_autostash(path, 1); +} + +int apply_autostash_oid(const char *stash_oid) +{ + return apply_save_autostash_oid(stash_oid, 1); +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { va_list ap; static struct strbuf buf = STRBUF_INIT; + char *reflog_action = getenv(GIT_REFLOG_ACTION); va_start(ap, fmt); strbuf_reset(&buf); - strbuf_addstr(&buf, action_name(opts)); + strbuf_addstr(&buf, reflog_action ? reflog_action : action_name(opts)); if (sub_action) strbuf_addf(&buf, " (%s)", sub_action); if (fmt) { @@ -3757,7 +3857,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts, return error(_("%s: not a valid OID"), orig_head); if (run_git_checkout(r, opts, oid_to_hex(onto), action)) { - apply_autostash(opts); + apply_autostash(rebase_path_autostash()); sequencer_remove_state(opts); return error(_("could not detach HEAD")); } @@ -3799,8 +3899,11 @@ static int pick_commits(struct repository *r, struct replay_opts *opts) { int res = 0, reschedule = 0; + char *prev_reflog_action; + /* Note that 0 for 3rd parameter of setenv means set only if not set */ setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + prev_reflog_action = xstrdup(getenv(GIT_REFLOG_ACTION)); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); @@ -3825,7 +3928,7 @@ static int pick_commits(struct repository *r, fclose(f); } if (!opts->quiet) - fprintf(stderr, "Rebasing (%d/%d)%s", + fprintf(stderr, _("Rebasing (%d/%d)%s"), todo_list->done_nr, todo_list->total_nr, opts->verbose ? "\n" : "\r"); @@ -3845,12 +3948,14 @@ static int pick_commits(struct repository *r, } if (item->command <= TODO_SQUASH) { if (is_rebase_i(opts)) - setenv("GIT_REFLOG_ACTION", reflog_message(opts, + setenv(GIT_REFLOG_ACTION, reflog_message(opts, command_to_string(item->command), NULL), 1); res = do_pick_commit(r, item->command, item->commit, opts, is_final_fixup(todo_list), &check_todo); + if (is_rebase_i(opts)) + setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ advise(_(rescheduled_advice), @@ -4071,13 +4176,13 @@ cleanup_head_ref: run_command(&hook); } } - apply_autostash(opts); + apply_autostash(rebase_path_autostash()); if (!opts->quiet) { if (!opts->verbose) term_clear_line(); fprintf(stderr, - "Successfully rebased and updated %s.\n", + _("Successfully rebased and updated %s.\n"), head_ref.buf); } @@ -4289,7 +4394,8 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts) struct strbuf buf = STRBUF_INIT; struct object_id oid; - if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) && + if (read_oneliner(&buf, rebase_path_stopped_sha(), + READ_ONELINER_SKIP_IF_EMPTY) && !get_oid_committish(buf.buf, &oid)) record_in_rewritten(&oid, peek_command(&todo_list, 0)); strbuf_release(&buf); @@ -4591,6 +4697,7 @@ static int make_script_with_merges(struct pretty_print_context *pp, 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; @@ -4645,6 +4752,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, is_empty = is_original_commit_empty(commit); if (!is_empty && (commit->object.flags & PATCHSAME)) continue; + if (is_empty && !keep_empty) + continue; strbuf_reset(&oneline); pretty_print_commit(pp, commit, &oneline); @@ -4656,6 +4765,9 @@ static int make_script_with_merges(struct pretty_print_context *pp, strbuf_addf(&buf, "%s %s %s", cmd_pick, oid_to_hex(&commit->object.oid), oneline.buf); + if (is_empty) + strbuf_addf(&buf, " %c empty", + comment_line_char); FLEX_ALLOC_STR(entry, string, buf.buf); oidcpy(&entry->entry.oid, &commit->object.oid); @@ -4819,14 +4931,16 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, struct pretty_print_context pp = {0}; 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; + int reapply_cherry_picks = flags & TODO_LIST_REAPPLY_CHERRY_PICKS; repo_init_revisions(r, &revs, NULL); revs.verbose_header = 1; if (!rebase_merges) revs.max_parents = 1; - revs.cherry_mark = 1; + revs.cherry_mark = !reapply_cherry_picks; revs.limited = 1; revs.reverse = 1; revs.right_only = 1; @@ -4858,9 +4972,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, if (!is_empty && (commit->object.flags & PATCHSAME)) continue; + if (is_empty && !keep_empty) + continue; strbuf_addf(out, "%s %s ", insn, oid_to_hex(&commit->object.oid)); pretty_print_commit(&pp, commit, out); + if (is_empty) + strbuf_addf(out, " %c empty", comment_line_char); strbuf_addch(out, '\n'); } return 0; @@ -4947,6 +5065,8 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis max = num; for (item = todo_list->items, i = 0; i < max; i++, item++) { + char cmd; + /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { strbuf_addf(buf, "%.*s\n", item->arg_len, @@ -4955,8 +5075,9 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis } /* add command to the buffer */ - if (flags & TODO_LIST_ABBREVIATE_CMDS) - strbuf_addch(buf, command_to_char(item->command)); + cmd = command_to_char(item->command); + if ((flags & TODO_LIST_ABBREVIATE_CMDS) && cmd) + strbuf_addch(buf, cmd); else strbuf_addstr(buf, command_to_string(item->command)); @@ -5079,7 +5200,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla todo_list_add_exec_commands(todo_list, commands); if (count_commands(todo_list) == 0) { - apply_autostash(opts); + apply_autostash(rebase_path_autostash()); sequencer_remove_state(opts); return error(_("nothing to do")); @@ -5090,12 +5211,12 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla if (res == -1) return -1; else if (res == -2) { - apply_autostash(opts); + apply_autostash(rebase_path_autostash()); sequencer_remove_state(opts); return -1; } else if (res == -3) { - apply_autostash(opts); + apply_autostash(rebase_path_autostash()); sequencer_remove_state(opts); todo_list_release(&new_todo); @@ -5264,10 +5385,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) todo_list->items[i].command = starts_with(subject, "fixup!") ? TODO_FIXUP : TODO_SQUASH; - if (next[i2] < 0) + if (tail[i2] < 0) { + next[i] = next[i2]; next[i2] = i; - else + } else { + next[i] = next[tail[i2]]; next[tail[i2]] = i; + } tail[i2] = i; } else if (!hashmap_get_from_hash(&subject2item, strhash(subject), subject)) { @@ -5315,3 +5439,24 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) return 0; } + +int sequencer_determine_whence(struct repository *r, enum commit_whence *whence) +{ + if (file_exists(git_path_cherry_pick_head(r))) { + struct object_id cherry_pick_head, rebase_head; + + if (file_exists(git_path_seq_dir())) + *whence = FROM_CHERRY_PICK_MULTI; + if (file_exists(rebase_path()) && + !get_oid("REBASE_HEAD", &rebase_head) && + !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) && + oideq(&rebase_head, &cherry_pick_head)) + *whence = FROM_REBASE_PICK; + else + *whence = FROM_CHERRY_PICK_SINGLE; + + return 1; + } + + return 0; +} |