#include "merge-recursive.h"
#include "dir.h"
#include "submodule.h"
+#include "revision.h"
struct path_hashmap_entry {
struct hashmap_entry e;
struct commit *commit = alloc_commit_node();
set_merge_remote_desc(commit, comment, (struct object *)commit);
- commit->tree = tree;
+ commit->maybe_tree = tree;
commit->object.parsed = 1;
return commit;
}
ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage, 0);
if (!ce)
- return err(o, _("addinfo_cache failed for path '%s'"), path);
+ return err(o, _("add_cacheinfo failed for path '%s'; merge aborting."), path);
ret = add_cache_entry(ce, options);
if (refresh) {
nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING);
if (!nce)
- return err(o, _("addinfo_cache failed for path '%s'"), path);
+ return err(o, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path);
if (nce != ce)
ret = add_cache_entry(nce, options);
}
init_tree_desc(desc, tree->buffer, tree->size);
}
-static int git_merge_trees(int index_only,
+static int git_merge_trees(struct merge_options *o,
struct tree *common,
struct tree *head,
struct tree *merge)
{
int rc;
struct tree_desc t[3];
- struct unpack_trees_options opts;
+ struct index_state tmp_index = { NULL };
- memset(&opts, 0, sizeof(opts));
- if (index_only)
- opts.index_only = 1;
+ memset(&o->unpack_opts, 0, sizeof(o->unpack_opts));
+ if (o->call_depth)
+ o->unpack_opts.index_only = 1;
else
- opts.update = 1;
- opts.merge = 1;
- opts.head_idx = 2;
- opts.fn = threeway_merge;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- setup_unpack_trees_porcelain(&opts, "merge");
+ o->unpack_opts.update = 1;
+ o->unpack_opts.merge = 1;
+ o->unpack_opts.head_idx = 2;
+ o->unpack_opts.fn = threeway_merge;
+ o->unpack_opts.src_index = &the_index;
+ o->unpack_opts.dst_index = &tmp_index;
+ o->unpack_opts.aggressive = !merge_detect_rename(o);
+ setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
init_tree_desc_from_tree(t+2, merge);
- rc = unpack_trees(3, t, &opts);
+ rc = unpack_trees(3, t, &o->unpack_opts);
cache_tree_free(&active_cache_tree);
+
+ /*
+ * Update the_index to match the new results, AFTER saving a copy
+ * in o->orig_index. Update src_index to point to the saved copy.
+ * (verify_uptodate() checks src_index, and the original index is
+ * the one that had the necessary modification timestamps.)
+ */
+ o->orig_index = the_index;
+ the_index = tmp_index;
+ o->unpack_opts.src_index = &o->orig_index;
+
return rc;
}
fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
(int)ce_namelen(ce), ce->name);
}
- die("BUG: unmerged index entries in merge-recursive.c");
+ BUG("unmerged index entries in merge-recursive.c");
}
if (!active_cache_tree)
!(empty_ok && is_empty_dir(path));
}
-static int was_tracked(const char *path)
+/*
+ * Returns whether path was tracked in the index before the merge started,
+ * and its oid and mode match the specified values
+ */
+static int was_tracked_and_matches(struct merge_options *o, const char *path,
+ const struct object_id *oid, unsigned mode)
{
- int pos = cache_name_pos(path, strlen(path));
+ int pos = index_name_pos(&o->orig_index, path, strlen(path));
+ struct cache_entry *ce;
+
+ if (0 > pos)
+ /* we were not tracking this path before the merge */
+ return 0;
+
+ /* See if the file we were tracking before matches */
+ ce = o->orig_index.cache[pos];
+ return (oid_eq(&ce->oid, oid) && ce->ce_mode == mode);
+}
+
+/*
+ * Returns whether path was tracked in the index before the merge started
+ */
+static int was_tracked(struct merge_options *o, const char *path)
+{
+ int pos = index_name_pos(&o->orig_index, path, strlen(path));
if (0 <= pos)
- /* we have been tracking this path */
+ /* we were tracking this path before the merge */
return 1;
- /*
- * Look for an unmerged entry for the path,
- * specifically stage #2, which would indicate
- * that "our" side before the merge started
- * had the path tracked (and resulted in a conflict).
- */
- for (pos = -1 - pos;
- pos < active_nr && !strcmp(path, active_cache[pos]->name);
- pos++)
- if (ce_stage(active_cache[pos]) == 2)
- return 1;
return 0;
}
static int would_lose_untracked(const char *path)
{
- return !was_tracked(path) && file_exists(path);
+ /*
+ * This may look like it can be simplified to:
+ * return !was_tracked(o, path) && file_exists(path)
+ * but it can't. This function needs to know whether path was in
+ * the working tree due to EITHER having been tracked in the index
+ * before the merge OR having been put into the working copy and
+ * index by unpack_trees(). Due to that either-or requirement, we
+ * check the current index instead of the original one.
+ *
+ * Note that we do not need to worry about merge-recursive itself
+ * updating the index after unpack_trees() and before calling this
+ * function, because we strictly require all code paths in
+ * merge-recursive to update the working tree first and the index
+ * second. Doing otherwise would break
+ * update_file()/would_lose_untracked(); see every comment in this
+ * file which mentions "update_stages".
+ */
+ int pos = cache_name_pos(path, strlen(path));
+
+ if (pos < 0)
+ pos = -1 - pos;
+ while (pos < active_nr &&
+ !strcmp(path, active_cache[pos]->name)) {
+ /*
+ * If stage #0, it is definitely tracked.
+ * If it has stage #2 then it was tracked
+ * before this merge started. All other
+ * cases the path was not tracked.
+ */
+ switch (ce_stage(active_cache[pos])) {
+ case 0:
+ case 2:
+ return 0;
+ }
+ pos++;
+ }
+ return file_exists(path);
+}
+
+static int was_dirty(struct merge_options *o, const char *path)
+{
+ struct cache_entry *ce;
+ int dirty = 1;
+
+ if (o->call_depth || !was_tracked(o, path))
+ return !dirty;
+
+ ce = index_file_exists(o->unpack_opts.src_index,
+ path, strlen(path), ignore_case);
+ dirty = verify_uptodate(ce, &o->unpack_opts) != 0;
+ return dirty;
}
static int make_room_for_path(struct merge_options *o, const char *path)
}
update_index:
if (!ret && update_cache)
- add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD);
+ if (add_cacheinfo(o, mode, oid, path, 0, update_wd,
+ ADD_CACHE_OK_TO_ADD))
+ return -1;
return ret;
}
return merge_status;
}
+static int find_first_merges(struct object_array *result, const char *path,
+ struct commit *a, struct commit *b)
+{
+ int i, j;
+ struct object_array merges = OBJECT_ARRAY_INIT;
+ struct commit *commit;
+ int contains_another;
+
+ char merged_revision[42];
+ 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));
+ init_revisions(&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 void print_commit(struct commit *commit)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode.type = DATE_NORMAL;
+ format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+ fprintf(stderr, "%s\n", sb.buf);
+ strbuf_release(&sb);
+}
+
+static int merge_submodule(struct merge_options *o,
+ struct object_id *result, const char *path,
+ const struct object_id *base, const struct object_id *a,
+ const struct object_id *b)
+{
+ struct commit *commit_base, *commit_a, *commit_b;
+ int parent_count;
+ struct object_array merges;
+
+ int i;
+ int search = !o->call_depth;
+
+ /* store a in result in case we fail */
+ oidcpy(result, a);
+
+ /* we can not handle deletion conflicts */
+ if (is_null_oid(base))
+ return 0;
+ if (is_null_oid(a))
+ return 0;
+ if (is_null_oid(b))
+ return 0;
+
+ if (add_submodule_odb(path)) {
+ output(o, 1, _("Failed to merge submodule %s (not checked out)"), path);
+ return 0;
+ }
+
+ if (!(commit_base = lookup_commit_reference(base)) ||
+ !(commit_a = lookup_commit_reference(a)) ||
+ !(commit_b = lookup_commit_reference(b))) {
+ output(o, 1, _("Failed to merge submodule %s (commits not present)"), path);
+ return 0;
+ }
+
+ /* check whether both changes are forward */
+ if (!in_merge_bases(commit_base, commit_a) ||
+ !in_merge_bases(commit_base, commit_b)) {
+ output(o, 1, _("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);
+ if (show(o, 3)) {
+ output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+ output_commit_title(o, commit_b);
+ } else if (show(o, 2))
+ output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(b));
+ else
+ ; /* no output */
+
+ return 1;
+ }
+ if (in_merge_bases(commit_b, commit_a)) {
+ oidcpy(result, a);
+ if (show(o, 3)) {
+ output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
+ output_commit_title(o, commit_a);
+ } else if (show(o, 2))
+ output(o, 2, _("Fast-forwarding submodule %s to %s"), path, oid_to_hex(a));
+ else
+ ; /* no output */
+
+ 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(&merges, path, commit_a, commit_b);
+ switch (parent_count) {
+ case 0:
+ output(o, 1, _("Failed to merge submodule %s (merge following commits not found)"), path);
+ break;
+
+ case 1:
+ output(o, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
+ output(o, 2, _("Found a possible merge resolution for the submodule:\n"));
+ print_commit((struct commit *) merges.objects[0].item);
+ output(o, 2, _(
+ "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);
+ break;
+
+ default:
+ output(o, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
+ for (i = 0; i < merges.nr; i++)
+ print_commit((struct commit *) merges.objects[i].item);
+ }
+
+ object_array_clear(&merges);
+ return 0;
+}
+
static int merge_file_1(struct merge_options *o,
- const struct diff_filespec *one,
- const struct diff_filespec *a,
- const struct diff_filespec *b,
- const char *branch1,
- const char *branch2,
- struct merge_file_info *result)
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
+ const char *filename,
+ const char *branch1,
+ const char *branch2,
+ struct merge_file_info *result)
{
result->merge = 0;
result->clean = 1;
return ret;
result->clean = (merge_status == 0);
} else if (S_ISGITLINK(a->mode)) {
- result->clean = merge_submodule(&result->oid,
+ result->clean = merge_submodule(o, &result->oid,
one->path,
&one->oid,
&a->oid,
- &b->oid,
- !o->call_depth);
+ &b->oid);
} else if (S_ISLNK(a->mode)) {
switch (o->recursive_variant) {
case MERGE_RECURSIVE_NORMAL:
break;
}
} else
- die("BUG: unsupported object type in the tree");
+ BUG("unsupported object type in the tree");
}
+ if (result->merge)
+ output(o, 2, _("Auto-merging %s"), filename);
+
return 0;
}
static int merge_file_special_markers(struct merge_options *o,
- const struct diff_filespec *one,
- const struct diff_filespec *a,
- const struct diff_filespec *b,
- const char *branch1,
- const char *filename1,
- const char *branch2,
- const char *filename2,
- struct merge_file_info *mfi)
+ const struct diff_filespec *one,
+ const struct diff_filespec *a,
+ const struct diff_filespec *b,
+ const char *target_filename,
+ const char *branch1,
+ const char *filename1,
+ const char *branch2,
+ const char *filename2,
+ struct merge_file_info *mfi)
{
char *side1 = NULL;
char *side2 = NULL;
if (filename2)
side2 = xstrfmt("%s:%s", branch2, filename2);
- ret = merge_file_1(o, one, a, b,
+ ret = merge_file_1(o, one, a, b, target_filename,
side1 ? side1 : branch1,
side2 ? side2 : branch2, mfi);
+
free(side1);
free(side2);
return ret;
}
static int merge_file_one(struct merge_options *o,
- const char *path,
- const struct object_id *o_oid, int o_mode,
- const struct object_id *a_oid, int a_mode,
- const struct object_id *b_oid, int b_mode,
- const char *branch1,
- const char *branch2,
- struct merge_file_info *mfi)
+ const char *path,
+ const struct object_id *o_oid, int o_mode,
+ const struct object_id *a_oid, int a_mode,
+ const struct object_id *b_oid, int b_mode,
+ const char *branch1,
+ const char *branch2,
+ struct merge_file_info *mfi)
{
struct diff_filespec one, a, b;
a.mode = a_mode;
oidcpy(&b.oid, b_oid);
b.mode = b_mode;
- return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi);
+ return merge_file_1(o, &one, &a, &b, path, branch1, branch2, mfi);
}
static int conflict_rename_dir(struct merge_options *o,
add = filespec_from_entry(&other, dst_entry, stage ^ 1);
if (add) {
+ int ren_src_was_dirty = was_dirty(o, rename->path);
char *add_name = unique_path(o, rename->path, other_branch);
if (update_file(o, 0, &add->oid, add->mode, add_name))
return -1;
- remove_file(o, 0, rename->path, 0);
+ if (ren_src_was_dirty) {
+ output(o, 1, _("Refusing to lose dirty file at %s"),
+ rename->path);
+ }
+ /*
+ * Because the double negatives somehow keep confusing me...
+ * 1) update_wd iff !ren_src_was_dirty.
+ * 2) no_wd iff !update_wd
+ * 3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
+ */
+ remove_file(o, 0, rename->path, ren_src_was_dirty);
dst_name = unique_path(o, rename->path, cur_branch);
} else {
if (dir_in_way(rename->path, !o->call_depth, 0)) {
struct diff_filespec *c1 = ci->pair1->two;
struct diff_filespec *c2 = ci->pair2->two;
char *path = c1->path; /* == c2->path */
+ char *path_side_1_desc;
+ char *path_side_2_desc;
struct merge_file_info mfi_c1;
struct merge_file_info mfi_c2;
int ret;
remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
+ path_side_1_desc = xstrfmt("%s (was %s)", path, a->path);
+ path_side_2_desc = xstrfmt("%s (was %s)", path, b->path);
if (merge_file_special_markers(o, a, c1, &ci->ren1_other,
+ path_side_1_desc,
o->branch1, c1->path,
o->branch2, ci->ren1_other.path, &mfi_c1) ||
merge_file_special_markers(o, b, &ci->ren2_other, c2,
+ path_side_2_desc,
o->branch1, ci->ren2_other.path,
o->branch2, c2->path, &mfi_c2))
return -1;
+ free(path_side_1_desc);
+ free(path_side_2_desc);
if (o->call_depth) {
/*
char *new_path2 = unique_path(o, path, ci->branch2);
output(o, 1, _("Renaming %s to %s and %s to %s instead"),
a->path, new_path1, b->path, new_path2);
- if (would_lose_untracked(path))
+ if (was_dirty(o, path))
+ output(o, 1, _("Refusing to lose dirty file at %s"),
+ path);
+ else if (would_lose_untracked(path))
/*
* Only way we get here is if both renames were from
* a directory rename AND user had an untracked file
diff_setup(&opts);
opts.flags.recursive = 1;
opts.flags.rename_empty = 0;
- opts.detect_rename = DIFF_DETECT_RENAME;
+ opts.detect_rename = merge_detect_rename(o);
+ /*
+ * We do not have logic to handle the detection of copies. In
+ * fact, it may not even make sense to add such logic: would we
+ * really want a change to a base file to be propagated through
+ * multiple other files by a merge?
+ */
+ if (opts.detect_rename > DIFF_DETECT_RENAME)
+ opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit :
1000;
char *new_path;
struct diff_filepair *pair = pairs->queue[i];
- if (pair->status == 'D')
+ if (pair->status != 'A' && pair->status != 'R')
continue;
dir_rename_ent = check_dir_renamed(pair->two->path,
dir_renames);
{
struct string_list_item *item;
int stage = (tree == a_tree ? 2 : 3);
+ int update_wd;
/*
* In all cases where we can do directory rename detection,
* saying the file would have been overwritten), but it might
* be dirty, though.
*/
- remove_file(o, 1, pair->two->path, 0 /* no_wd */);
+ update_wd = !was_dirty(o, pair->two->path);
+ if (!update_wd)
+ output(o, 1, _("Refusing to lose dirty file at %s"),
+ pair->two->path);
+ remove_file(o, 1, pair->two->path, !update_wd);
/* Find or create a new re->dst_entry */
item = string_list_lookup(entries, new_path);
struct diff_filepair *pair = pairs->queue[i];
char *new_path; /* non-NULL only with directory renames */
- if (pair->status == 'D') {
+ if (pair->status != 'A' && pair->status != 'R') {
diff_free_filepair(pair);
continue;
}
const char *ren2_dst = ren2->pair->two->path;
enum rename_type rename_type;
if (strcmp(ren1_src, ren2_src) != 0)
- die("BUG: ren1_src != ren2_src");
+ BUG("ren1_src != ren2_src");
ren2->dst_entry->processed = 1;
ren2->processed = 1;
if (strcmp(ren1_dst, ren2_dst) != 0) {
ren2 = lookup->util;
ren2_dst = ren2->pair->two->path;
if (strcmp(ren1_dst, ren2_dst) != 0)
- die("BUG: ren1_dst != ren2_dst");
+ BUG("ren1_dst != ren2_dst");
clean_merge = 0;
ren2->processed = 1;
* add-source case).
*/
remove_file(o, 1, ren1_src,
- renamed_stage == 2 || !was_tracked(ren1_src));
+ renamed_stage == 2 || !was_tracked(o, ren1_src));
oidcpy(&src_other.oid,
&ren1->src_entry->stages[other_stage].oid);
ri->head_renames = NULL;
ri->merge_renames = NULL;
- if (!o->detect_rename)
+ if (!merge_detect_rename(o))
return 1;
head_pairs = get_diffpairs(o, common, head);
static int merge_content(struct merge_options *o,
const char *path,
+ int is_dirty,
struct object_id *o_oid, int o_mode,
struct object_id *a_oid, int a_mode,
struct object_id *b_oid, int b_mode,
S_ISGITLINK(pair1->two->mode)))
df_conflict_remains = 1;
}
- if (merge_file_special_markers(o, &one, &a, &b,
+ if (merge_file_special_markers(o, &one, &a, &b, path,
o->branch1, path1,
o->branch2, path2, &mfi))
return -1;
- if (mfi.clean && !df_conflict_remains &&
- oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) {
- int path_renamed_outside_HEAD;
+ /*
+ * We can skip updating the working tree file iff:
+ * a) The merge is clean
+ * b) The merge matches what was in HEAD (content, mode, pathname)
+ * c) The target path is usable (i.e. not involved in D/F conflict)
+ */
+ if (mfi.clean &&
+ was_tracked_and_matches(o, path, &mfi.oid, mfi.mode) &&
+ !df_conflict_remains) {
output(o, 3, _("Skipped %s (merged same as existing)"), path);
- /*
- * The content merge resulted in the same file contents we
- * already had. We can return early if those file contents
- * are recorded at the correct path (which may not be true
- * if the merge involves a rename).
- */
- path_renamed_outside_HEAD = !path2 || !strcmp(path, path2);
- if (!path_renamed_outside_HEAD) {
- add_cacheinfo(o, mfi.mode, &mfi.oid, path,
- 0, (!o->call_depth), 0);
- return mfi.clean;
- }
- } else
- output(o, 2, _("Auto-merging %s"), path);
+ if (add_cacheinfo(o, mfi.mode, &mfi.oid, path,
+ 0, (!o->call_depth && !is_dirty), 0))
+ return -1;
+ return mfi.clean;
+ }
if (!mfi.clean) {
if (S_ISGITLINK(mfi.mode))
return -1;
}
- if (df_conflict_remains) {
+ if (df_conflict_remains || is_dirty) {
char *new_path;
if (o->call_depth) {
remove_file_from_cache(path);
if (update_stages(o, path, &one, &a, &b))
return -1;
} else {
- int file_from_stage2 = was_tracked(path);
+ int file_from_stage2 = was_tracked(o, path);
struct diff_filespec merged;
oidcpy(&merged.oid, &mfi.oid);
merged.mode = mfi.mode;
}
new_path = unique_path(o, path, rename_conflict_info->branch1);
+ if (is_dirty) {
+ output(o, 1, _("Refusing to lose dirty file at %s"),
+ path);
+ }
output(o, 1, _("Adding as %s instead"), new_path);
if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) {
free(new_path);
mfi.clean = 0;
} else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path))
return -1;
- return mfi.clean;
+ return !is_dirty && mfi.clean;
+}
+
+static int conflict_rename_normal(struct merge_options *o,
+ const char *path,
+ struct object_id *o_oid, unsigned int o_mode,
+ struct object_id *a_oid, unsigned int a_mode,
+ struct object_id *b_oid, unsigned int b_mode,
+ struct rename_conflict_info *ci)
+{
+ /* Merge the content and write it out */
+ return merge_content(o, path, was_dirty(o, path),
+ o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
+ ci);
}
/* Per entry merge function */
switch (conflict_info->rename_type) {
case RENAME_NORMAL:
case RENAME_ONE_FILE_TO_ONE:
- clean_merge = merge_content(o, path,
- o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
- conflict_info);
+ clean_merge = conflict_rename_normal(o,
+ path,
+ o_oid, o_mode,
+ a_oid, a_mode,
+ b_oid, b_mode,
+ conflict_info);
break;
case RENAME_DIR:
clean_merge = 1;
} else if (a_oid && b_oid) {
/* Case C: Added in both (check for same permissions) and */
/* case D: Modified in both, but differently. */
- clean_merge = merge_content(o, path,
+ int is_dirty = 0; /* unpack_trees would have bailed if dirty */
+ clean_merge = merge_content(o, path, is_dirty,
o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
NULL);
} else if (!o_oid && !a_oid && !b_oid) {
*/
remove_file(o, 1, path, !a_mode);
} else
- die("BUG: fatal merge failure, shouldn't happen.");
+ BUG("fatal merge failure, shouldn't happen.");
return clean_merge;
}
return 1;
}
- code = git_merge_trees(o->call_depth, common, head, merge);
+ code = git_merge_trees(o, common, head, merge);
if (code != 0) {
if (show(o, 4) || o->call_depth)
for (i = 0; i < entries->nr; i++) {
struct stage_data *e = entries->items[i].util;
if (!e->processed)
- die("BUG: unprocessed path??? %s",
+ BUG("unprocessed path??? %s",
entries->items[i].string);
}
else
clean = 1;
+ /* Free the extra index left from git_merge_trees() */
+ /*
+ * FIXME: Need to also free data allocated by
+ * setup_unpack_trees_porcelain() tucked away in o->unpack_opts.msgs,
+ * but the problem is that only half of it refers to dynamically
+ * allocated data, while the other half points at static strings.
+ */
+ discard_index(&o->orig_index);
+
if (o->call_depth && !(*result = write_tree_from_memory(o)))
return -1;
read_cache();
o->ancestor = "merged common ancestors";
- clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree,
+ clean = merge_trees(o, get_commit_tree(h1), get_commit_tree(h2),
+ get_commit_tree(merged_common_ancestors),
&mrtree);
if (clean < 0) {
flush_output(o);
static void merge_recursive_config(struct merge_options *o)
{
+ char *value = NULL;
git_config_get_int("merge.verbosity", &o->verbosity);
git_config_get_int("diff.renamelimit", &o->diff_rename_limit);
git_config_get_int("merge.renamelimit", &o->merge_rename_limit);
+ if (!git_config_get_string("diff.renames", &value)) {
+ o->diff_detect_rename = git_config_rename("diff.renames", value);
+ free(value);
+ }
+ if (!git_config_get_string("merge.renames", &value)) {
+ o->merge_detect_rename = git_config_rename("merge.renames", value);
+ free(value);
+ }
git_config(git_xmerge_config, NULL);
}
o->diff_rename_limit = -1;
o->merge_rename_limit = -1;
o->renormalize = 0;
- o->detect_rename = 1;
+ o->diff_detect_rename = -1;
+ o->merge_detect_rename = -1;
merge_recursive_config(o);
merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
if (merge_verbosity)
else if (!strcmp(s, "no-renormalize"))
o->renormalize = 0;
else if (!strcmp(s, "no-renames"))
- o->detect_rename = 0;
+ o->merge_detect_rename = 0;
else if (!strcmp(s, "find-renames")) {
- o->detect_rename = 1;
+ o->merge_detect_rename = 1;
o->rename_score = 0;
}
else if (skip_prefix(s, "find-renames=", &arg) ||
skip_prefix(s, "rename-threshold=", &arg)) {
if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
return -1;
- o->detect_rename = 1;
+ o->merge_detect_rename = 1;
}
else
return -1;