summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c310
1 files changed, 181 insertions, 129 deletions
diff --git a/sequencer.c b/sequencer.c
index 0bec01cf38..cd2aabf1f7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -8,6 +8,7 @@
#include "sequencer.h"
#include "tag.h"
#include "run-command.h"
+#include "hook.h"
#include "exec-cmd.h"
#include "utf8.h"
#include "cache-tree.h"
@@ -403,7 +404,7 @@ static void print_advice(struct repository *r, int show_hint,
char *msg = getenv("GIT_CHERRY_PICK_HELP");
if (msg) {
- fprintf(stderr, "%s\n", msg);
+ advise("%s\n", msg);
/*
* A conflict has occurred but the porcelain
* (typically rebase --interactive) wants to take care
@@ -418,10 +419,22 @@ static void print_advice(struct repository *r, int show_hint,
if (opts->no_commit)
advise(_("after resolving the conflicts, mark the corrected paths\n"
"with 'git add <paths>' or 'git rm <paths>'"));
+ else if (opts->action == REPLAY_PICK)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git cherry-pick --continue\".\n"
+ "You can instead skip this commit with \"git cherry-pick --skip\".\n"
+ "To abort and get back to the state before \"git cherry-pick\",\n"
+ "run \"git cherry-pick --abort\"."));
+ else if (opts->action == REPLAY_REVERT)
+ advise(_("After resolving the conflicts, mark them with\n"
+ "\"git add/rm <pathspec>\", then run\n"
+ "\"git revert --continue\".\n"
+ "You can instead skip this commit with \"git revert --skip\".\n"
+ "To abort and get back to the state before \"git revert\",\n"
+ "run \"git revert --abort\"."));
else
- advise(_("after resolving the conflicts, mark the corrected paths\n"
- "with 'git add <paths>' or 'git rm <paths>'\n"
- "and commit the result with 'git commit'"));
+ BUG("unexpected pick action in print_advice()");
}
}
@@ -486,7 +499,7 @@ static int error_dirty_index(struct repository *repo, struct replay_opts *opts)
error(_("your local changes would be overwritten by %s."),
_(action_name(opts)));
- if (advice_commit_before_merge)
+ if (advice_enabled(ADVICE_COMMIT_BEFORE_MERGE))
advise(_("commit your changes or stash them to proceed."));
return -1;
}
@@ -636,7 +649,7 @@ static int do_recursive_merge(struct repository *r,
for (i = 0; i < opts->xopts_nr; i++)
parse_merge_opt(&o, opts->xopts[i]);
- if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
memset(&result, 0, sizeof(result));
merge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,
&result);
@@ -652,6 +665,7 @@ static int do_recursive_merge(struct repository *r,
merge_switch_to_result(&o, head_tree, &result, 1, show_output);
clean = result.clean;
} else {
+ ensure_full_index(r->index);
clean = merge_trees(&o, head_tree, next_tree, base_tree);
if (is_rebase_i(opts) && clean <= 0)
fputs(o.obuf.buf, stdout);
@@ -983,7 +997,8 @@ static int run_git_commit(const char *defmsg,
cmd.git_cmd = 1;
- if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+ if (is_rebase_i(opts) && !(!defmsg && (flags & AMEND_MSG)) &&
+ read_env_script(&cmd.env_array)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error(_(staged_changes_advice),
@@ -1241,7 +1256,7 @@ N_("Your name and email address were configured automatically based\n"
static const char *implicit_ident_advice(void)
{
- char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *user_config = interpolate_path("~/.gitconfig", 0);
char *xdg_config = xdg_config_home("config");
int config_exists = file_exists(user_config) || file_exists(xdg_config);
@@ -1293,7 +1308,7 @@ void print_commit_summary(struct repository *r,
if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
- if (advice_implicit_identity) {
+ if (advice_enabled(ADVICE_IMPLICIT_IDENTITY)) {
strbuf_addch(&format, '\n');
strbuf_addstr(&format, implicit_ident_advice());
}
@@ -1445,7 +1460,7 @@ static int try_to_commit(struct repository *r,
}
}
- if (find_hook("prepare-commit-msg")) {
+ if (hook_exists("prepare-commit-msg")) {
res = run_prepare_commit_msg_hook(r, msg, hook_commit);
if (res)
goto out;
@@ -2065,7 +2080,7 @@ static int do_pick_commit(struct repository *r,
/*
* We do not intend to commit immediately. We just want to
* merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
+ * that represents the "current" state for the merge machinery
* to work on.
*/
if (write_index_as_tree(&head, r->index, r->index_file, 0, NULL))
@@ -2346,6 +2361,7 @@ static int read_and_refresh_cache(struct repository *r,
_(action_name(opts)));
}
refresh_index(r->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+
if (index_fd >= 0) {
if (write_locked_index(r->index, &index_lock,
COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
@@ -2353,6 +2369,13 @@ static int read_and_refresh_cache(struct repository *r,
_(action_name(opts)));
}
}
+
+ /*
+ * If we are resolving merges in any way other than "ort", then
+ * expand the sparse index.
+ */
+ if (opts->strategy && strcmp(opts->strategy, "ort"))
+ ensure_full_index(r->index);
return 0;
}
@@ -2671,7 +2694,6 @@ 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);
int res;
@@ -2679,11 +2701,6 @@ static int read_populate_todo(struct repository *r,
if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
return -1;
- res = stat(todo_file, &st);
- if (res)
- return error(_("could not stat '%s'"), todo_file);
- fill_stat_data(&todo_list->stat, &st);
-
res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
@@ -3041,7 +3058,7 @@ static int create_seq_dir(struct repository *r)
}
if (in_progress_error) {
error("%s", in_progress_error);
- if (advice_sequencer_in_use)
+ if (advice_enabled(ADVICE_SEQUENCER_IN_USE))
advise(in_progress_advice,
advise_skip ? "--skip | " : "");
return -1;
@@ -3245,7 +3262,7 @@ int sequencer_skip(struct repository *r, struct replay_opts *opts)
give_advice:
error(_("there is nothing to skip"));
- if (advice_resolve_conflict) {
+ if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) {
advise(_("have you committed already?\n"
"try \"git %s --continue\""),
action == REPLAY_REVERT ? "revert" : "cherry-pick");
@@ -3521,6 +3538,7 @@ static int do_exec(struct repository *r, const char *command_line)
return status;
}
+__attribute__((format (printf, 2, 3)))
static int safe_append(const char *filename, const char *fmt, ...)
{
va_list ap;
@@ -3598,8 +3616,27 @@ static int do_label(struct repository *r, const char *name, int len)
return ret;
}
+__attribute__((format (printf, 3, 4)))
static const char *reflog_message(struct replay_opts *opts,
- const char *sub_action, const char *fmt, ...);
+ 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, reflog_action ? reflog_action : action_name(opts));
+ if (sub_action)
+ strbuf_addf(&buf, " (%s)", sub_action);
+ if (fmt) {
+ strbuf_addstr(&buf, ": ");
+ strbuf_vaddf(&buf, fmt, ap);
+ }
+ va_end(ap);
+
+ return buf.buf;
+}
static int do_reset(struct repository *r,
const char *name, int len,
@@ -3608,9 +3645,9 @@ static int do_reset(struct repository *r,
struct strbuf ref_name = STRBUF_INIT;
struct object_id oid;
struct lock_file lock = LOCK_INIT;
- struct tree_desc desc;
+ struct tree_desc desc = { 0 };
struct tree *tree;
- struct unpack_trees_options unpack_tree_opts;
+ struct unpack_trees_options unpack_tree_opts = { 0 };
int ret = 0;
if (repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0)
@@ -3642,14 +3679,11 @@ static int do_reset(struct repository *r,
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
if (get_oid(ref_name.buf, &oid) &&
get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
- error(_("could not read '%s'"), ref_name.buf);
- rollback_lock_file(&lock);
- strbuf_release(&ref_name);
- return -1;
+ ret = error(_("could not read '%s'"), ref_name.buf);
+ goto cleanup;
}
}
- 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 = r->index;
@@ -3657,27 +3691,22 @@ static int do_reset(struct repository *r,
unpack_tree_opts.fn = oneway_merge;
unpack_tree_opts.merge = 1;
unpack_tree_opts.update = 1;
+ unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
init_checkout_metadata(&unpack_tree_opts.meta, name, &oid, NULL);
if (repo_read_index_unmerged(r)) {
- rollback_lock_file(&lock);
- strbuf_release(&ref_name);
- return error_resolve_conflict(_(action_name(opts)));
+ ret = error_resolve_conflict(_(action_name(opts)));
+ goto cleanup;
}
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);
- strbuf_release(&ref_name);
- return -1;
+ ret = error(_("failed to find tree of %s"), oid_to_hex(&oid));
+ goto cleanup;
}
if (unpack_trees(1, &desc, &unpack_tree_opts)) {
- rollback_lock_file(&lock);
- free((void *)desc.buffer);
- strbuf_release(&ref_name);
- return -1;
+ ret = -1;
+ goto cleanup;
}
tree = parse_tree_indirect(&oid);
@@ -3685,14 +3714,17 @@ static int do_reset(struct repository *r,
if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0)
ret = error(_("could not write index"));
- free((void *)desc.buffer);
if (!ret)
ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
len, name), "HEAD", &oid,
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
-
+cleanup:
+ free((void *)desc.buffer);
+ if (ret < 0)
+ rollback_lock_file(&lock);
strbuf_release(&ref_name);
+ clear_unpack_trees_porcelain(&unpack_tree_opts);
return ret;
}
@@ -3719,10 +3751,9 @@ static struct commit *lookup_label(const char *label, int len,
static int do_merge(struct repository *r,
struct commit *commit,
const char *arg, int arg_len,
- int flags, struct replay_opts *opts)
+ int flags, int *check_todo, struct replay_opts *opts)
{
- int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
- EDIT_MSG | VERIFY_MSG : 0;
+ int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
@@ -3796,6 +3827,45 @@ static int do_merge(struct repository *r,
goto leave_merge;
}
+ /*
+ * If HEAD is not identical to the first parent of the original merge
+ * commit, we cannot fast-forward.
+ */
+ can_fast_forward = opts->allow_ff && commit && commit->parents &&
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
+
+ /*
+ * If any merge head is different from the original one, we cannot
+ * fast-forward.
+ */
+ if (can_fast_forward) {
+ struct commit_list *p = commit->parents->next;
+
+ for (j = to_merge; j && p; j = j->next, p = p->next)
+ if (!oideq(&j->item->object.oid,
+ &p->item->object.oid)) {
+ can_fast_forward = 0;
+ break;
+ }
+ /*
+ * If the number of merge heads differs from the original merge
+ * commit, we cannot fast-forward.
+ */
+ if (j || p)
+ can_fast_forward = 0;
+ }
+
+ if (can_fast_forward) {
+ rollback_lock_file(&lock);
+ ret = fast_forward_to(r, &commit->object.oid,
+ &head_commit->object.oid, 0, opts);
+ if (flags & TODO_EDIT_MERGE_MSG)
+ goto fast_forward_edit;
+
+ goto leave_merge;
+ }
+
if (commit) {
const char *encoding = get_commit_output_encoding();
const char *message = logmsg_reencode(commit, NULL, encoding);
@@ -3845,46 +3915,6 @@ static int do_merge(struct repository *r,
}
}
- /*
- * If HEAD is not identical to the first parent of the original merge
- * commit, we cannot fast-forward.
- */
- can_fast_forward = opts->allow_ff && commit && commit->parents &&
- oideq(&commit->parents->item->object.oid,
- &head_commit->object.oid);
-
- /*
- * If any merge head is different from the original one, we cannot
- * fast-forward.
- */
- if (can_fast_forward) {
- struct commit_list *p = commit->parents->next;
-
- for (j = to_merge; j && p; j = j->next, p = p->next)
- if (!oideq(&j->item->object.oid,
- &p->item->object.oid)) {
- can_fast_forward = 0;
- break;
- }
- /*
- * If the number of merge heads differs from the original merge
- * commit, we cannot fast-forward.
- */
- if (j || p)
- can_fast_forward = 0;
- }
-
- if (can_fast_forward) {
- rollback_lock_file(&lock);
- 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 (strategy || to_merge->next) {
/* Octopus merge */
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -3915,7 +3945,10 @@ static int do_merge(struct repository *r,
strvec_pushf(&cmd.args,
"-X%s", opts->xopts[k]);
}
- strvec_push(&cmd.args, "--no-edit");
+ if (!(flags & TODO_EDIT_MERGE_MSG))
+ strvec_push(&cmd.args, "--no-edit");
+ else
+ strvec_push(&cmd.args, "--edit");
strvec_push(&cmd.args, "--no-ff");
strvec_push(&cmd.args, "--no-log");
strvec_push(&cmd.args, "--no-stat");
@@ -3968,7 +4001,7 @@ static int do_merge(struct repository *r,
o.branch2 = ref_name.buf;
o.buffer_output = 2;
- if (opts->strategy && !strcmp(opts->strategy, "ort")) {
+ if (!opts->strategy || !strcmp(opts->strategy, "ort")) {
/*
* TODO: Should use merge_incore_recursive() and
* merge_switch_to_result(), skipping the call to
@@ -4015,10 +4048,17 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- fast_forward_edit:
ret = !!run_git_commit(git_path_merge_msg(r), opts,
run_commit_flags);
+ if (!ret && flags & TODO_EDIT_MERGE_MSG) {
+ fast_forward_edit:
+ *check_todo = 1;
+ run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
+ ret = !!run_git_commit(NULL, opts, run_commit_flags);
+ }
+
+
leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
@@ -4178,27 +4218,6 @@ 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, reflog_action ? reflog_action : action_name(opts));
- if (sub_action)
- strbuf_addf(&buf, " (%s)", sub_action);
- if (fmt) {
- strbuf_addstr(&buf, ": ");
- strbuf_vaddf(&buf, fmt, ap);
- }
- va_end(ap);
-
- return buf.buf;
-}
-
static int run_git_checkout(struct repository *r, struct replay_opts *opts,
const char *commit, const char *action)
{
@@ -4255,6 +4274,30 @@ static int stopped_at_head(struct repository *r)
}
+static int reread_todo_if_changed(struct repository *r,
+ struct todo_list *todo_list,
+ struct replay_opts *opts)
+{
+ int offset;
+ struct strbuf buf = STRBUF_INIT;
+
+ if (strbuf_read_file_or_whine(&buf, get_todo_path(opts)) < 0)
+ return -1;
+ offset = get_item_line_offset(todo_list, todo_list->current + 1);
+ if (buf.len != todo_list->buf.len - offset ||
+ memcmp(buf.buf, todo_list->buf.buf + offset, buf.len)) {
+ /* Reread the todo file if it has changed. */
+ todo_list_release(todo_list);
+ if (read_populate_todo(r, todo_list, opts))
+ return -1; /* message was printed */
+ /* `current` will be incremented on return */
+ todo_list->current = -1;
+ }
+ strbuf_release(&buf);
+
+ return 0;
+}
+
static const char rescheduled_advice[] =
N_("Could not execute the todo command\n"
"\n"
@@ -4406,9 +4449,8 @@ static int pick_commits(struct repository *r,
if ((res = do_reset(r, arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
- if ((res = do_merge(r, item->commit,
- arg, item->arg_len,
- item->flags, opts)) < 0)
+ if ((res = do_merge(r, item->commit, arg, item->arg_len,
+ item->flags, &check_todo, opts)) < 0)
reschedule = 1;
else if (item->commit)
record_in_rewritten(&item->commit->object.oid,
@@ -4434,20 +4476,9 @@ static int pick_commits(struct repository *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;
- }
+ } else if (is_rebase_i(opts) && check_todo && !res &&
+ reread_todo_if_changed(r, todo_list, opts)) {
+ return -1;
}
todo_list->current++;
@@ -4717,6 +4748,9 @@ static int commit_staged_changes(struct repository *r,
refs_delete_ref(get_main_ref_store(r), "",
"CHERRY_PICK_HEAD", NULL, 0))
return error(_("could not remove CHERRY_PICK_HEAD"));
+ if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
+ return error_errno(_("could not remove '%s'"),
+ git_path_merge_msg(r));
if (!final_fixup)
return 0;
}
@@ -5100,6 +5134,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,
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;
+ int skipped_commit = 0;
struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
struct strbuf label = STRBUF_INIT;
struct commit_list *commits = NULL, **tail = &commits, *iter;
@@ -5150,8 +5185,13 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidset_insert(&interesting, &commit->object.oid);
is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
@@ -5215,6 +5255,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,
oidcpy(&entry->entry.oid, &commit->object.oid);
oidmap_put(&commit2todo, entry);
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
/*
* Second phase:
@@ -5335,6 +5378,7 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
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;
+ int skipped_commit = 0;
repo_init_revisions(r, &revs, NULL);
revs.verbose_header = 1;
@@ -5370,8 +5414,13 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
while ((commit = get_revision(&revs))) {
int is_empty = is_original_commit_empty(commit);
- if (!is_empty && (commit->object.flags & PATCHSAME))
+ if (!is_empty && (commit->object.flags & PATCHSAME)) {
+ if (flags & TODO_LIST_WARN_SKIPPED_CHERRY_PICKS)
+ warning(_("skipped previously applied commit %s"),
+ short_commit_name(commit));
+ skipped_commit = 1;
continue;
+ }
if (is_empty && !keep_empty)
continue;
strbuf_addf(out, "%s %s ", insn,
@@ -5381,6 +5430,9 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
strbuf_addf(out, " %c empty", comment_line_char);
strbuf_addch(out, '\n');
}
+ if (skipped_commit)
+ advise_if_enabled(ADVICE_SKIPPED_CHERRY_PICKS,
+ _("use --reapply-cherry-picks to include skipped commits"));
return 0;
}
@@ -5388,8 +5440,8 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,
* Add commands after pick and (series of) squash/fixup commands
* in the todo list.
*/
-void todo_list_add_exec_commands(struct todo_list *todo_list,
- struct string_list *commands)
+static void todo_list_add_exec_commands(struct todo_list *todo_list,
+ struct string_list *commands)
{
struct strbuf *buf = &todo_list->buf;
size_t base_offset = buf->len;