diff options
Diffstat (limited to 'merge-ort.c')
-rw-r--r-- | merge-ort.c | 1117 |
1 files changed, 1083 insertions, 34 deletions
diff --git a/merge-ort.c b/merge-ort.c index 31103d2140..6900ab9e7f 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -25,8 +25,11 @@ #include "diff.h" #include "diffcore.h" #include "dir.h" +#include "ll-merge.h" #include "object-store.h" +#include "revision.h" #include "strmap.h" +#include "submodule.h" #include "tree.h" #include "unpack-trees.h" #include "xdiff-interface.h" @@ -48,6 +51,25 @@ enum merge_side { MERGE_SIDE2 = 2 }; +struct rename_info { + /* + * pairs: pairing of filenames from diffcore_rename() + * + * Index 1 and 2 correspond to sides 1 & 2 as used in + * conflict_info.stages. Index 0 unused. + */ + struct diff_queue_struct pairs[3]; + + /* + * needed_limit: value needed for inexact rename detection to run + * + * If the current rename limit wasn't high enough for inexact + * rename detection to run, this records the limit needed. Otherwise, + * this value remains 0. + */ + int needed_limit; +}; + struct merge_options_internal { /* * paths: primary data structure in all of merge ort. @@ -116,6 +138,11 @@ struct merge_options_internal { struct strmap output; /* + * renames: various data relating to rename detection + */ + struct rename_info renames; + + /* * current_dir_name: temporary var used in collect_merge_info_callback() * * Used to set merged_info.directory_name; see documentation for that @@ -325,6 +352,25 @@ static int err(struct merge_options *opt, const char *err, ...) return -1; } +static void format_commit(struct strbuf *sb, + int indent, + struct commit *commit) +{ + struct merge_remote_desc *desc; + struct pretty_print_context ctx = {0}; + ctx.abbrev = DEFAULT_ABBREV; + + strbuf_addchars(sb, ' ', indent); + desc = merge_remote_util(commit); + if (desc) { + strbuf_addf(sb, "virtual %s\n", desc->name); + return; + } + + format_commit_message(commit, "%h %s", sb, &ctx); + strbuf_addch(sb, '\n'); +} + __attribute__((format (printf, 4, 5))) static void path_msg(struct merge_options *opt, const char *path, @@ -346,6 +392,36 @@ static void path_msg(struct merge_options *opt, strbuf_addch(sb, '\n'); } +/* add a string to a strbuf, but converting "/" to "_" */ +static void add_flattened_path(struct strbuf *out, const char *s) +{ + size_t i = out->len; + strbuf_addstr(out, s); + for (; i < out->len; i++) + if (out->buf[i] == '/') + out->buf[i] = '_'; +} + +static char *unique_path(struct strmap *existing_paths, + const char *path, + const char *branch) +{ + struct strbuf newpath = STRBUF_INIT; + int suffix = 0; + size_t base_len; + + strbuf_addf(&newpath, "%s~", path); + add_flattened_path(&newpath, branch); + + base_len = newpath.len; + while (strmap_contains(existing_paths, newpath.buf)) { + strbuf_setlen(&newpath, base_len); + strbuf_addf(&newpath, "_%d", suffix++); + } + + return strbuf_detach(&newpath, NULL); +} + /*** Function Grouping: functions related to collect_merge_info() ***/ static void setup_path_info(struct merge_options *opt, @@ -604,6 +680,249 @@ static int collect_merge_info(struct merge_options *opt, /*** Function Grouping: functions related to threeway content merges ***/ +static int find_first_merges(struct repository *repo, + const char *path, + struct commit *a, + struct commit *b, + struct object_array *result) +{ + int i, j; + struct object_array merges = OBJECT_ARRAY_INIT; + struct commit *commit; + int contains_another; + + char merged_revision[GIT_MAX_HEXSZ + 2]; + const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path", + "--all", merged_revision, NULL }; + struct rev_info revs; + struct setup_revision_opt rev_opts; + + memset(result, 0, sizeof(struct object_array)); + memset(&rev_opts, 0, sizeof(rev_opts)); + + /* get all revisions that merge commit a */ + xsnprintf(merged_revision, sizeof(merged_revision), "^%s", + oid_to_hex(&a->object.oid)); + repo_init_revisions(repo, &revs, NULL); + rev_opts.submodule = path; + /* FIXME: can't handle linked worktrees in submodules yet */ + revs.single_worktree = path != NULL; + setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); + + /* save all revisions from the above list that contain b */ + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + while ((commit = get_revision(&revs)) != NULL) { + struct object *o = &(commit->object); + if (in_merge_bases(b, commit)) + add_object_array(o, NULL, &merges); + } + reset_revision_walk(); + + /* Now we've got all merges that contain a and b. Prune all + * merges that contain another found merge and save them in + * result. + */ + for (i = 0; i < merges.nr; i++) { + struct commit *m1 = (struct commit *) merges.objects[i].item; + + contains_another = 0; + for (j = 0; j < merges.nr; j++) { + struct commit *m2 = (struct commit *) merges.objects[j].item; + if (i != j && in_merge_bases(m2, m1)) { + contains_another = 1; + break; + } + } + + if (!contains_another) + add_object_array(merges.objects[i].item, NULL, result); + } + + object_array_clear(&merges); + return result->nr; +} + +static int merge_submodule(struct merge_options *opt, + const char *path, + const struct object_id *o, + const struct object_id *a, + const struct object_id *b, + struct object_id *result) +{ + struct commit *commit_o, *commit_a, *commit_b; + int parent_count; + struct object_array merges; + struct strbuf sb = STRBUF_INIT; + + int i; + int search = !opt->priv->call_depth; + + /* store fallback answer in result in case we fail */ + oidcpy(result, opt->priv->call_depth ? o : a); + + /* we can not handle deletion conflicts */ + if (is_null_oid(o)) + return 0; + if (is_null_oid(a)) + return 0; + if (is_null_oid(b)) + return 0; + + if (add_submodule_odb(path)) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s (not checked out)"), + path); + return 0; + } + + if (!(commit_o = lookup_commit_reference(opt->repo, o)) || + !(commit_a = lookup_commit_reference(opt->repo, a)) || + !(commit_b = lookup_commit_reference(opt->repo, b))) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s (commits not present)"), + path); + return 0; + } + + /* check whether both changes are forward */ + if (!in_merge_bases(commit_o, commit_a) || + !in_merge_bases(commit_o, commit_b)) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s " + "(commits don't follow merge-base)"), + path); + return 0; + } + + /* Case #1: a is contained in b or vice versa */ + if (in_merge_bases(commit_a, commit_b)) { + oidcpy(result, b); + path_msg(opt, path, 1, + _("Note: Fast-forwarding submodule %s to %s"), + path, oid_to_hex(b)); + return 1; + } + if (in_merge_bases(commit_b, commit_a)) { + oidcpy(result, a); + path_msg(opt, path, 1, + _("Note: Fast-forwarding submodule %s to %s"), + path, oid_to_hex(a)); + return 1; + } + + /* + * Case #2: There are one or more merges that contain a and b in + * the submodule. If there is only one, then present it as a + * suggestion to the user, but leave it marked unmerged so the + * user needs to confirm the resolution. + */ + + /* Skip the search if makes no sense to the calling context. */ + if (!search) + return 0; + + /* find commit which merges them */ + parent_count = find_first_merges(opt->repo, path, commit_a, commit_b, + &merges); + switch (parent_count) { + case 0: + path_msg(opt, path, 0, _("Failed to merge submodule %s"), path); + break; + + case 1: + format_commit(&sb, 4, + (struct commit *)merges.objects[0].item); + path_msg(opt, path, 0, + _("Failed to merge submodule %s, but a possible merge " + "resolution exists:\n%s\n"), + path, sb.buf); + path_msg(opt, path, 1, + _("If this is correct simply add it to the index " + "for example\n" + "by using:\n\n" + " git update-index --cacheinfo 160000 %s \"%s\"\n\n" + "which will accept this suggestion.\n"), + oid_to_hex(&merges.objects[0].item->oid), path); + strbuf_release(&sb); + break; + default: + for (i = 0; i < merges.nr; i++) + format_commit(&sb, 4, + (struct commit *)merges.objects[i].item); + path_msg(opt, path, 0, + _("Failed to merge submodule %s, but multiple " + "possible merges exist:\n%s"), path, sb.buf); + strbuf_release(&sb); + } + + object_array_clear(&merges); + return 0; +} + +static int merge_3way(struct merge_options *opt, + const char *path, + const struct object_id *o, + const struct object_id *a, + const struct object_id *b, + const char *pathnames[3], + const int extra_marker_size, + mmbuffer_t *result_buf) +{ + mmfile_t orig, src1, src2; + struct ll_merge_options ll_opts = {0}; + char *base, *name1, *name2; + int merge_status; + + ll_opts.renormalize = opt->renormalize; + ll_opts.extra_marker_size = extra_marker_size; + ll_opts.xdl_opts = opt->xdl_opts; + + if (opt->priv->call_depth) { + ll_opts.virtual_ancestor = 1; + ll_opts.variant = 0; + } else { + switch (opt->recursive_variant) { + case MERGE_VARIANT_OURS: + ll_opts.variant = XDL_MERGE_FAVOR_OURS; + break; + case MERGE_VARIANT_THEIRS: + ll_opts.variant = XDL_MERGE_FAVOR_THEIRS; + break; + default: + ll_opts.variant = 0; + break; + } + } + + assert(pathnames[0] && pathnames[1] && pathnames[2] && opt->ancestor); + if (pathnames[0] == pathnames[1] && pathnames[1] == pathnames[2]) { + base = mkpathdup("%s", opt->ancestor); + name1 = mkpathdup("%s", opt->branch1); + name2 = mkpathdup("%s", opt->branch2); + } else { + base = mkpathdup("%s:%s", opt->ancestor, pathnames[0]); + name1 = mkpathdup("%s:%s", opt->branch1, pathnames[1]); + name2 = mkpathdup("%s:%s", opt->branch2, pathnames[2]); + } + + read_mmblob(&orig, o); + read_mmblob(&src1, a); + read_mmblob(&src2, b); + + merge_status = ll_merge(result_buf, path, &orig, base, + &src1, name1, &src2, name2, + opt->repo->index, &ll_opts); + + free(base); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + return merge_status; +} + static int handle_content_merge(struct merge_options *opt, const char *path, const struct version_info *o, @@ -613,7 +932,130 @@ static int handle_content_merge(struct merge_options *opt, const int extra_marker_size, struct version_info *result) { - die("Not yet implemented"); + /* + * path is the target location where we want to put the file, and + * is used to determine any normalization rules in ll_merge. + * + * The normal case is that path and all entries in pathnames are + * identical, though renames can affect which path we got one of + * the three blobs to merge on various sides of history. + * + * extra_marker_size is the amount to extend conflict markers in + * ll_merge; this is neeed if we have content merges of content + * merges, which happens for example with rename/rename(2to1) and + * rename/add conflicts. + */ + unsigned clean = 1; + + /* + * handle_content_merge() needs both files to be of the same type, i.e. + * both files OR both submodules OR both symlinks. Conflicting types + * needs to be handled elsewhere. + */ + assert((S_IFMT & a->mode) == (S_IFMT & b->mode)); + + /* Merge modes */ + if (a->mode == b->mode || a->mode == o->mode) + result->mode = b->mode; + else { + /* must be the 100644/100755 case */ + assert(S_ISREG(a->mode)); + result->mode = a->mode; + clean = (b->mode == o->mode); + /* + * FIXME: If opt->priv->call_depth && !clean, then we really + * should not make result->mode match either a->mode or + * b->mode; that causes t6036 "check conflicting mode for + * regular file" to fail. It would be best to use some other + * mode, but we'll confuse all kinds of stuff if we use one + * where S_ISREG(result->mode) isn't true, and if we use + * something like 0100666, then tree-walk.c's calls to + * canon_mode() will just normalize that to 100644 for us and + * thus not solve anything. + * + * Figure out if there's some kind of way we can work around + * this... + */ + } + + /* + * Trivial oid merge. + * + * Note: While one might assume that the next four lines would + * be unnecessary due to the fact that match_mask is often + * setup and already handled, renames don't always take care + * of that. + */ + if (oideq(&a->oid, &b->oid) || oideq(&a->oid, &o->oid)) + oidcpy(&result->oid, &b->oid); + else if (oideq(&b->oid, &o->oid)) + oidcpy(&result->oid, &a->oid); + + /* Remaining rules depend on file vs. submodule vs. symlink. */ + else if (S_ISREG(a->mode)) { + mmbuffer_t result_buf; + int ret = 0, merge_status; + int two_way; + + /* + * If 'o' is different type, treat it as null so we do a + * two-way merge. + */ + two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); + + merge_status = merge_3way(opt, path, + two_way ? &null_oid : &o->oid, + &a->oid, &b->oid, + pathnames, extra_marker_size, + &result_buf); + + if ((merge_status < 0) || !result_buf.ptr) + ret = err(opt, _("Failed to execute internal merge")); + + if (!ret && + write_object_file(result_buf.ptr, result_buf.size, + blob_type, &result->oid)) + ret = err(opt, _("Unable to add %s to database"), + path); + + free(result_buf.ptr); + if (ret) + return -1; + clean &= (merge_status == 0); + path_msg(opt, path, 1, _("Auto-merging %s"), path); + } else if (S_ISGITLINK(a->mode)) { + int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); + clean = merge_submodule(opt, pathnames[0], + two_way ? &null_oid : &o->oid, + &a->oid, &b->oid, &result->oid); + if (opt->priv->call_depth && two_way && !clean) { + result->mode = o->mode; + oidcpy(&result->oid, &o->oid); + } + } else if (S_ISLNK(a->mode)) { + if (opt->priv->call_depth) { + clean = 0; + result->mode = o->mode; + oidcpy(&result->oid, &o->oid); + } else { + switch (opt->recursive_variant) { + case MERGE_VARIANT_NORMAL: + clean = 0; + oidcpy(&result->oid, &a->oid); + break; + case MERGE_VARIANT_OURS: + oidcpy(&result->oid, &a->oid); + break; + case MERGE_VARIANT_THEIRS: + oidcpy(&result->oid, &b->oid); + break; + } + } + } else + BUG("unsupported object type in the tree: %06o for %s", + a->mode, path); + + return clean; } /*** Function Grouping: functions related to detect_and_process_renames(), *** @@ -623,20 +1065,397 @@ static int handle_content_merge(struct merge_options *opt, /*** Function Grouping: functions related to regular rename detection ***/ +static int process_renames(struct merge_options *opt, + struct diff_queue_struct *renames) +{ + int clean_merge = 1, i; + + for (i = 0; i < renames->nr; ++i) { + const char *oldpath = NULL, *newpath; + struct diff_filepair *pair = renames->queue[i]; + struct conflict_info *oldinfo = NULL, *newinfo = NULL; + struct strmap_entry *old_ent, *new_ent; + unsigned int old_sidemask; + int target_index, other_source_index; + int source_deleted, collision, type_changed; + const char *rename_branch = NULL, *delete_branch = NULL; + + old_ent = strmap_get_entry(&opt->priv->paths, pair->one->path); + oldpath = old_ent->key; + oldinfo = old_ent->value; + + new_ent = strmap_get_entry(&opt->priv->paths, pair->two->path); + newpath = new_ent->key; + newinfo = new_ent->value; + + /* + * diff_filepairs have copies of pathnames, thus we have to + * use standard 'strcmp()' (negated) instead of '=='. + */ + if (i + 1 < renames->nr && + !strcmp(oldpath, renames->queue[i+1]->one->path)) { + /* Handle rename/rename(1to2) or rename/rename(1to1) */ + const char *pathnames[3]; + struct version_info merged; + struct conflict_info *base, *side1, *side2; + unsigned was_binary_blob = 0; + + pathnames[0] = oldpath; + pathnames[1] = newpath; + pathnames[2] = renames->queue[i+1]->two->path; + + base = strmap_get(&opt->priv->paths, pathnames[0]); + side1 = strmap_get(&opt->priv->paths, pathnames[1]); + side2 = strmap_get(&opt->priv->paths, pathnames[2]); + + VERIFY_CI(base); + VERIFY_CI(side1); + VERIFY_CI(side2); + + if (!strcmp(pathnames[1], pathnames[2])) { + /* Both sides renamed the same way */ + assert(side1 == side2); + memcpy(&side1->stages[0], &base->stages[0], + sizeof(merged)); + side1->filemask |= (1 << MERGE_BASE); + /* Mark base as resolved by removal */ + base->merged.is_null = 1; + base->merged.clean = 1; + + /* We handled both renames, i.e. i+1 handled */ + i++; + /* Move to next rename */ + continue; + } + + /* This is a rename/rename(1to2) */ + clean_merge = handle_content_merge(opt, + pair->one->path, + &base->stages[0], + &side1->stages[1], + &side2->stages[2], + pathnames, + 1 + 2 * opt->priv->call_depth, + &merged); + if (!clean_merge && + merged.mode == side1->stages[1].mode && + oideq(&merged.oid, &side1->stages[1].oid)) + was_binary_blob = 1; + memcpy(&side1->stages[1], &merged, sizeof(merged)); + if (was_binary_blob) { + /* + * Getting here means we were attempting to + * merge a binary blob. + * + * Since we can't merge binaries, + * handle_content_merge() just takes one + * side. But we don't want to copy the + * contents of one side to both paths. We + * used the contents of side1 above for + * side1->stages, let's use the contents of + * side2 for side2->stages below. + */ + oidcpy(&merged.oid, &side2->stages[2].oid); + merged.mode = side2->stages[2].mode; + } + memcpy(&side2->stages[2], &merged, sizeof(merged)); + + side1->path_conflict = 1; + side2->path_conflict = 1; + /* + * TODO: For renames we normally remove the path at the + * old name. It would thus seem consistent to do the + * same for rename/rename(1to2) cases, but we haven't + * done so traditionally and a number of the regression + * tests now encode an expectation that the file is + * left there at stage 1. If we ever decide to change + * this, add the following two lines here: + * base->merged.is_null = 1; + * base->merged.clean = 1; + * and remove the setting of base->path_conflict to 1. + */ + base->path_conflict = 1; + path_msg(opt, oldpath, 0, + _("CONFLICT (rename/rename): %s renamed to " + "%s in %s and to %s in %s."), + pathnames[0], + pathnames[1], opt->branch1, + pathnames[2], opt->branch2); + + i++; /* We handled both renames, i.e. i+1 handled */ + continue; + } + + VERIFY_CI(oldinfo); + VERIFY_CI(newinfo); + target_index = pair->score; /* from collect_renames() */ + assert(target_index == 1 || target_index == 2); + other_source_index = 3 - target_index; + old_sidemask = (1 << other_source_index); /* 2 or 4 */ + source_deleted = (oldinfo->filemask == 1); + collision = ((newinfo->filemask & old_sidemask) != 0); + type_changed = !source_deleted && + (S_ISREG(oldinfo->stages[other_source_index].mode) != + S_ISREG(newinfo->stages[target_index].mode)); + if (type_changed && collision) { + /* + * special handling so later blocks can handle this... + * + * if type_changed && collision are both true, then this + * was really a double rename, but one side wasn't + * detected due to lack of break detection. I.e. + * something like + * orig: has normal file 'foo' + * side1: renames 'foo' to 'bar', adds 'foo' symlink + * side2: renames 'foo' to 'bar' + * In this case, the foo->bar rename on side1 won't be + * detected because the new symlink named 'foo' is + * there and we don't do break detection. But we detect + * this here because we don't want to merge the content + * of the foo symlink with the foo->bar file, so we + * have some logic to handle this special case. The + * easiest way to do that is make 'bar' on side1 not + * be considered a colliding file but the other part + * of a normal rename. If the file is very different, + * well we're going to get content merge conflicts + * anyway so it doesn't hurt. And if the colliding + * file also has a different type, that'll be handled + * by the content merge logic in process_entry() too. + * + * See also t6430, 'rename vs. rename/symlink' + */ + collision = 0; + } + if (source_deleted) { + if (target_index == 1) { + rename_branch = opt->branch1; + delete_branch = opt->branch2; + } else { + rename_branch = opt->branch2; + delete_branch = opt->branch1; + } + } + + assert(source_deleted || oldinfo->filemask & old_sidemask); + + /* Need to check for special types of rename conflicts... */ + if (collision && !source_deleted) { + /* collision: rename/add or rename/rename(2to1) */ + const char *pathnames[3]; + struct version_info merged; + + struct conflict_info *base, *side1, *side2; + unsigned clean; + + pathnames[0] = oldpath; + pathnames[other_source_index] = oldpath; + pathnames[target_index] = newpath; + + base = strmap_get(&opt->priv->paths, pathnames[0]); + side1 = strmap_get(&opt->priv->paths, pathnames[1]); + side2 = strmap_get(&opt->priv->paths, pathnames[2]); + + VERIFY_CI(base); + VERIFY_CI(side1); + VERIFY_CI(side2); + + clean = handle_content_merge(opt, pair->one->path, + &base->stages[0], + &side1->stages[1], + &side2->stages[2], + pathnames, + 1 + 2 * opt->priv->call_depth, + &merged); + + memcpy(&newinfo->stages[target_index], &merged, + sizeof(merged)); + if (!clean) { + path_msg(opt, newpath, 0, + _("CONFLICT (rename involved in " + "collision): rename of %s -> %s has " + "content conflicts AND collides " + "with another path; this may result " + "in nested conflict markers."), + oldpath, newpath); + } + } else if (collision && source_deleted) { + /* + * rename/add/delete or rename/rename(2to1)/delete: + * since oldpath was deleted on the side that didn't + * do the rename, there's not much of a content merge + * we can do for the rename. oldinfo->merged.is_null + * was already set, so we just leave things as-is so + * they look like an add/add conflict. + */ + + newinfo->path_conflict = 1; + path_msg(opt, newpath, 0, + _("CONFLICT (rename/delete): %s renamed " + "to %s in %s, but deleted in %s."), + oldpath, newpath, rename_branch, delete_branch); + } else { + /* + * a few different cases...start by copying the + * existing stage(s) from oldinfo over the newinfo + * and update the pathname(s). + */ + memcpy(&newinfo->stages[0], &oldinfo->stages[0], + sizeof(newinfo->stages[0])); + newinfo->filemask |= (1 << MERGE_BASE); + newinfo->pathnames[0] = oldpath; + if (type_changed) { + /* rename vs. typechange */ + /* Mark the original as resolved by removal */ + memcpy(&oldinfo->stages[0].oid, &null_oid, + sizeof(oldinfo->stages[0].oid)); + oldinfo->stages[0].mode = 0; + oldinfo->filemask &= 0x06; + } else if (source_deleted) { + /* rename/delete */ + newinfo->path_conflict = 1; + path_msg(opt, newpath, 0, + _("CONFLICT (rename/delete): %s renamed" + " to %s in %s, but deleted in %s."), + oldpath, newpath, + rename_branch, delete_branch); + } else { + /* normal rename */ + memcpy(&newinfo->stages[other_source_index], + &oldinfo->stages[other_source_index], + sizeof(newinfo->stages[0])); + newinfo->filemask |= (1 << other_source_index); + newinfo->pathnames[other_source_index] = oldpath; + } + } + + if (!type_changed) { + /* Mark the original as resolved by removal */ + oldinfo->merged.is_null = 1; + oldinfo->merged.clean = 1; + } + + } + + return clean_merge; +} + +static int compare_pairs(const void *a_, const void *b_) +{ + const struct diff_filepair *a = *((const struct diff_filepair **)a_); + const struct diff_filepair *b = *((const struct diff_filepair **)b_); + + return strcmp(a->one->path, b->one->path); +} + +/* Call diffcore_rename() to compute which files have changed on given side */ +static void detect_regular_renames(struct merge_options *opt, + struct tree *merge_base, + struct tree *side, + unsigned side_index) +{ + struct diff_options diff_opts; + struct rename_info *renames = &opt->priv->renames; + + repo_diff_setup(opt->repo, &diff_opts); + diff_opts.flags.recursive = 1; + diff_opts.flags.rename_empty = 0; + diff_opts.detect_rename = DIFF_DETECT_RENAME; + diff_opts.rename_limit = opt->rename_limit; + if (opt->rename_limit <= 0) + diff_opts.rename_limit = 1000; + diff_opts.rename_score = opt->rename_score; + diff_opts.show_rename_progress = opt->show_rename_progress; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&diff_opts); + diff_tree_oid(&merge_base->object.oid, &side->object.oid, "", + &diff_opts); + diffcore_std(&diff_opts); + + if (diff_opts.needed_rename_limit > renames->needed_limit) + renames->needed_limit = diff_opts.needed_rename_limit; + + renames->pairs[side_index] = diff_queued_diff; + + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_queued_diff.queue = NULL; + diff_flush(&diff_opts); +} + +/* + * Get information of all renames which occurred in 'side_pairs', discarding + * non-renames. + */ +static int collect_renames(struct merge_options *opt, + struct diff_queue_struct *result, + unsigned side_index) +{ + int i, clean = 1; + struct diff_queue_struct *side_pairs; + struct rename_info *renames = &opt->priv->renames; + + side_pairs = &renames->pairs[side_index]; + + for (i = 0; i < side_pairs->nr; ++i) { + struct diff_filepair *p = side_pairs->queue[i]; + + if (p->status != 'R') { + diff_free_filepair(p); + continue; + } + + /* + * p->score comes back from diffcore_rename_extended() with + * the similarity of the renamed file. The similarity is + * was used to determine that the two files were related + * and are a rename, which we have already used, but beyond + * that we have no use for the similarity. So p->score is + * now irrelevant. However, process_renames() will need to + * know which side of the merge this rename was associated + * with, so overwrite p->score with that value. + */ + p->score = side_index; + result->queue[result->nr++] = p; + } + + return clean; +} + static int detect_and_process_renames(struct merge_options *opt, struct tree *merge_base, struct tree *side1, struct tree *side2) { - int clean = 1; + struct diff_queue_struct combined; + struct rename_info *renames = &opt->priv->renames; + int s, clean = 1; + + memset(&combined, 0, sizeof(combined)); + + detect_regular_renames(opt, merge_base, side1, MERGE_SIDE1); + detect_regular_renames(opt, merge_base, side2, MERGE_SIDE2); + + ALLOC_GROW(combined.queue, + renames->pairs[1].nr + renames->pairs[2].nr, + combined.alloc); + clean &= collect_renames(opt, &combined, MERGE_SIDE1); + clean &= collect_renames(opt, &combined, MERGE_SIDE2); + QSORT(combined.queue, combined.nr, compare_pairs); + + clean &= process_renames(opt, &combined); + + /* Free memory for renames->pairs[] and combined */ + for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) { + free(renames->pairs[s].queue); + DIFF_QUEUE_CLEAR(&renames->pairs[s]); + } + if (combined.nr) { + int i; + for (i = 0; i < combined.nr; i++) + diff_free_filepair(combined.queue[i]); + free(combined.queue); + } - /* - * Rename detection works by detecting file similarity. Here we use - * a really easy-to-implement scheme: files are similar IFF they have - * the same filename. Therefore, by this scheme, there are no renames. - * - * TODO: Actually implement a real rename detection scheme. - */ return clean; } @@ -965,6 +1784,8 @@ static void process_entry(struct merge_options *opt, struct conflict_info *ci, struct directory_versions *dir_metadata) { + int df_file_index = 0; + VERIFY_CI(ci); assert(ci->filemask >= 0 && ci->filemask <= 7); /* ci->match_mask == 7 was handled in collect_merge_info_callback() */ @@ -979,14 +1800,108 @@ static void process_entry(struct merge_options *opt, assert(ci->df_conflict); } - if (ci->df_conflict) { - die("Not yet implemented."); + if (ci->df_conflict && ci->merged.result.mode == 0) { + int i; + + /* + * directory no longer in the way, but we do have a file we + * need to place here so we need to clean away the "directory + * merges to nothing" result. + */ + ci->df_conflict = 0; + assert(ci->filemask != 0); + ci->merged.clean = 0; + ci->merged.is_null = 0; + /* and we want to zero out any directory-related entries */ + ci->match_mask = (ci->match_mask & ~ci->dirmask); + ci->dirmask = 0; + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + if (ci->filemask & (1 << i)) + continue; + ci->stages[i].mode = 0; + oidcpy(&ci->stages[i].oid, &null_oid); + } + } else if (ci->df_conflict && ci->merged.result.mode != 0) { + /* + * This started out as a D/F conflict, and the entries in + * the competing directory were not removed by the merge as + * evidenced by write_completed_directory() writing a value + * to ci->merged.result.mode. + */ + struct conflict_info *new_ci; + const char *branch; + const char *old_path = path; + int i; + + assert(ci->merged.result.mode == S_IFDIR); + + /* + * If filemask is 1, we can just ignore the file as having + * been deleted on both sides. We do not want to overwrite + * ci->merged.result, since it stores the tree for all the + * files under it. + */ + if (ci->filemask == 1) { + ci->filemask = 0; + return; + } + + /* + * This file still exists on at least one side, and we want + * the directory to remain here, so we need to move this + * path to some new location. + */ + new_ci = xcalloc(1, sizeof(*new_ci)); + /* We don't really want new_ci->merged.result copied, but it'll + * be overwritten below so it doesn't matter. We also don't + * want any directory mode/oid values copied, but we'll zero + * those out immediately. We do want the rest of ci copied. + */ + memcpy(new_ci, ci, sizeof(*ci)); + new_ci->match_mask = (new_ci->match_mask & ~new_ci->dirmask); + new_ci->dirmask = 0; + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + if (new_ci->filemask & (1 << i)) + continue; + /* zero out any entries related to directories */ + new_ci->stages[i].mode = 0; + oidcpy(&new_ci->stages[i].oid, &null_oid); + } + + /* + * Find out which side this file came from; note that we + * cannot just use ci->filemask, because renames could cause + * the filemask to go back to 7. So we use dirmask, then + * pick the opposite side's index. + */ + df_file_index = (ci->dirmask & (1 << 1)) ? 2 : 1; + branch = (df_file_index == 1) ? opt->branch1 : opt->branch2; + path = unique_path(&opt->priv->paths, path, branch); + strmap_put(&opt->priv->paths, path, new_ci); + + path_msg(opt, path, 0, + _("CONFLICT (file/directory): directory in the way " + "of %s from %s; moving it to %s instead."), + old_path, branch, path); + + /* + * Zero out the filemask for the old ci. At this point, ci + * was just an entry for a directory, so we don't need to + * do anything more with it. + */ + ci->filemask = 0; + + /* + * Now note that we're working on the new entry (path was + * updated above. + */ + ci = new_ci; } /* * NOTE: Below there is a long switch-like if-elseif-elseif... block * which the code goes through even for the df_conflict cases - * above. Well, it will once we don't die-not-implemented above. + * above. */ if (ci->match_mask) { ci->merged.clean = 1; @@ -1010,21 +1925,142 @@ static void process_entry(struct merge_options *opt, } else if (ci->filemask >= 6 && (S_IFMT & ci->stages[1].mode) != (S_IFMT & ci->stages[2].mode)) { - /* - * Two different items from (file/submodule/symlink) - */ - die("Not yet implemented."); + /* Two different items from (file/submodule/symlink) */ + if (opt->priv->call_depth) { + /* Just use the version from the merge base */ + ci->merged.clean = 0; + oidcpy(&ci->merged.result.oid, &ci->stages[0].oid); + ci->merged.result.mode = ci->stages[0].mode; + ci->merged.is_null = (ci->merged.result.mode == 0); + } else { + /* Handle by renaming one or both to separate paths. */ + unsigned o_mode = ci->stages[0].mode; + unsigned a_mode = ci->stages[1].mode; + unsigned b_mode = ci->stages[2].mode; + struct conflict_info *new_ci; + const char *a_path = NULL, *b_path = NULL; + int rename_a = 0, rename_b = 0; + + new_ci = xmalloc(sizeof(*new_ci)); + + if (S_ISREG(a_mode)) + rename_a = 1; + else if (S_ISREG(b_mode)) + rename_b = 1; + else { + rename_a = 1; + rename_b = 1; + } + + path_msg(opt, path, 0, + _("CONFLICT (distinct types): %s had different " + "types on each side; renamed %s of them so " + "each can be recorded somewhere."), + path, + (rename_a && rename_b) ? _("both") : _("one")); + + ci->merged.clean = 0; + memcpy(new_ci, ci, sizeof(*new_ci)); + + /* Put b into new_ci, removing a from stages */ + new_ci->merged.result.mode = ci->stages[2].mode; + oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid); + new_ci->stages[1].mode = 0; + oidcpy(&new_ci->stages[1].oid, &null_oid); + new_ci->filemask = 5; + if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) { + new_ci->stages[0].mode = 0; + oidcpy(&new_ci->stages[0].oid, &null_oid); + new_ci->filemask = 4; + } + + /* Leave only a in ci, fixing stages. */ + ci->merged.result.mode = ci->stages[1].mode; + oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); + ci->stages[2].mode = 0; + oidcpy(&ci->stages[2].oid, &null_oid); + ci->filemask = 3; + if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) { + ci->stages[0].mode = 0; + oidcpy(&ci->stages[0].oid, &null_oid); + ci->filemask = 2; + } + + /* Insert entries into opt->priv_paths */ + assert(rename_a || rename_b); + if (rename_a) { + a_path = unique_path(&opt->priv->paths, + path, opt->branch1); + strmap_put(&opt->priv->paths, a_path, ci); + } + + if (rename_b) + b_path = unique_path(&opt->priv->paths, + path, opt->branch2); + else + b_path = path; + strmap_put(&opt->priv->paths, b_path, new_ci); + + if (rename_a && rename_b) { + strmap_remove(&opt->priv->paths, path, 0); + /* + * We removed path from opt->priv->paths. path + * will also eventually need to be freed, but + * it may still be used by e.g. ci->pathnames. + * So, store it in another string-list for now. + */ + string_list_append(&opt->priv->paths_to_free, + path); + } + + /* + * Do special handling for b_path since process_entry() + * won't be called on it specially. + */ + strmap_put(&opt->priv->conflicted, b_path, new_ci); + record_entry_for_tree(dir_metadata, b_path, + &new_ci->merged); + + /* + * Remaining code for processing this entry should + * think in terms of processing a_path. + */ + if (a_path) + path = a_path; + } } else if (ci->filemask >= 6) { - /* - * TODO: Needs a two-way or three-way content merge, but we're - * just being lazy and copying the version from HEAD and - * leaving it as conflicted. - */ - ci->merged.clean = 0; - ci->merged.result.mode = ci->stages[1].mode; - oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); - /* When we fix above, we'll call handle_content_merge() */ - (void)handle_content_merge; + /* Need a two-way or three-way content merge */ + struct version_info merged_file; + unsigned clean_merge; + struct version_info *o = &ci->stages[0]; + struct version_info *a = &ci->stages[1]; + struct version_info *b = &ci->stages[2]; + + clean_merge = handle_content_merge(opt, path, o, a, b, + ci->pathnames, + opt->priv->call_depth * 2, + &merged_file); + ci->merged.clean = clean_merge && + !ci->df_conflict && !ci->path_conflict; + ci->merged.result.mode = merged_file.mode; + ci->merged.is_null = (merged_file.mode == 0); + oidcpy(&ci->merged.result.oid, &merged_file.oid); + if (clean_merge && ci->df_conflict) { + assert(df_file_index == 1 || df_file_index == 2); + ci->filemask = 1 << df_file_index; + ci->stages[df_file_index].mode = merged_file.mode; + oidcpy(&ci->stages[df_file_index].oid, &merged_file.oid); + } + if (!clean_merge) { + const char *reason = _("content"); + if (ci->filemask == 6) + reason = _("add/add"); + if (S_ISGITLINK(merged_file.mode)) + reason = _("submodule"); + path_msg(opt, path, 0, + _("CONFLICT (%s): Merge conflict in %s"), + reason, path); + } } else if (ci->filemask == 3 || ci->filemask == 5) { /* Modify/delete */ const char *modify_branch, *delete_branch; @@ -1038,24 +2074,33 @@ static void process_entry(struct merge_options *opt, modify_branch = (side == 1) ? opt->branch1 : opt->branch2; delete_branch = (side == 1) ? opt->branch2 : opt->branch1; - path_msg(opt, path, 0, - _("CONFLICT (modify/delete): %s deleted in %s " - "and modified in %s. Version %s of %s left " - "in tree."), - path, delete_branch, modify_branch, - modify_branch, path); + if (ci->path_conflict && + oideq(&ci->stages[0].oid, &ci->stages[side].oid)) { + /* + * This came from a rename/delete; no action to take, + * but avoid printing "modify/delete" conflict notice + * since the contents were not modified. + */ + } else { + path_msg(opt, path, 0, + _("CONFLICT (modify/delete): %s deleted in %s " + "and modified in %s. Version %s of %s left " + "in tree."), + path, delete_branch, modify_branch, + modify_branch, path); + } } else if (ci->filemask == 2 || ci->filemask == 4) { /* Added on one side */ int side = (ci->filemask == 4) ? 2 : 1; ci->merged.result.mode = ci->stages[side].mode; oidcpy(&ci->merged.result.oid, &ci->stages[side].oid); - ci->merged.clean = !ci->df_conflict; + ci->merged.clean = !ci->df_conflict && !ci->path_conflict; } else if (ci->filemask == 1) { /* Deleted on both sides */ ci->merged.is_null = 1; ci->merged.result.mode = 0; oidcpy(&ci->merged.result.oid, &null_oid); - ci->merged.clean = 1; + ci->merged.clean = !ci->path_conflict; } /* @@ -1333,6 +2378,10 @@ void merge_switch_to_result(struct merge_options *opt, printf("%s", sb->buf); } string_list_clear(&olist, 0); + + /* Also include needed rename limit adjustment now */ + diff_warn_rename_limit("merge.renamelimit", + opti->renames.needed_limit, 0); } merge_finalize(opt, result); |