diff options
Diffstat (limited to 'builtin/merge.c')
-rw-r--r-- | builtin/merge.c | 572 |
1 files changed, 367 insertions, 205 deletions
diff --git a/builtin/merge.c b/builtin/merge.c index 325891edb6..470fc57c5d 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -26,6 +26,8 @@ #include "merge-recursive.h" #include "resolve-undo.h" #include "remote.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -44,13 +46,12 @@ static const char * const builtin_merge_usage[] = { NULL }; -static int show_diffstat = 1, shortlog_len, squash; +static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only; +static int fast_forward_only, option_edit = -1; static int allow_trivial = 1, have_message; -static struct strbuf merge_msg; -static struct commit_list *remoteheads; -static unsigned char head[20], stash[20]; +static int overwrite_ignore = 1; +static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char **xopts; @@ -63,6 +64,7 @@ static int allow_rerere_auto; static int abort_current_merge; static int show_progress = -1; static int default_to_upstream; +static const char *sign_commit; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -190,6 +192,8 @@ static struct option builtin_merge_options[] = { "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), + OPT_BOOL('e', "edit", &option_edit, + "edit message before committing"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast-forward (default)"), OPT_BOOLEAN(0, "ff-only", &fast_forward_only, @@ -206,6 +210,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "abort", &abort_current_merge, "abort the current in-progress merge"), OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), OPT_END() }; @@ -217,7 +224,7 @@ static void drop_save(void) unlink(git_path("MERGE_MODE")); } -static void save_state(void) +static int save_state(unsigned char *stash) { int len; struct child_process cp; @@ -236,11 +243,12 @@ static void save_state(void) if (finish_command(&cp) || len < 0) die(_("stash failed")); - else if (!len) - return; + else if (!len) /* no changes */ + return -1; strbuf_setlen(&buffer, buffer.len-1); if (get_sha1(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); + return 0; } static void read_empty(unsigned const char *sha1, int verbose) @@ -278,7 +286,8 @@ static void reset_hard(unsigned const char *sha1, int verbose) die(_("read-tree failed")); } -static void restore_state(void) +static void restore_state(const unsigned char *head, + const unsigned char *stash) { struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; @@ -308,25 +317,25 @@ static void finish_up_to_date(const char *msg) drop_save(); } -static void squash_message(void) +static void squash_message(struct commit *commit, struct commit_list *remoteheads) { struct rev_info rev; - struct commit *commit; struct strbuf out = STRBUF_INIT; struct commit_list *j; + const char *filename; int fd; struct pretty_print_context ctx = {0}; printf(_("Squash commit -- not updating HEAD\n")); - fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); + filename = git_path("SQUASH_MSG"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) - die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG")); + die_errno(_("Could not write to '%s'"), filename); init_revisions(&rev, NULL); rev.ignore_merges = 1; rev.commit_format = CMIT_FMT_MEDIUM; - commit = lookup_commit(head); commit->object.flags |= UNINTERESTING; add_pending_object(&rev, &commit->object, NULL); @@ -355,9 +364,12 @@ static void squash_message(void) strbuf_release(&out); } -static void finish(const unsigned char *new_head, const char *msg) +static void finish(struct commit *head_commit, + struct commit_list *remoteheads, + const unsigned char *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; + const unsigned char *head = head_commit->object.sha1; if (!msg) strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); @@ -368,7 +380,7 @@ static void finish(const unsigned char *new_head, const char *msg) getenv("GIT_REFLOG_ACTION"), msg); } if (squash) { - squash_message(); + squash_message(head_commit, remoteheads); } else { if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); @@ -387,11 +399,11 @@ static void finish(const unsigned char *new_head, const char *msg) if (new_head && show_diffstat) { struct diff_options opts; diff_setup(&opts); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ opts.output_format |= DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); if (diff_setup_done(&opts) < 0) die(_("diff_setup_done failed")); diff_tree_sha1(head, new_head, "", &opts); @@ -408,8 +420,8 @@ static void finish(const unsigned char *new_head, const char *msg) /* Get the name for the merge commit's message. */ static void merge_name(const char *remote, struct strbuf *msg) { - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; + struct commit *remote_head; + unsigned char branch_head[20]; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; @@ -420,7 +432,7 @@ static void merge_name(const char *remote, struct strbuf *msg) remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); + remote_head = get_merge_parent(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); @@ -430,6 +442,11 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(branch_head), remote); goto cleanup; } + if (!prefixcmp(found_ref, "refs/tags/")) { + strbuf_addf(msg, "%s\t\ttag '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } if (!prefixcmp(found_ref, "refs/remotes/")) { strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); @@ -468,10 +485,10 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { + if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), + sha1_to_hex(remote_head->object.sha1), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -481,14 +498,16 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!strcmp(remote, "FETCH_HEAD") && !access(git_path("FETCH_HEAD"), R_OK)) { + const char *filename; FILE *fp; struct strbuf line = STRBUF_INIT; char *ptr; - fp = fopen(git_path("FETCH_HEAD"), "r"); + filename = git_path("FETCH_HEAD"); + fp = fopen(filename, "r"); if (!fp) die_errno(_("could not open '%s' for reading"), - git_path("FETCH_HEAD")); + filename); strbuf_getline(&line, fp, '\n'); fclose(fp); ptr = strstr(line.buf, "\tnot-for-merge\t"); @@ -499,7 +518,7 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); + sha1_to_hex(remote_head->object.sha1), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); @@ -527,6 +546,8 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { + int status; + if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { @@ -543,15 +564,7 @@ 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, "merge.renormalize")) option_renormalize = git_config_bool(k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { - int is_bool; - shortlog_len = git_config_bool_or_int(k, v, &is_bool); - if (!is_bool && shortlog_len < 0) - return error(_("%s: negative length %s"), k, v); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; - return 0; - } else if (!strcmp(k, "merge.ff")) { + else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { allow_fast_forward = boolval; @@ -564,6 +577,13 @@ static int git_merge_config(const char *k, const char *v, void *cb) default_to_upstream = git_config_bool(k, v); return 0; } + + status = fmt_merge_msg_config(k, v, cb); + if (status) + return status; + status = git_gpg_config(k, v, NULL); + if (status) + return status; return git_diff_ui_config(k, v, cb); } @@ -663,7 +683,8 @@ int try_merge_command(const char *strategy, size_t xopts_nr, } static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) + struct commit_list *remoteheads, + struct commit *head, const char *head_arg) { int index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); @@ -703,13 +724,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; + o.branch2 = merge_remote_util(remoteheads->item)->name; for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(&o, lookup_commit(head), + clean = merge_recursive(&o, head, remoteheads->item, reversed, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || @@ -758,10 +779,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; + if (overwrite_ignore) { + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(&dir); + opts.dir = &dir; + } opts.head_idx = 1; opts.src_index = &the_index; @@ -834,77 +857,110 @@ static void add_strategies(const char *string, unsigned attr) } -static void write_merge_msg(void) +static void write_merge_msg(struct strbuf *msg) { - int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); + const char *filename = git_path("MERGE_MSG"); + int fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG")); + filename); + if (write_in_full(fd, msg->buf, msg->len) != msg->len) + die_errno(_("Could not write to '%s'"), filename); close(fd); } -static void read_merge_msg(void) +static void read_merge_msg(struct strbuf *msg) +{ + const char *filename = git_path("MERGE_MSG"); + strbuf_reset(msg); + if (strbuf_read_file(msg, filename, 0) < 0) + die_errno(_("Could not read from '%s'"), filename); +} + +static void write_merge_state(struct commit_list *); +static void abort_commit(struct commit_list *remoteheads, const char *err_msg) { - strbuf_reset(&merge_msg); - if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0) - die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG")); + if (err_msg) + error("%s", err_msg); + fprintf(stderr, + _("Not committing merge; use 'git commit' to complete the merge.\n")); + write_merge_state(remoteheads); + exit(1); } -static void run_prepare_commit_msg(void) +static const char merge_editor_comment[] = +N_("Please enter a commit message to explain why this merge is necessary,\n" + "especially if it merges an updated upstream into a topic branch.\n" + "\n" + "Lines starting with '#' will be ignored, and an empty message aborts\n" + "the commit.\n"); + +static void prepare_to_commit(struct commit_list *remoteheads) { - write_merge_msg(); + struct strbuf msg = STRBUF_INIT; + const char *comment = _(merge_editor_comment); + strbuf_addbuf(&msg, &merge_msg); + strbuf_addch(&msg, '\n'); + if (0 < option_edit) + strbuf_add_lines(&msg, "# ", comment, strlen(comment)); + write_merge_msg(&msg); run_hook(get_index_file(), "prepare-commit-msg", git_path("MERGE_MSG"), "merge", NULL, NULL); - read_merge_msg(); + if (0 < option_edit) { + if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) + abort_commit(remoteheads, NULL); + } + read_merge_msg(&msg); + stripspace(&msg, 0 < option_edit); + if (!msg.len) + abort_commit(remoteheads, _("Empty commit message.")); + strbuf_release(&merge_msg); + strbuf_addbuf(&merge_msg, &msg); + strbuf_release(&msg); } -static int merge_trivial(void) +static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parent = xmalloc(sizeof(*parent)); write_tree_trivial(result_tree); printf(_("Wonderful.\n")); - parent->item = lookup_commit(head); + parent->item = head; parent->next = xmalloc(sizeof(*parent->next)); parent->next->item = remoteheads->item; parent->next->next = NULL; - run_prepare_commit_msg(); - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); - finish(result_commit, "In-index merge"); + prepare_to_commit(remoteheads); + if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL, + sign_commit)) + die(_("failed to write commit object")); + finish(head, remoteheads, result_commit, "In-index merge"); drop_save(); return 0; } -static int finish_automerge(struct commit_list *common, +static int finish_automerge(struct commit *head, + int head_subsumed, + struct commit_list *common, + struct commit_list *remoteheads, unsigned char *result_tree, const char *wt_strategy) { - struct commit_list *parents = NULL, *j; + struct commit_list *parents = NULL; struct strbuf buf = STRBUF_INIT; unsigned char result_commit[20]; free_commit_list(common); - if (allow_fast_forward) { - parents = remoteheads; - commit_list_insert(lookup_commit(head), &parents); - parents = reduce_heads(parents); - } else { - struct commit_list **pptr = &parents; - - pptr = &commit_list_insert(lookup_commit(head), - pptr)->next; - for (j = remoteheads; j; j = j->next) - pptr = &commit_list_insert(j->item, pptr)->next; - } - free_commit_list(remoteheads); + parents = remoteheads; + if (!head_subsumed || !allow_fast_forward) + commit_list_insert(head, &parents); strbuf_addch(&merge_msg, '\n'); - run_prepare_commit_msg(); - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); - finish(result_commit, buf.buf); + prepare_to_commit(remoteheads); + if (commit_tree(&merge_msg, result_tree, parents, result_commit, + NULL, sign_commit)) + die(_("failed to write commit object")); + strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); + finish(head, remoteheads, result_commit, buf.buf); strbuf_release(&buf); drop_save(); return 0; @@ -912,13 +968,14 @@ static int finish_automerge(struct commit_list *common, static int suggest_conflicts(int renormalizing) { + const char *filename; FILE *fp; int pos; - fp = fopen(git_path("MERGE_MSG"), "a"); + filename = git_path("MERGE_MSG"); + fp = fopen(filename, "a"); if (!fp) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MSG")); + die_errno(_("Could not open '%s' for writing"), filename); fprintf(fp, "\nConflicts:\n"); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -938,7 +995,8 @@ static int suggest_conflicts(int renormalizing) return 1; } -static struct commit *is_old_style_invocation(int argc, const char **argv) +static struct commit *is_old_style_invocation(int argc, const char **argv, + const unsigned char *head) { struct commit *second_token = NULL; if (argc > 2) { @@ -1007,16 +1065,119 @@ static int setup_with_upstream(const char ***argv) return i; } +static void write_merge_state(struct commit_list *remoteheads) +{ + const char *filename; + int fd; + struct commit_list *j; + struct strbuf buf = STRBUF_INIT; + + for (j = remoteheads; j; j = j->next) { + unsigned const char *sha1; + struct commit *c = j->item; + if (c->util && merge_remote_util(c)->obj) { + sha1 = merge_remote_util(c)->obj->sha1; + } else { + sha1 = c->object.sha1; + } + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + } + filename = git_path("MERGE_HEAD"); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); + strbuf_addch(&merge_msg, '\n'); + write_merge_msg(&merge_msg); + + filename = git_path("MERGE_MODE"); + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + strbuf_reset(&buf); + if (!allow_fast_forward) + strbuf_addf(&buf, "no-ff"); + if (write_in_full(fd, buf.buf, buf.len) != buf.len) + die_errno(_("Could not write to '%s'"), filename); + close(fd); +} + +static int default_edit_option(void) +{ + static const char name[] = "GIT_MERGE_AUTOEDIT"; + const char *e = getenv(name); + struct stat st_stdin, st_stdout; + + if (have_message) + /* an explicit -m msg without --[no-]edit */ + return 0; + + if (e) { + int v = git_config_maybe_bool(name, e); + if (v < 0) + die("Bad value '%s' in environment '%s'", e, name); + return v; + } + + /* Use editor if stdin and stdout are the same and is a tty */ + return (!fstat(0, &st_stdin) && + !fstat(1, &st_stdout) && + isatty(0) && isatty(1) && + st_stdin.st_dev == st_stdout.st_dev && + st_stdin.st_ino == st_stdout.st_ino && + st_stdin.st_mode == st_stdout.st_mode); +} + +static struct commit_list *collect_parents(struct commit *head_commit, + int *head_subsumed, + int argc, const char **argv) +{ + int i; + struct commit_list *remoteheads = NULL, *parents, *next; + struct commit_list **remotes = &remoteheads; + + if (head_commit) + remotes = &commit_list_insert(head_commit, remotes)->next; + for (i = 0; i < argc; i++) { + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) + die(_("%s - not something we can merge"), argv[i]); + remotes = &commit_list_insert(commit, remotes)->next; + } + *remotes = NULL; + + parents = reduce_heads(remoteheads); + + *head_subsumed = 1; /* we will flip this to 0 when we find it */ + for (remoteheads = NULL, remotes = &remoteheads; + parents; + parents = next) { + struct commit *commit = parents->item; + next = parents->next; + if (commit == head_commit) + *head_subsumed = 0; + else + remotes = &commit_list_insert(commit, remotes)->next; + } + return remoteheads; +} + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; + unsigned char stash[20]; + unsigned char head_sha1[20]; + struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, head_invalid = 0, i; + int flag, i, ret = 0, head_subsumed; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list **remotes = &remoteheads; + struct commit_list *remoteheads, *p; + void *branch_to_free; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1025,22 +1186,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = resolve_ref("HEAD", head, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); if (branch && !prefixcmp(branch, "refs/heads/")) branch += 11; - if (is_null_sha1(head)) - head_invalid = 1; + if (!branch || is_null_sha1(head_sha1)) + head_commit = NULL; + else + head_commit = lookup_commit_or_die(head_sha1, "HEAD"); git_config(git_merge_config, NULL); - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - if (branch_mergeoptions) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + if (shortlog_len < 0) + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (verbosity < 0 && show_progress == -1) show_progress = 0; @@ -1053,7 +1214,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ - return cmd_reset(nargc, nargv, prefix); + ret = cmd_reset(nargc, nargv, prefix); + goto done; } if (read_cache_unmerged()) @@ -1092,9 +1254,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("You cannot combine --no-ff with --ff-only.")); if (!abort_current_merge) { - if (!argc && default_to_upstream) - argc = setup_with_upstream(&argv); - else if (argc == 1 && !strcmp(argv[0], "-")) + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) argv[0] = "@{-1}"; } if (!argc) @@ -1110,13 +1275,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * additional safety measure to check for it. */ - if (!have_message && is_old_style_invocation(argc, argv)) { + if (!have_message && head_commit && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { strbuf_addstr(&merge_msg, argv[0]); head_arg = argv[1]; argv += 2; argc -= 2; - } else if (head_invalid) { - struct object *remote_head; + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + } else if (!head_commit) { + struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. @@ -1130,13 +1297,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!allow_fast_forward) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + remote_head = remoteheads->item; if (!remote_head) die(_("%s - not something we can merge"), argv[0]); - read_empty(remote_head->sha1, 0); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - return 0; + read_empty(remote_head->object.sha1, 0); + update_ref("initial pull", "HEAD", remote_head->object.sha1, + NULL, 0, DIE_ON_ERR); + goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1144,52 +1312,56 @@ int cmd_merge(int argc, const char **argv, const char *prefix) head_arg = "HEAD"; /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. + * All the rest are the commits being merged; prepare + * the standard merge summary message to be appended + * to the given message. */ - for (i = 0; i < argc; i++) - merge_name(argv[i], &merge_names); + remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + for (p = remoteheads; p; p = p->next) + merge_name(merge_remote_util(p->item)->name, &merge_names); if (!have_message || shortlog_len) { - fmt_merge_msg(&merge_names, &merge_msg, !have_message, - shortlog_len); + struct fmt_merge_msg_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + + fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) strbuf_setlen(&merge_msg, merge_msg.len - 1); } } - if (head_invalid || !argc) + if (!head_commit || !argc) usage_with_options(builtin_merge_usage, builtin_merge_options); strbuf_addstr(&buf, "merge"); - for (i = 0; i < argc; i++) - strbuf_addf(&buf, " %s", argv[i]); + for (p = remoteheads; p; p = p->next) + strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name); setenv("GIT_REFLOG_ACTION", buf.buf, 0); strbuf_reset(&buf); - for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); - if (!o) - die(_("%s - not something we can merge"), argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; - remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); - setenv(buf.buf, argv[i], 1); + for (p = remoteheads; p; p = p->next) { + struct commit *commit = p->item; + strbuf_addf(&buf, "GITHEAD_%s", + sha1_to_hex(commit->object.sha1)); + setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); + if (!fast_forward_only && + merge_remote_util(commit) && + merge_remote_util(commit)->obj && + merge_remote_util(commit)->obj->type == OBJ_TAG) + allow_fast_forward = 0; } + if (option_edit < 0) + option_edit = default_edit_option(); + if (!use_strategies) { - if (!remoteheads->next) + if (!remoteheads) + ; /* already up-to-date */ + else if (!remoteheads->next) add_strategies(pull_twohead, DEFAULT_TWOHEAD); else add_strategies(pull_octopus, DEFAULT_OCTOPUS); @@ -1202,38 +1374,40 @@ int cmd_merge(int argc, const char **argv, const char *prefix) allow_trivial = 0; } - if (!remoteheads->next) - common = get_merge_bases(lookup_commit(head), - remoteheads->item, 1); + if (!remoteheads) + ; /* already up-to-date */ + else if (!remoteheads->next) + common = get_merge_bases(head_commit, remoteheads->item, 1); else { struct commit_list *list = remoteheads; - commit_list_insert(lookup_commit(head), &list); + commit_list_insert(head_commit, &list); common = get_octopus_merge_bases(list); free(list); } - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, - DIE_ON_ERR); + update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1, + NULL, 0, DIE_ON_ERR); - if (!common) + if (remoteheads && !common) ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads->next && !common->next && - common->item == remoteheads->item) { + else if (!remoteheads || + (!remoteheads->next && !common->next && + common->item == remoteheads->item)) { /* * 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."); - return 0; + goto done; } else if (allow_fast_forward && !remoteheads->next && !common->next && - !hashcmp(common->item->object.sha1, head)) { + !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; - struct object *o; + struct commit *commit; char hex[41]; - strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); + strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); if (verbosity >= 0) printf(_("Updating %s..%s\n"), @@ -1244,17 +1418,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); - if (!o) - return 1; + commit = remoteheads->item; + if (!commit) { + ret = 1; + goto done; + } - if (checkout_fast_forward(head, remoteheads->item->object.sha1)) - return 1; + if (checkout_fast_forward(head_commit->object.sha1, + commit->object.sha1)) { + ret = 1; + goto done; + } - finish(o->sha1, msg.buf); + finish(head_commit, remoteheads, commit->object.sha1, msg.buf); drop_save(); - return 0; + goto done; } else if (!remoteheads->next && common->next) ; /* @@ -1272,8 +1450,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) git_committer_info(IDENT_ERROR_ON_NO_NAME); printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, - head, remoteheads->item->object.sha1)) - return merge_trivial(); + head_commit->object.sha1, + remoteheads->item->object.sha1)) { + ret = merge_trivial(head_commit, remoteheads); + goto done; + } printf(_("Nope.\n")); } } else { @@ -1292,8 +1473,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * merge_bases again, otherwise "git merge HEAD^ * HEAD^^" would be missed. */ - common_one = get_merge_bases(lookup_commit(head), - j->item, 1); + common_one = get_merge_bases(head_commit, j->item, 1); if (hashcmp(common_one->item->object.sha1, j->item->object.sha1)) { up_to_date = 0; @@ -1302,7 +1482,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (up_to_date) { finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; + goto done; } } @@ -1320,21 +1500,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * sync with the head commit. The strategies are responsible * to ensure this. */ - if (use_strategies_nr != 1) { - /* - * Stash away the local changes so that we can try more - * than one. - */ - save_state(); - } else { - memcpy(stash, null_sha1, 20); - } + if (use_strategies_nr == 1 || + /* + * Stash away the local changes so that we can try more than one. + */ + save_state(stash)) + hashcpy(stash, null_sha1); for (i = 0; i < use_strategies_nr; i++) { int ret; if (i) { printf(_("Rewinding the tree to pristine...\n")); - restore_state(); + restore_state(head_commit->object.sha1, stash); } if (use_strategies_nr != 1) printf(_("Trying merge strategy %s...\n"), @@ -1346,7 +1523,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) wt_strategy = use_strategies[i]->name; ret = try_merge_strategy(use_strategies[i]->name, - common, head_arg); + common, remoteheads, + head_commit, head_arg); if (!option_commit && !ret) { merge_was_ok = 1; /* @@ -1387,66 +1565,50 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) - return finish_automerge(common, result_tree, wt_strategy); + if (automerge_was_ok) { + ret = finish_automerge(head_commit, head_subsumed, + common, remoteheads, + result_tree, wt_strategy); + goto done; + } /* * Pick the result from the best strategy and have the user fix * it up. */ if (!best_strategy) { - restore_state(); + restore_state(head_commit->object.sha1, stash); if (use_strategies_nr > 1) fprintf(stderr, _("No merge strategy handled the merge.\n")); else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - return 2; + ret = 2; + goto done; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { printf(_("Rewinding the tree to pristine...\n")); - restore_state(); + restore_state(head_commit->object.sha1, stash); printf(_("Using the %s to prepare resolving by hand.\n"), best_strategy); - try_merge_strategy(best_strategy, common, head_arg); + try_merge_strategy(best_strategy, common, remoteheads, + head_commit, head_arg); } if (squash) - finish(NULL, NULL); - else { - int fd; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD")); - close(fd); - strbuf_addch(&merge_msg, '\n'); - write_merge_msg(); - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), - git_path("MERGE_MODE")); - strbuf_reset(&buf); - if (!allow_fast_forward) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE")); - close(fd); - } + finish(head_commit, remoteheads, NULL, NULL); + else + write_merge_state(remoteheads); - if (merge_was_ok) { + if (merge_was_ok) fprintf(stderr, _("Automatic merge went well; " "stopped before committing as requested\n")); - return 0; - } else - return suggest_conflicts(option_renormalize); + else + ret = suggest_conflicts(option_renormalize); + +done: + free(branch_to_free); + return ret; } |