in merge-ort.c [3971:4337]
static int process_entry(struct merge_options *opt,
const char *path,
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() */
assert(ci->match_mask == 0 || ci->match_mask == 3 ||
ci->match_mask == 5 || ci->match_mask == 6);
if (ci->dirmask) {
record_entry_for_tree(dir_metadata, path, &ci->merged);
if (ci->filemask == 0)
/* nothing else to handle */
return 0;
assert(ci->df_conflict);
}
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(the_hash_algo));
}
} 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 0;
}
/*
* 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 = mem_pool_calloc(&opt->priv->pool, 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(the_hash_algo));
}
/*
* 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, path, branch);
strmap_put(&opt->priv->paths, path, new_ci);
path_msg(opt, CONFLICT_FILE_DIRECTORY, 0,
path, old_path, NULL, NULL,
_("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.
*/
if (ci->match_mask) {
ci->merged.clean = !ci->df_conflict && !ci->path_conflict;
if (ci->match_mask == 6) {
/* stages[1] == stages[2] */
ci->merged.result.mode = ci->stages[1].mode;
oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
} else {
/* determine the mask of the side that didn't match */
unsigned int othermask = 7 & ~ci->match_mask;
int side = (othermask == 4) ? 2 : 1;
ci->merged.result.mode = ci->stages[side].mode;
ci->merged.is_null = !ci->merged.result.mode;
if (ci->merged.is_null)
ci->merged.clean = 1;
oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
assert(othermask == 2 || othermask == 4);
assert(ci->merged.is_null ==
(ci->filemask == ci->match_mask));
}
} else if (ci->filemask >= 6 &&
(S_IFMT & ci->stages[1].mode) !=
(S_IFMT & ci->stages[2].mode)) {
/* 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 = mem_pool_alloc(&opt->priv->pool,
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;
}
if (rename_a)
a_path = unique_path(opt, path, opt->branch1);
if (rename_b)
b_path = unique_path(opt, path, opt->branch2);
if (rename_a && rename_b) {
path_msg(opt, CONFLICT_DISTINCT_MODES, 0,
path, a_path, b_path, NULL,
_("CONFLICT (distinct types): %s had "
"different types on each side; "
"renamed both of them so each can "
"be recorded somewhere."),
path);
} else {
path_msg(opt, CONFLICT_DISTINCT_MODES, 0,
path, rename_a ? a_path : b_path,
NULL, NULL,
_("CONFLICT (distinct types): %s had "
"different types on each side; "
"renamed one of them so each can be "
"recorded somewhere."),
path);
}
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(the_hash_algo));
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(the_hash_algo));
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(the_hash_algo));
ci->filemask = 3;
if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) {
ci->stages[0].mode = 0;
oidcpy(&ci->stages[0].oid, null_oid(the_hash_algo));
ci->filemask = 2;
}
/* Insert entries into opt->priv_paths */
assert(rename_a || rename_b);
if (rename_a)
strmap_put(&opt->priv->paths, a_path, ci);
if (!rename_b)
b_path = path;
strmap_put(&opt->priv->paths, b_path, new_ci);
if (rename_a && rename_b)
strmap_remove(&opt->priv->paths, path, 0);
/*
* 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) {
/* Need a two-way or three-way content merge */
struct version_info merged_file;
int 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);
if (clean_merge < 0)
return -1;
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, CONFLICT_CONTENTS, 0,
path, NULL, NULL, NULL,
_("CONFLICT (%s): Merge conflict in %s"),
reason, path);
}
} else if (ci->filemask == 3 || ci->filemask == 5) {
/* Modify/delete */
const char *modify_branch, *delete_branch;
int side = (ci->filemask == 5) ? 2 : 1;
int index = opt->priv->call_depth ? 0 : side;
ci->merged.result.mode = ci->stages[index].mode;
oidcpy(&ci->merged.result.oid, &ci->stages[index].oid);
ci->merged.clean = 0;
modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
if (opt->renormalize &&
blob_unchanged(opt, &ci->stages[0], &ci->stages[side],
path)) {
if (!ci->path_conflict) {
/*
* Blob unchanged after renormalization, so
* there's no modify/delete conflict after all;
* we can just remove the file.
*/
ci->merged.is_null = 1;
ci->merged.clean = 1;
/*
* file goes away => even if there was a
* directory/file conflict there isn't one now.
*/
ci->df_conflict = 0;
} else {
/* rename/delete, so conflict remains */
}
} else 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, CONFLICT_MODIFY_DELETE, 0,
path, NULL, NULL, NULL,
_("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->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(the_hash_algo));
assert(!ci->df_conflict);
ci->merged.clean = !ci->path_conflict;
}
/*
* If still conflicted, record it separately. This allows us to later
* iterate over just conflicted entries when updating the index instead
* of iterating over all entries.
*/
if (!ci->merged.clean)
strmap_put(&opt->priv->conflicted, path, ci);
/* Record metadata for ci->merged in dir_metadata */
record_entry_for_tree(dir_metadata, path, &ci->merged);
return 0;
}