diff options
Diffstat (limited to 'builtin/merge.c')
-rw-r--r-- | builtin/merge.c | 148 |
1 files changed, 106 insertions, 42 deletions
diff --git a/builtin/merge.c b/builtin/merge.c index 062e911441..42a1989d4a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -14,6 +14,7 @@ #include "lockfile.h" #include "run-command.h" #include "diff.h" +#include "diff-merges.h" #include "refs.h" #include "refspec.h" #include "commit.h" @@ -28,6 +29,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "resolve-undo.h" #include "remote.h" #include "fmt-merge-msg.h" @@ -40,6 +42,7 @@ #include "branch.h" #include "commit-reach.h" #include "wt-status.h" +#include "commit-graph.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -53,8 +56,8 @@ struct strategy { static const char * const builtin_merge_usage[] = { N_("git merge [<options>] [<commit>...]"), - N_("git merge --abort"), - N_("git merge --continue"), + "git merge --abort", + "git merge --continue", NULL }; @@ -62,6 +65,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = -1; static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; +static int check_trust_level = 1; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -70,7 +74,6 @@ static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; static char *branch_mergeoptions; -static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; @@ -81,11 +84,13 @@ static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; +static int autostash; static int no_verify; static struct strategy all_strategy[] = { - { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, + { "recursive", NO_TRIVIAL }, { "octopus", DEFAULT_OCTOPUS }, + { "ort", DEFAULT_TWOHEAD | NO_TRIVIAL }, { "resolve", 0 }, { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, @@ -157,10 +162,17 @@ static struct strategy *get_strategy(const char *name) struct strategy *ret; static struct cmdnames main_cmds, other_cmds; static int loaded; + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); if (!name) return NULL; + if (default_strategy && + !strcmp(default_strategy, "ort") && + !strcmp(name, "recursive")) { + name = "ort"; + } + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) if (!strcmp(name, all_strategy[i].name)) return &all_strategy[i]; @@ -198,7 +210,7 @@ static struct strategy *get_strategy(const char *name) exit(1); } - ret = xcalloc(1, sizeof(struct strategy)); + CALLOC_ARRAY(ret, 1); ret->name = xstrdup(name); ret->attr = NO_TRIVIAL; return ret; @@ -240,9 +252,9 @@ static int option_parse_n(const struct option *opt, } static struct option builtin_merge_options[] = { - { OPTION_CALLBACK, 'n', NULL, NULL, NULL, + OPT_CALLBACK_F('n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG, option_parse_n }, + PARSE_OPT_NOARG, option_parse_n), OPT_BOOL(0, "stat", &show_diffstat, N_("show a diffstat at the end of the merge")), OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), @@ -285,8 +297,9 @@ static struct option builtin_merge_options[] = { OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), - OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), OPT_END() }; @@ -380,10 +393,14 @@ static void restore_state(const struct object_id *head, } /* This is called when no merge was necessary. */ -static void finish_up_to_date(const char *msg) +static void finish_up_to_date(void) { - if (verbosity >= 0) - printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); + if (verbosity >= 0) { + if (squash) + puts(_("Already up to date. (nothing to squash)")); + else + puts(_("Already up to date.")); + } remove_merge_branch_state(the_repository); } @@ -397,7 +414,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead printf(_("Squash commit -- not updating HEAD\n")); repo_init_revisions(the_repository, &rev, NULL); - rev.ignore_merges = 1; + diff_merges_suppress(&rev); rev.commit_format = CMIT_FMT_MEDIUM; commit->object.flags |= UNINTERESTING; @@ -446,7 +463,6 @@ static void finish(struct commit *head_commit, if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, UPDATE_REFS_DIE_ON_ERR); /* @@ -454,7 +470,7 @@ static void finish(struct commit *head_commit, * user should see them. */ close_object_store(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_maintenance(verbosity < 0); } } if (new_head && show_diffstat) { @@ -474,6 +490,7 @@ static void finish(struct commit *head_commit, /* Run a post-merge hook */ run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); } @@ -486,7 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg) struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; - char *found_ref; + char *found_ref = NULL; int len, early; strbuf_branchname(&bname, remote, 0); @@ -497,7 +514,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!remote_head) die(_("'%s' does not point to a commit"), remote); - if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) { + if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) { if (starts_with(found_ref, "refs/heads/")) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", oid_to_hex(&branch_head), remote); @@ -569,6 +586,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addf(msg, "%s\t\tcommit '%s'\n", oid_to_hex(&remote_head->object.oid), remote); cleanup: + free(found_ref); strbuf_release(&buf); strbuf_release(&bname); } @@ -596,10 +614,12 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { int status; + const char *str; - if (branch && starts_with(k, "branch.") && - starts_with(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { + if (branch && + skip_prefix(k, "branch.", &str) && + skip_prefix(str, branch, &str) && + !strcmp(str, ".mergeoptions")) { free(branch_mergeoptions); branch_mergeoptions = xstrdup(v); return 0; @@ -615,8 +635,6 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "commit.cleanup")) return git_config_string(&cleanup_arg, k, v); - else if (!strcmp(k, "merge.renormalize")) - option_renormalize = git_config_bool(k, v); else if (!strcmp(k, "merge.ff")) { int boolval = git_parse_maybe_bool(v); if (0 <= boolval) { @@ -631,6 +649,11 @@ static int git_merge_config(const char *k, const char *v, void *cb) } else if (!strcmp(k, "commit.gpgsign")) { sign_commit = git_config_bool(k, v) ? "" : NULL; return 0; + } else if (!strcmp(k, "gpg.mintrustlevel")) { + check_trust_level = 0; + } else if (!strcmp(k, "merge.autostash")) { + autostash = git_config_bool(k, v); + return 0; } status = fmt_merge_msg_config(k, v, cb); @@ -693,7 +716,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); - if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree") || + !strcmp(strategy, "ort")) { struct lock_file lock = LOCK_INIT; int clean, x; struct commit *result; @@ -710,13 +734,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; - o.renormalize = option_renormalize; o.show_rename_progress = show_progress == -1 ? isatty(2) : show_progress; for (x = 0; x < xopts_nr; x++) if (parse_merge_opt(&o, xopts[x])) - die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); + die(_("unknown strategy option: -X%s"), xopts[x]); o.branch1 = head_arg; o.branch2 = merge_remote_util(remoteheads->item)->name; @@ -725,8 +748,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, commit_list_insert(j->item, &reversed); hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - clean = merge_recursive(&o, head, - remoteheads->item, reversed, &result); + if (!strcmp(strategy, "ort")) + clean = merge_ort_recursive(&o, head, remoteheads->item, + reversed, &result); + else + clean = merge_recursive(&o, head, remoteheads->item, + reversed, &result); if (clean < 0) exit(128); if (write_locked_index(&the_index, &lock, @@ -835,9 +862,11 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_commented_addf(&msg, "\n"); } strbuf_commented_addf(&msg, _(merge_editor_comment)); - strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ? - scissors_editor_comment : - no_scissors_editor_comment), comment_line_char); + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) + strbuf_commented_addf(&msg, _(scissors_editor_comment)); + else + strbuf_commented_addf(&msg, + _(no_scissors_editor_comment), comment_line_char); } if (signoff) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); @@ -1109,9 +1138,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge merge_names = &fetch_head_file; filename = git_path_fetch_head(the_repository); - fd = open(filename, O_RDONLY); - if (fd < 0) - die_errno(_("could not open '%s' for reading"), filename); + fd = xopen(filename, O_RDONLY); if (strbuf_read(merge_names, fd, 0) < 0) die_errno(_("could not read '%s'"), filename); @@ -1257,6 +1284,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (branch) skip_prefix(branch, "refs/heads/", &branch); + if (!pull_twohead) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy && !strcmp(default_strategy, "ort")) + pull_twohead = "ort"; + } + init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@ -1278,6 +1311,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + struct strbuf stash_oid = STRBUF_INIT; if (orig_argc != 2) usage_msg_opt(_("--abort expects no arguments"), @@ -1286,8 +1320,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); + if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository), + READ_ONELINER_SKIP_IF_EMPTY)) + unlink(git_path_merge_autostash(the_repository)); + /* Invoke 'git reset --merge' */ ret = cmd_reset(nargc, nargv, prefix); + + if (stash_oid.len) + apply_autostash_oid(stash_oid.buf); + + strbuf_release(&stash_oid); goto done; } @@ -1331,7 +1374,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path_cherry_pick_head(the_repository))) { + if (ref_exists("CHERRY_PICK_HEAD")) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); @@ -1397,7 +1440,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("Can merge only exactly one commit into empty head")); if (verify_signatures) - verify_merge_signature(remoteheads->item, verbosity); + verify_merge_signature(remoteheads->item, verbosity, + check_trust_level); remote_head_oid = &remoteheads->item->object.oid; read_empty(remote_head_oid, 0); @@ -1420,7 +1464,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (verify_signatures) { for (p = remoteheads; p; p = p->next) { - verify_merge_signature(p->item, verbosity); + verify_merge_signature(p->item, verbosity, + check_trust_level); } } @@ -1440,6 +1485,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fast_forward = FF_NO; } + if (!use_strategies && !pull_twohead && + remoteheads && !remoteheads->next) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy) + append_strategy(get_strategy(default_strategy)); + } if (!use_strategies) { if (!remoteheads) ; /* already up-to-date */ @@ -1482,7 +1533,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If head can reach all the merge then we are up to date. * but first the most common case of merging one remote. */ - finish_up_to_date(_("Already up to date.")); + finish_up_to_date(); goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && @@ -1508,10 +1559,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, overwrite_ignore)) { + apply_autostash(git_path_merge_autostash(the_repository)); ret = 1; goto done; } @@ -1566,13 +1622,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } if (up_to_date) { - finish_up_to_date(_("Already up to date. Yeeah!")); + finish_up_to_date(); goto done; } } if (fast_forward == FF_ONLY) - die(_("Not possible to fast-forward, aborting.")); + die_ff_impossible(); + + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1624,7 +1685,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } merge_was_ok = 1; } - cnt = evaluate_result(); + cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; if (best_cnt <= 0 || cnt <= best_cnt) { best_strategy = use_strategies[i]->name; best_cnt = cnt; @@ -1655,6 +1716,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); + apply_autostash(git_path_merge_autostash(the_repository)); ret = 2; goto done; } else if (best_strategy == wt_strategy) @@ -1662,15 +1724,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else { printf(_("Rewinding the tree to pristine...\n")); restore_state(&head_commit->object.oid, &stash); - printf(_("Using the %s to prepare resolving by hand.\n"), + printf(_("Using the %s strategy to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, head_commit); } - if (squash) + if (squash) { finish(head_commit, remoteheads, NULL, NULL); - else + + git_test_write_commit_graph_or_die(); + } else write_merge_state(remoteheads); if (merge_was_ok) |