opts.fn = threeway_merge;
opts.src_index = &the_index;
opts.dst_index = &the_index;
- set_porcelain_error_msgs(opts.msgs, "merge");
+ setup_unpack_trees_porcelain(&opts, "merge");
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
merge_status = ll_merge(result_buf, a->path, &orig, base_name,
&src1, name1, &src2, name2,
- (!!o->call_depth) | (favor << 1));
+ ((o->call_depth ? LL_OPT_VIRTUAL_ANCESTOR : 0) |
+ (o->renormalize ? LL_OPT_RENORMALIZE : 0) |
+ create_ll_flag(favor)));
free(name1);
free(name2);
struct string_list_item *item;
/* we only use sha1 and mode of these */
struct diff_filespec src_other, dst_other;
- int try_merge, stage = a_renames == renames1 ? 3: 2;
+ int try_merge;
- remove_file(o, 1, ren1_src, o->call_depth || stage == 3);
+ /*
+ * unpack_trees loads entries from common-commit
+ * into stage 1, from head-commit into stage 2, and
+ * from merge-commit into stage 3. We keep track
+ * of which side corresponds to the rename.
+ */
+ int renamed_stage = a_renames == renames1 ? 2 : 3;
+ int other_stage = a_renames == renames1 ? 3 : 2;
- hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
- src_other.mode = ren1->src_entry->stages[stage].mode;
- hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);
- dst_other.mode = ren1->dst_entry->stages[stage].mode;
+ remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+ hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
+ src_other.mode = ren1->src_entry->stages[other_stage].mode;
+ hashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);
+ dst_other.mode = ren1->dst_entry->stages[other_stage].mode;
try_merge = 0;
- if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
- clean_merge = 0;
- output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
- " directory %s added in %s",
- ren1_src, ren1_dst, branch1,
- ren1_dst, branch2);
- conflict_rename_dir(o, ren1, branch1);
- } else if (sha_eq(src_other.sha1, null_sha1)) {
+ if (sha_eq(src_other.sha1, null_sha1)) {
clean_merge = 0;
output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s "
"and deleted in %s",
ren1->pair->two : NULL,
branch1 == o->branch1 ?
NULL : ren1->pair->two, 1);
+ } else if ((dst_other.mode == ren1->pair->two->mode) &&
+ sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
+ /* Added file on the other side
+ identical to the file being
+ renamed: clean merge */
+ update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
} else if (!sha_eq(dst_other.sha1, null_sha1)) {
const char *new_path;
clean_merge = 0;
if (!ren1->dst_entry->stages[2].mode !=
!ren1->dst_entry->stages[3].mode)
ren1->dst_entry->processed = 0;
+ } else if (string_list_has_string(&o->current_directory_set, ren1_dst)) {
+ clean_merge = 0;
+ output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s "
+ " directory %s added in %s",
+ ren1_src, ren1_dst, branch1,
+ ren1_dst, branch2);
+ conflict_rename_dir(o, ren1, branch1);
} else {
if (mfi.merge || !mfi.clean)
output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha;
}
+static int read_sha1_strbuf(const unsigned char *sha1, struct strbuf *dst)
+{
+ void *buf;
+ enum object_type type;
+ unsigned long size;
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return error("cannot read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_BLOB) {
+ free(buf);
+ return error("object %s is not a blob", sha1_to_hex(sha1));
+ }
+ strbuf_attach(dst, buf, size, size + 1);
+ return 0;
+}
+
+static int blob_unchanged(const unsigned char *o_sha,
+ const unsigned char *a_sha,
+ int renormalize, const char *path)
+{
+ struct strbuf o = STRBUF_INIT;
+ struct strbuf a = STRBUF_INIT;
+ int ret = 0; /* assume changed for safety */
+
+ if (sha_eq(o_sha, a_sha))
+ return 1;
+ if (!renormalize)
+ return 0;
+
+ assert(o_sha && a_sha);
+ if (read_sha1_strbuf(o_sha, &o) || read_sha1_strbuf(a_sha, &a))
+ goto error_return;
+ /*
+ * Note: binary | is used so that both renormalizations are
+ * performed. Comparison can be skipped if both files are
+ * unchanged since their sha1s have already been compared.
+ */
+ if (renormalize_buffer(path, o.buf, o.len, &o) |
+ renormalize_buffer(path, a.buf, o.len, &a))
+ ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
+
+error_return:
+ strbuf_release(&o);
+ strbuf_release(&a);
+ return ret;
+}
+
/* Per entry merge function */
static int process_entry(struct merge_options *o,
const char *path, struct stage_data *entry)
print_index_entry("\tpath: ", entry);
*/
int clean_merge = 1;
+ int normalize = o->renormalize;
unsigned o_mode = entry->stages[1].mode;
unsigned a_mode = entry->stages[2].mode;
unsigned b_mode = entry->stages[3].mode;
if (o_sha && (!a_sha || !b_sha)) {
/* Case A: Deleted in one */
if ((!a_sha && !b_sha) ||
- (sha_eq(a_sha, o_sha) && !b_sha) ||
- (!a_sha && sha_eq(b_sha, o_sha))) {
+ (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||
+ (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {
/* Deleted in both or deleted in one and
* unchanged in the other */
if (a_sha)
const char *conf;
struct stat st;
- /* We currently only handle D->F cases */
- assert((!o_sha && a_sha && !b_sha) ||
- (!o_sha && !a_sha && b_sha));
+ if (!((!o_sha && a_sha && !b_sha) || (!o_sha && !a_sha && b_sha)))
+ return 1; /* we don't handle non D-F cases */
entry->processed = 1;
return clean_merge;
}
-void set_porcelain_error_msgs(const char **msgs, const char *cmd)
-{
- const char *msg;
- char *tmp;
- const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
- if (advice_commit_before_merge)
- msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
- "Please, commit your changes or stash them before you can %s.";
- else
- msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
- tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
- sprintf(tmp, msg, cmd, cmd2);
- msgs[ERROR_WOULD_OVERWRITE] = tmp;
- msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
-
- msgs[ERROR_NOT_UPTODATE_DIR] =
- "Updating the following directories would lose untracked files in it:\n%s";
-
- if (advice_commit_before_merge)
- msg = "The following untracked working tree files would be %s by %s:\n%%s"
- "Please move or remove them before you can %s.";
- else
- msg = "The following untracked working tree files would be %s by %s:\n%%s";
- tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
- sprintf(tmp, msg, "removed", cmd, cmd2);
- msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
- tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
- sprintf(tmp, msg, "overwritten", cmd, cmd2);
- msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
-
- /*
- * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
- * cannot easily display it as a list.
- */
- msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind.";
-
- msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
- "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
- msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
- "The following Working tree files would be overwritten by sparse checkout update:\n%s";
- msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
- "The following Working tree files would be removed by sparse checkout update:\n%s";
-}
-
int merge_trees(struct merge_options *o,
struct tree *head,
struct tree *merge,
&& !process_df_entry(o, path, e))
clean = 0;
}
+ for (i = 0; i < entries->nr; i++) {
+ struct stage_data *e = entries->items[i].util;
+ if (!e->processed)
+ die("Unprocessed path??? %s",
+ entries->items[i].string);
+ }
string_list_clear(re_merge, 0);
string_list_clear(re_head, 0);
o->buffer_output = 1;
o->diff_rename_limit = -1;
o->merge_rename_limit = -1;
+ o->renormalize = 0;
git_config(merge_recursive_config, o);
if (getenv("GIT_MERGE_VERBOSITY"))
o->verbosity =