From: Junio C Hamano Date: Fri, 2 Nov 2018 02:04:59 +0000 (+0900) Subject: Merge branch 'js/rebase-i-shortopt' X-Git-Tag: v2.20.0-rc0~83 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/85fcf1cbb6eb2e8fb1512a5ffe834b6a07deb4cd?hp=-c Merge branch 'js/rebase-i-shortopt' "git rebase -i" learned to take 'b' as the short form of 'break' option in the todo list. * js/rebase-i-shortopt: rebase -i: recognize short commands without arguments --- 85fcf1cbb6eb2e8fb1512a5ffe834b6a07deb4cd diff --combined sequencer.c index 73efa92da8,3107f59ea7..9e1ab3a2a7 --- a/sequencer.c +++ b/sequencer.c @@@ -30,7 -30,6 +30,7 @@@ #include "oidset.h" #include "commit-slab.h" #include "alias.h" +#include "commit-reach.h" #include "rebase-interactive.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@@ -68,12 -67,12 +68,12 @@@ static GIT_PATH_FUNC(rebase_path_done, * The file to keep track of how many commands were already processed (e.g. * for the prompt). */ -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum") /* * The file to keep track of how many commands are to be processed in total * (e.g. for the prompt). */ -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end") /* * The commit message that is planned to be used for any changes that * need to be committed following a user interaction. @@@ -231,16 -230,13 +231,16 @@@ static const char *get_todo_path(const * Returns 3 when sob exists within conforming footer as last entry */ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, - int ignore_footer) + size_t ignore_footer) { + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; struct trailer_info info; - int i; + size_t i; int found_sob = 0, found_sob_last = 0; - trailer_info_get(&info, sb->buf); + opts.no_divider = 1; + + trailer_info_get(&info, sb->buf, &opts); if (info.trailer_start == info.trailer_end) return 0; @@@ -316,7 -312,7 +316,7 @@@ static const char *action_name(const st case REPLAY_INTERACTIVE_REBASE: return N_("rebase -i"); } - die(_("Unknown action: %d"), opts->action); + die(_("unknown action: %d"), opts->action); } struct commit_message { @@@ -442,7 -438,7 +442,7 @@@ static int read_oneliner(struct strbuf static struct tree *empty_tree(void) { - return lookup_tree(the_hash_algo->empty_tree); + return lookup_tree(the_repository, the_repository->hash_algo->empty_tree); } static int error_dirty_index(struct replay_opts *opts) @@@ -479,8 -475,8 +479,8 @@@ static int fast_forward_to(const struc struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - read_cache(); - if (checkout_fast_forward(from, to, 1)) + read_index(&the_index); + if (checkout_fast_forward(the_repository, from, to, 1)) return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); @@@ -603,7 -599,7 +603,7 @@@ static int is_index_unchanged(void if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) return error(_("could not resolve HEAD commit")); - head_commit = lookup_commit(&head_oid); + head_commit = lookup_commit(the_repository, &head_oid); /* * If head_commit is NULL, check_commit, called from @@@ -619,7 -615,7 +619,7 @@@ if (!(cache_tree_oid = get_cache_tree_oid())) return -1; - return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit)); + return oideq(cache_tree_oid, get_commit_tree_oid(head_commit)); } static int write_author_script(const char *message) @@@ -648,7 -644,7 +648,7 @@@ missing_author else if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); while (*message && *message != '\n' && *message != '\r') if (skip_prefix(message, "> ", &message)) @@@ -656,37 -652,18 +656,37 @@@ else if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); while (*message && *message != '\n' && *message != '\r') if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); + strbuf_addch(&buf, '\''); res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); strbuf_release(&buf); return res; } + +/* + * write_author_script() used to fail to terminate the last line with a "'" and + * also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". We check for + * the terminating "'" on the last line to see how "'" has been escaped in case + * git was upgraded while rebase was stopped. + */ +static int quoting_is_broken(const char *s, size_t n) +{ + /* Skip any empty lines in case the file was hand edited */ + while (n > 0 && s[--n] == '\n') + ; /* empty */ + if (n > 0 && s[n] != '\'') + return 1; + + return 0; +} + /* * Read a list of environment variable assignments (such as the author-script * file) into an environment block. Returns -1 on error, 0 otherwise. @@@ -694,18 -671,14 +694,18 @@@ static int read_env_script(struct argv_array *env) { struct strbuf script = STRBUF_INIT; - int i, count = 0; - char *p, *p2; + int i, count = 0, sq_bug; + const char *p2; + char *p; if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) return -1; - + /* write_author_script() used to quote incorrectly */ + sq_bug = quoting_is_broken(script.buf, script.len); for (p = script.buf; *p; p++) - if (skip_prefix(p, "'\\\\''", (const char **)&p2)) + if (sq_bug && skip_prefix(p, "'\\\\''", &p2)) + strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); + else if (skip_prefix(p, "'\\''", &p2)) strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); else if (*p == '\'') strbuf_splice(&script, p-- - script.buf, 1, "", 0); @@@ -740,51 -713,43 +740,51 @@@ static const char *read_author_ident(st const char *keys[] = { "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE=" }; - char *in, *out, *eol; - int i = 0, len; + struct strbuf out = STRBUF_INIT; + char *in, *eol; + const char *val[3]; + int i = 0; if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0) return NULL; /* dequote values and construct ident line in-place */ - for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { + for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { if (!skip_prefix(in, keys[i], (const char **)&in)) { - warning("could not parse '%s' (looking for '%s'", + warning(_("could not parse '%s' (looking for '%s')"), rebase_path_author_script(), keys[i]); return NULL; } eol = strchrnul(in, '\n'); *eol = '\0'; - sq_dequote(in); - len = strlen(in); - - if (i > 0) /* separate values by spaces */ - *(out++) = ' '; - if (i == 1) /* email needs to be surrounded by <...> */ - *(out++) = '<'; - memmove(out, in, len); - out += len; - if (i == 1) /* email needs to be surrounded by <...> */ - *(out++) = '>'; + if (!sq_dequote(in)) { + warning(_("bad quoting on %s value in '%s'"), + keys[i], rebase_path_author_script()); + return NULL; + } + val[i] = in; in = eol + 1; } if (i < 3) { - warning("could not parse '%s' (looking for '%s')", + warning(_("could not parse '%s' (looking for '%s')"), rebase_path_author_script(), keys[i]); return NULL; } - buf->len = out - buf->buf; + /* validate date since fmt_ident() will die() on bad value */ + if (parse_date(val[2], &out)){ + warning(_("invalid date format '%s' in '%s'"), + val[2], rebase_path_author_script()); + strbuf_release(&out); + return NULL; + } + + strbuf_reset(&out); + strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0)); + strbuf_swap(buf, &out); + strbuf_release(&out); return buf->buf; } @@@ -846,18 -811,11 +846,18 @@@ static int run_git_commit(const char *d if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) { struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; - const char *author = is_rebase_i(opts) ? - read_author_ident(&script) : NULL; + 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"); @@@ -918,7 -876,7 +918,7 @@@ if ((flags & ALLOW_EMPTY)) argv_array_push(&cmd.args, "--allow-empty"); - if (opts->allow_empty_message) + if (!(flags & EDIT_MSG)) argv_array_push(&cmd.args, "--allow-empty-message"); if (is_rebase_i(opts) && !(flags & EDIT_MSG)) @@@ -1147,7 -1105,7 +1147,7 @@@ void print_commit_summary(const char *p struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; - commit = lookup_commit(oid); + commit = lookup_commit(the_repository, oid); if (!commit) die(_("couldn't look up newly created commit")); if (parse_commit(commit)) @@@ -1180,7 -1138,7 +1180,7 @@@ strbuf_release(&author_ident); strbuf_release(&committer_ident); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); setup_revisions(0, NULL, &rev, NULL); rev.diff = 1; @@@ -1222,10 -1180,10 +1222,10 @@@ static int parse_head(struct commit **h if (get_oid("HEAD", &oid)) { current_head = NULL; } else { - current_head = lookup_commit_reference(&oid); + current_head = lookup_commit_reference(the_repository, &oid); if (!current_head) return error(_("could not parse HEAD")); - if (oidcmp(&oid, ¤t_head->object.oid)) { + if (!oideq(&oid, ¤t_head->object.oid)) { warning(_("HEAD %s is not a commit!"), oid_to_hex(&oid)); } @@@ -1290,14 -1248,14 +1290,14 @@@ static int try_to_commit(struct strbuf commit_list_insert(current_head, &parents); } - if (write_cache_as_tree(&tree, 0, NULL)) { + if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) { res = error(_("git write-tree failed to write a tree")); goto out; } - if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ? - get_commit_tree_oid(current_head) : - the_hash_algo->empty_tree, &tree)) { + 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; } @@@ -1321,7 -1279,7 +1321,7 @@@ if (cleanup != COMMIT_MSG_CLEANUP_NONE) strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL); - if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) { + if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) { res = 1; /* run 'git commit' to display error message */ goto out; } @@@ -1402,7 -1360,7 +1402,7 @@@ static int is_original_commit_empty(str ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } - return !oidcmp(ptree_oid, get_commit_tree_oid(commit)); + return oideq(ptree_oid, get_commit_tree_oid(commit)); } /* @@@ -1493,7 -1451,7 +1493,7 @@@ static const char *command_to_string(co { if (command < TODO_COMMENT) return todo_command_info[command].str; - die("Unknown command: %d", command); + die(_("unknown command: %d"), command); } static char command_to_char(const enum todo_command command) @@@ -1559,7 -1517,7 +1559,7 @@@ static int update_squash_messages(enum if (get_oid("HEAD", &head)) return error(_("need a HEAD to fixup")); - if (!(head_commit = lookup_commit_reference(&head))) + if (!(head_commit = lookup_commit_reference(the_repository, &head))) return error(_("could not read HEAD")); if (!(head_message = get_commit_buffer(head_commit, NULL))) return error(_("could not read HEAD's commit message")); @@@ -1591,13 -1549,13 +1591,13 @@@ unlink(rebase_path_fixup_msg()); strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("This is the commit message #%d:"), - ++opts->current_fixup_count); + ++opts->current_fixup_count + 1); strbuf_addstr(&buf, "\n\n"); strbuf_addstr(&buf, body); } else if (command == TODO_FIXUP) { strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("The commit message #%d will be skipped:"), - ++opts->current_fixup_count); + ++opts->current_fixup_count + 1); strbuf_addstr(&buf, "\n\n"); strbuf_add_commented_lines(&buf, body, strlen(body)); } else @@@ -1678,13 -1636,13 +1678,13 @@@ static int do_pick_commit(enum todo_com * that represents the "current" state for merge-recursive * to work on. */ - if (write_cache_as_tree(&head, 0, NULL)) + if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL)) return error(_("your index file is unmerged.")); } else { unborn = get_oid("HEAD", &head); /* Do we want to generate a root commit? */ if (is_pick_or_similar(command) && opts->have_squash_onto && - !oidcmp(&head, &opts->squash_onto)) { + oideq(&head, &opts->squash_onto)) { if (is_fixup(command)) return error(_("cannot fixup root commit")); flags |= CREATE_ROOT_COMMIT; @@@ -1727,7 -1685,7 +1727,7 @@@ oid_to_hex(&commit->object.oid)); if (opts->allow_ff && !is_fixup(command) && - ((parent && !oidcmp(&parent->object.oid, &head)) || + ((parent && oideq(&parent->object.oid, &head)) || (!parent && unborn))) { if (is_rebase_i(opts)) write_author_script(msg.message); @@@ -1837,7 -1795,7 +1837,7 @@@ commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res |= try_merge_command(opts->strategy, + res |= try_merge_command(the_repository, opts->strategy, opts->xopts_nr, (const char **)opts->xopts, common, oid_to_hex(&head), remotes); free_commit_list(common); @@@ -1866,7 -1824,7 +1866,7 @@@ : _("could not apply %s... %s"), short_commit_name(commit), msg.subject); print_advice(res == 1, opts); - rerere(opts->allow_rerere_auto); + repo_rerere(the_repository, opts->allow_rerere_auto); goto leave; } @@@ -1912,6 -1870,8 +1912,6 @@@ static int prepare_revs(struct replay_o if (prepare_revision_walk(opts->revs)) return error(_("revision walk setup failed")); - if (!opts->revs->commits) - return error(_("empty commit set passed")); return 0; } @@@ -1919,7 -1879,7 +1919,7 @@@ static int read_and_refresh_cache(struc { struct lock_file index_lock = LOCK_INIT; int index_fd = hold_locked_index(&index_lock, 0); - if (read_index_preload(&the_index, NULL) < 0) { + if (read_index_preload(&the_index, NULL, 0) < 0) { rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), _(action_name(opts))); @@@ -1994,7 -1954,8 +1994,8 @@@ static int parse_insn_line(struct todo_ if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; - } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + } else if ((bol + 1 == eol || bol[1] == ' ') && + *bol == todo_command_info[i].c) { bol++; item->command = i; break; @@@ -2055,7 -2016,7 +2056,7 @@@ if (status < 0) return -1; - item->commit = lookup_commit_reference(&commit_oid); + item->commit = lookup_commit_reference(the_repository, &commit_oid); return !item->commit; } @@@ -2377,7 -2338,7 +2378,7 @@@ int write_basic_state(struct replay_opt write_file(rebase_path_quiet(), "\n"); if (opts->verbose) - write_file(rebase_path_verbose(), ""); + write_file(rebase_path_verbose(), "%s", ""); if (opts->strategy) write_file(rebase_path_strategy(), "%s\n", opts->strategy); if (opts->xopts_nr > 0) @@@ -2423,10 -2384,6 +2424,10 @@@ static int walk_revs_populate_todo(stru short_commit_name(commit), subject_len, subject); unuse_commit_buffer(commit, commit_buffer); } + + if (!todo_list->nr) + return error(_("empty commit set passed")); + return 0; } @@@ -2486,7 -2443,7 +2487,7 @@@ static int rollback_is_safe(void if (get_oid("HEAD", &actual_head)) oidclr(&actual_head); - return !oidcmp(&actual_head, &expected_head); + return oideq(&actual_head, &expected_head); } static int reset_for_rollback(const struct object_id *oid) @@@ -2659,7 -2616,7 +2660,7 @@@ static int make_patch(struct commit *co strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - init_revisions(&log_tree_opt, NULL); + repo_init_revisions(the_repository, &log_tree_opt, NULL); log_tree_opt.abbrev = 0; log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; @@@ -2703,39 -2660,23 +2704,39 @@@ static int error_with_patch(struct comm const char *subject, int subject_len, struct replay_opts *opts, int exit_code, int to_amend) { - if (make_patch(commit, opts)) - return -1; + if (commit) { + if (make_patch(commit, opts)) + return -1; + } else if (copy_file(rebase_path_message(), + git_path_merge_msg(the_repository), 0666)) + return error(_("unable to copy '%s' to '%s'"), + git_path_merge_msg(the_repository), rebase_path_message()); if (to_amend) { if (intend_to_amend()) return -1; - fprintf(stderr, "You can amend the commit now, with\n" - "\n" - " git commit --amend %s\n" - "\n" - "Once you are satisfied with your changes, run\n" - "\n" - " git rebase --continue\n", gpg_sign_opt_quoted(opts)); - } else if (exit_code) - fprintf(stderr, "Could not apply %s... %.*s\n", - short_commit_name(commit), subject_len, subject); + fprintf(stderr, + _("You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n"), + gpg_sign_opt_quoted(opts)); + } else if (exit_code) { + if (commit) + fprintf_ln(stderr, _("Could not apply %s... %.*s"), + short_commit_name(commit), subject_len, subject); + else + /* + * We don't have the hash of the parent so + * just print the line from the todo file. + */ + fprintf_ln(stderr, _("Could not merge %.*s"), + subject_len, subject); + } return exit_code; } @@@ -2763,8 -2704,6 +2764,8 @@@ static int do_exec(const char *command_ 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", + absolute_path(get_git_work_tree())); status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL, child_env.argv); @@@ -2848,7 -2787,7 +2849,7 @@@ static int do_label(const char *name, i struct object_id head_oid; if (len == 1 && *name == '#') - return error("Illegal label name: '%.*s'", len, name); + 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); @@@ -2970,26 -2909,6 +2971,26 @@@ static int do_reset(const char *name, i return ret; } +static struct commit *lookup_label(const char *label, int len, + struct strbuf *buf) +{ + struct commit *commit; + + strbuf_reset(buf); + strbuf_addf(buf, "refs/rewritten/%.*s", len, label); + commit = lookup_commit_reference_by_name(buf->buf); + if (!commit) { + /* fall back to non-rewritten ref or commit */ + strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0); + commit = lookup_commit_reference_by_name(buf->buf); + } + + if (!commit) + error(_("could not resolve '%s'"), buf->buf); + + return commit; +} + static int do_merge(struct commit *commit, const char *arg, int arg_len, int flags, struct replay_opts *opts) { @@@ -2998,9 -2917,8 +2999,9 @@@ struct strbuf ref_name = STRBUF_INIT; struct commit *head_commit, *merge_commit, *i; struct commit_list *bases, *j, *reversed = NULL; + struct commit_list *to_merge = NULL, **tail = &to_merge; struct merge_options o; - int merge_arg_len, oneline_offset, can_fast_forward, ret; + int merge_arg_len, oneline_offset, can_fast_forward, ret, k; static struct lock_file lock; const char *p; @@@ -3015,51 -2933,38 +3016,51 @@@ goto leave_merge; } - oneline_offset = arg_len; - merge_arg_len = strcspn(arg, " \t\n"); - p = arg + merge_arg_len; - p += strspn(p, " \t\n"); - if (*p == '#' && (!p[1] || isspace(p[1]))) { - p += 1 + strspn(p + 1, " \t\n"); - oneline_offset = p - arg; - } else if (p - arg < arg_len) - BUG("octopus merges are not supported yet: '%s'", p); - - strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg); - merge_commit = lookup_commit_reference_by_name(ref_name.buf); - if (!merge_commit) { - /* fall back to non-rewritten ref or commit */ - strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0); - merge_commit = lookup_commit_reference_by_name(ref_name.buf); + /* + * For octopus merges, the arg starts with the list of revisions to be + * merged. The list is optionally followed by '#' and the oneline. + */ + merge_arg_len = oneline_offset = arg_len; + for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) { + if (!*p) + break; + if (*p == '#' && (!p[1] || isspace(p[1]))) { + p += 1 + strspn(p + 1, " \t\n"); + oneline_offset = p - arg; + break; + } + k = strcspn(p, " \t\n"); + if (!k) + continue; + merge_commit = lookup_label(p, k, &ref_name); + if (!merge_commit) { + ret = error(_("unable to parse '%.*s'"), k, p); + goto leave_merge; + } + tail = &commit_list_insert(merge_commit, tail)->next; + p += k; + merge_arg_len = p - arg; } - if (!merge_commit) { - ret = error(_("could not resolve '%s'"), ref_name.buf); + if (!to_merge) { + ret = error(_("nothing to merge: '%.*s'"), arg_len, arg); goto leave_merge; } if (opts->have_squash_onto && - !oidcmp(&head_commit->object.oid, &opts->squash_onto)) { + oideq(&head_commit->object.oid, &opts->squash_onto)) { /* * When the user tells us to "merge" something into a * "[new root]", let's simply fast-forward to the merge head. */ rollback_lock_file(&lock); - ret = fast_forward_to(&merge_commit->object.oid, - &head_commit->object.oid, 0, opts); + if (to_merge->next) + ret = error(_("octopus merge cannot be executed on " + "top of a [new root]")); + else + ret = fast_forward_to(&to_merge->item->object.oid, + &head_commit->object.oid, 0, + opts); goto leave_merge; } @@@ -3095,8 -3000,7 +3096,8 @@@ p = arg + oneline_offset; len = arg_len - oneline_offset; } else { - strbuf_addf(&buf, "Merge branch '%.*s'", + strbuf_addf(&buf, "Merge %s '%.*s'", + to_merge->next ? "branches" : "branch", merge_arg_len, arg); p = buf.buf; len = buf.len; @@@ -3116,87 -3020,39 +3117,87 @@@ * commit, we cannot fast-forward. */ can_fast_forward = opts->allow_ff && commit && commit->parents && - !oidcmp(&commit->parents->item->object.oid, - &head_commit->object.oid); + oideq(&commit->parents->item->object.oid, + &head_commit->object.oid); /* - * If the merge head is different from the original one, we cannot + * If any merge head is different from the original one, we cannot * fast-forward. */ if (can_fast_forward) { - struct commit_list *second_parent = commit->parents->next; + struct commit_list *p = commit->parents->next; - if (second_parent && !second_parent->next && - oidcmp(&merge_commit->object.oid, - &second_parent->item->object.oid)) + 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 && commit->parents->next && - !commit->parents->next->next && - !oidcmp(&commit->parents->next->item->object.oid, - &merge_commit->object.oid)) { + if (can_fast_forward) { rollback_lock_file(&lock); ret = fast_forward_to(&commit->object.oid, &head_commit->object.oid, 0, opts); goto leave_merge; } + if (to_merge->next) { + /* Octopus merge */ + struct child_process cmd = CHILD_PROCESS_INIT; + + if (read_env_script(&cmd.env_array)) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + + ret = error(_(staged_changes_advice), gpg_opt, gpg_opt); + goto leave_merge; + } + + cmd.git_cmd = 1; + argv_array_push(&cmd.args, "merge"); + argv_array_push(&cmd.args, "-s"); + argv_array_push(&cmd.args, "octopus"); + 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)); + if (opts->gpg_sign) + argv_array_push(&cmd.args, opts->gpg_sign); + + /* Add the tips to be merged */ + for (j = to_merge; j; j = j->next) + argv_array_push(&cmd.args, + oid_to_hex(&j->item->object.oid)); + + strbuf_release(&ref_name); + unlink(git_path_cherry_pick_head(the_repository)); + 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)) + ret = error(_("could not read index")); + goto leave_merge; + } + + merge_commit = to_merge->item; 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); bases = get_merge_bases(head_commit, merge_commit); - if (bases && !oidcmp(&merge_commit->object.oid, - &bases->item->object.oid)) { + if (bases && oideq(&merge_commit->object.oid, + &bases->item->object.oid)) { ret = 0; /* skip merging an ancestor of HEAD */ goto leave_merge; @@@ -3239,7 -3095,7 +3240,7 @@@ rollback_lock_file(&lock); if (ret) - rerere(opts->allow_rerere_auto); + repo_rerere(the_repository, opts->allow_rerere_auto); else /* * In case of problems, we now want to return a positive @@@ -3252,7 -3108,6 +3253,7 @@@ leave_merge: strbuf_release(&ref_name); rollback_lock_file(&lock); + free_commit_list(to_merge); return ret; } @@@ -3401,8 -3256,7 +3402,8 @@@ static int stopped_at_head(void struct commit *commit; struct commit_message message; - if (get_oid("HEAD", &head) || !(commit = lookup_commit(&head)) || + if (get_oid("HEAD", &head) || + !(commit = lookup_commit(the_repository, &head)) || parse_commit(commit) || get_message(commit, &message)) fprintf(stderr, _("Stopped at HEAD\n")); else { @@@ -3512,9 -3366,9 +3513,9 @@@ static int pick_commits(struct todo_lis */ if (item->command == TODO_REWORD && !get_oid("HEAD", &oid) && - (!oidcmp(&item->commit->object.oid, &oid) || + (oideq(&item->commit->object.oid, &oid) || (opts->have_squash_onto && - !oidcmp(&opts->squash_onto, &oid)))) + oideq(&opts->squash_onto, &oid)))) to_amend = 1; return res | error_with_patch(item->commit, @@@ -3640,7 -3494,7 +3641,7 @@@ cleanup_head_ref struct object_id orig, head; memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - init_revisions(&log_tree_opt, NULL); + repo_init_revisions(the_repository, &log_tree_opt, NULL); log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; @@@ -3729,7 -3583,7 +3730,7 @@@ static int commit_staged_changes(struc if (get_oid_hex(rev.buf, &to_amend)) return error(_("invalid contents: '%s'"), rebase_path_amend()); - if (!is_clean && oidcmp(&head, &to_amend)) + if (!is_clean && !oideq(&head, &to_amend)) return error(_("\nYou have uncommitted changes in your " "working tree. Please, commit them\n" "first and then run 'git rebase " @@@ -3741,20 -3595,9 +3742,20 @@@ * the commit message and if there was a squash, let the user * edit it. */ - if (is_clean && !oidcmp(&head, &to_amend) && - opts->current_fixup_count > 0 && - file_exists(rebase_path_stopped_sha())) { + if (!is_clean || !opts->current_fixup_count) + ; /* this is not the final fixup */ + else if (!oideq(&head, &to_amend) || + !file_exists(rebase_path_stopped_sha())) { + /* was a final fixup or squash done manually? */ + if (!is_fixup(peek_command(todo_list, 0))) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; + } + } else { + /* we are in a fixup/squash chain */ const char *p = opts->current_fixups.buf; int len = opts->current_fixups.len; @@@ -3919,7 -3762,7 +3920,7 @@@ int sequencer_pick_revisions(struct rep continue; if (!get_oid(name, &oid)) { - if (!lookup_commit_reference_gently(&oid, 1)) { + if (!lookup_commit_reference_gently(the_repository, &oid, 1)) { enum object_type type = oid_object_info(the_repository, &oid, NULL); @@@ -3945,10 -3788,8 +3946,10 @@@ if (prepare_revision_walk(opts->revs)) return error(_("revision walk setup failed")); cmit = get_revision(opts->revs); - if (!cmit || get_revision(opts->revs)) - return error("BUG: expected exactly one commit from walk"); + if (!cmit) + return error(_("empty commit set passed")); + if (get_revision(opts->revs)) + BUG("unexpected extra commit from walk"); return single_pick(cmit, opts); } @@@ -3973,7 -3814,7 +3974,7 @@@ return res; } -void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag) { unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; @@@ -4190,6 -4031,7 +4191,6 @@@ static int make_script_with_merges(stru */ while ((commit = get_revision(revs))) { struct commit_list *to_merge; - int is_octopus; const char *p1, *p2; struct object_id *oid; int is_empty; @@@ -4221,6 -4063,11 +4222,6 @@@ continue; } - is_octopus = to_merge && to_merge->next; - - if (is_octopus) - BUG("Octopus merges not yet supported"); - /* Create a label */ strbuf_reset(&label); if (skip_prefix(oneline.buf, "Merge ", &p1) && @@@ -4242,17 -4089,13 +4243,17 @@@ strbuf_addf(&buf, "%s -C %s", cmd_merge, oid_to_hex(&commit->object.oid)); - /* label the tip of merged branch */ - oid = &to_merge->item->object.oid; - strbuf_addch(&buf, ' '); + /* label the tips of merged branches */ + for (; to_merge; to_merge = to_merge->next) { + oid = &to_merge->item->object.oid; + strbuf_addch(&buf, ' '); + + if (!oidset_contains(&interesting, oid)) { + strbuf_addstr(&buf, label_oid(oid, NULL, + &state)); + continue; + } - if (!oidset_contains(&interesting, oid)) - strbuf_addstr(&buf, label_oid(oid, NULL, &state)); - else { tips_tail = &commit_list_insert(to_merge->item, tips_tail)->next; @@@ -4276,7 -4119,9 +4277,7 @@@ struct object_id *oid = &parent->item->object.oid; if (!oidset_contains(&interesting, oid)) continue; - if (!oidset_contains(&child_seen, oid)) - oidset_insert(&child_seen, oid); - else + if (oidset_insert(&child_seen, oid)) label_oid(oid, "branch-point", &state); } @@@ -4303,7 -4148,7 +4304,7 @@@ entry = oidmap_get(&state.commit2label, &commit->object.oid); if (entry) - fprintf(out, "\n# Branch %s\n", entry->string); + fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string); else fprintf(out, "\n"); @@@ -4384,7 -4229,7 +4385,7 @@@ int sequencer_make_script(FILE *out, in const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; int rebase_merges = flags & TODO_LIST_REBASE_MERGES; - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); revs.verbose_header = 1; if (!rebase_merges) revs.max_parents = 1; @@@ -4441,9 -4286,10 +4442,9 @@@ int sequencer_add_exec_commands(const c { const char *todo_file = rebase_path_todo(); struct todo_list todo_list = TODO_LIST_INIT; - struct todo_item *item; struct strbuf *buf = &todo_list.buf; size_t offset = 0, commands_len = strlen(commands); - int i, first; + int i, insert; if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); @@@ -4453,40 -4299,19 +4454,40 @@@ return error(_("unusable todo list: '%s'"), todo_file); } - first = 1; - /* insert 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); + /* + * Insert 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. + */ + 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; } - first = 0; + + if (command == TODO_PICK || command == TODO_MERGE) + insert = i + 1; } - /* append final */ - strbuf_add(buf, commands, commands_len); + /* insert or append final */ + 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); i = write_message(buf->buf, buf->len, todo_file, 0); todo_list_release(&todo_list); @@@ -4713,7 -4538,7 +4714,7 @@@ static int skip_unnecessary_picks(struc if (item->commit->parents->next) break; /* merge commit */ parent_oid = &item->commit->parents->item->object.oid; - if (hashcmp(parent_oid->hash, output_oid->hash)) + if (!oideq(parent_oid, output_oid)) break; oidcpy(output_oid, &item->commit->object.oid); } diff --combined t/lib-rebase.sh index 241f64b09b,86572438ec..f6c45ee08f --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@@ -14,8 -14,8 +14,8 @@@ # specified line. # # " " -- add a line with the specified command -# ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken -# from the specified line. +# ("pick", "squash", "fixup", "edit", "reword" or "drop") and the +# SHA1 taken from the specified line. # # "exec_cmd_with_args" -- add an "exec cmd with args" line. # @@@ -47,9 -47,9 +47,9 @@@ set_fake_editor () action=pick for line in $FAKE_LINES; do case $line in - squash|fixup|edit|reword|drop) + pick|squash|fixup|edit|reword|drop) action="$line";; - exec*|break) + exec*|break|b) echo "$line" | sed 's/_/ /g' >> "$1";; "#") echo '# comment' >> "$1";; diff --combined t/t3418-rebase-continue.sh index 75cd2d80df,b282505aac..4c3f7d8dfe --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@@ -60,7 -60,7 +60,7 @@@ test_expect_success 'rebase --continue EOF chmod +x test-bin/git-merge-funny && ( - PATH=./test-bin:$PATH + PATH=./test-bin:$PATH && test_must_fail git rebase -s funny -Xopt master topic ) && test -f funny.was.run && @@@ -68,7 -68,7 +68,7 @@@ echo "Resolved" >F2 && git add F2 && ( - PATH=./test-bin:$PATH + PATH=./test-bin:$PATH && git rebase --continue ) && test -f funny.was.run @@@ -160,15 -160,13 +160,15 @@@ test_expect_success '--skip after faile : The first squash was skipped, therefore: && git show HEAD >out && test_i18ngrep "# This is a combination of 2 commits" out && + test_i18ngrep "# This is the commit message #2:" out && (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) && git show HEAD >out && test_i18ngrep ! "# This is a combination" out && : Final squash failed, but there was still a squash && - test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt + test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt && + test_i18ngrep "# This is the commit message #2:" .git/copy.txt ' test_expect_success 'setup rerere database' ' @@@ -245,7 -243,9 +245,9 @@@ unset GIT_SEQUENCE_EDITO test_expect_success 'the todo command "break" works' ' rm -f execed && - FAKE_LINES="break exec_>execed" git rebase -i HEAD && + FAKE_LINES="break b exec_>execed" git rebase -i HEAD && + test_path_is_missing execed && + git rebase --continue && test_path_is_missing execed && git rebase --continue && test_path_is_file execed