#include "unpack-trees.h"
#include "path-list.h"
#include "xdiff-interface.h"
+#include "attr.h"
+
+static int subtree_merge;
+
+static struct tree *shift_tree_object(struct tree *one, struct tree *two)
+{
+ unsigned char shifted[20];
+
+ /*
+ * NEEDSWORK: this limits the recursion depth to hardcoded
+ * value '2' to avoid excessive overhead.
+ */
+ shift_tree(one->object.sha1, two->object.sha1, shifted, 2);
+ if (!hashcmp(two->object.sha1, shifted))
+ return two;
+ return lookup_tree(shifted);
+}
/*
* A virtual commit has
struct cache_entry *ce;
ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
if (!ce)
- return error("cache_addinfo failed: %s", strerror(cache_errno));
+ return error("addinfo_cache failed for path '%s'", path);
return add_cache_entry(ce, options);
}
{
struct tree *result = NULL;
- if (unmerged_index())
+ if (unmerged_index()) {
+ int i;
+ output(0, "There are unmerged index entries:");
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce))
+ output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name);
+ }
return NULL;
+ }
if (!active_cache_tree)
active_cache_tree = cache_tree();
update_wd = 0;
if (update_wd) {
- char type[20];
+ enum object_type type;
void *buf;
unsigned long size;
- buf = read_sha1_file(sha, type, &size);
+ buf = read_sha1_file(sha, &type, &size);
if (!buf)
die("cannot read object %s '%s'", sha1_to_hex(sha), path);
- if (strcmp(type, blob_type) != 0)
+ if (type != OBJ_BLOB)
die("blob expected for %s '%s'", sha1_to_hex(sha), path);
- if (S_ISREG(mode)) {
+ if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) {
int fd;
if (mkdir_p(path, 0777))
die("failed to create path %s: %s", path, strerror(errno));
memcpy(lnk, buf, size);
lnk[size] = '\0';
mkdir_p(path, 0777);
- unlink(lnk);
+ unlink(path);
symlink(lnk, path);
+ free(lnk);
} else
die("do not know what to do with %06o %s '%s'",
mode, sha1_to_hex(sha), path);
static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
{
unsigned long size;
- char type[20];
+ enum object_type type;
if (!hashcmp(sha1, null_sha1)) {
mm->ptr = xstrdup("");
return;
}
- mm->ptr = read_sha1_file(sha1, type, &size);
- if (!mm->ptr || strcmp(type, blob_type))
+ mm->ptr = read_sha1_file(sha1, &type, &size);
+ if (!mm->ptr || type != OBJ_BLOB)
die("unable to read blob object %s", sha1_to_hex(sha1));
mm->size = size;
}
+/* Low-level merge functions */
+typedef int (*ll_merge_fn)(mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ mmbuffer_t *result);
+
+static int ll_xdl_merge(mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ mmbuffer_t *result)
+{
+ xpparam_t xpp;
+
+ memset(&xpp, 0, sizeof(xpp));
+ return xdl_merge(orig,
+ src1, name1,
+ src2, name2,
+ &xpp, XDL_MERGE_ZEALOUS,
+ result);
+}
+
+static int ll_union_merge(mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ mmbuffer_t *result)
+{
+ char *src, *dst;
+ long size;
+ const int marker_size = 7;
+
+ int status = ll_xdl_merge(orig, src1, NULL, src2, NULL, result);
+ if (status <= 0)
+ return status;
+ size = result->size;
+ src = dst = result->ptr;
+ while (size) {
+ char ch;
+ if ((marker_size < size) &&
+ (*src == '<' || *src == '=' || *src == '>')) {
+ int i;
+ ch = *src;
+ for (i = 0; i < marker_size; i++)
+ if (src[i] != ch)
+ goto not_a_marker;
+ if (src[marker_size] != '\n')
+ goto not_a_marker;
+ src += marker_size + 1;
+ size -= marker_size + 1;
+ continue;
+ }
+ not_a_marker:
+ do {
+ ch = *src++;
+ *dst++ = ch;
+ size--;
+ } while (ch != '\n' && size);
+ }
+ result->size = dst - result->ptr;
+ return 0;
+}
+
+static int ll_binary_merge(mmfile_t *orig,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
+ mmbuffer_t *result)
+{
+ /*
+ * The tentative merge result is "ours" for the final round,
+ * or common ancestor for an internal merge. Still return
+ * "conflicted merge" status.
+ */
+ mmfile_t *stolen = index_only ? orig : src1;
+
+ result->ptr = stolen->ptr;
+ result->size = stolen->size;
+ stolen->ptr = NULL;
+ return 1;
+}
+
+static struct {
+ const char *name;
+ ll_merge_fn fn;
+} ll_merge_fns[] = {
+ { "text", ll_xdl_merge },
+ { "binary", ll_binary_merge },
+ { "union", ll_union_merge },
+ { NULL, NULL },
+};
+
+static ll_merge_fn find_ll_merge_fn(void *merge_attr)
+{
+ const char *name;
+ int i;
+
+ if (ATTR_TRUE(merge_attr) || ATTR_UNSET(merge_attr))
+ return ll_xdl_merge;
+ else if (ATTR_FALSE(merge_attr))
+ return ll_binary_merge;
+
+ /* Otherwise merge_attr may name the merge function */
+ name = merge_attr;
+ for (i = 0; ll_merge_fns[i].name; i++)
+ if (!strcmp(ll_merge_fns[i].name, name))
+ return ll_merge_fns[i].fn;
+
+ /* default to the 3-way */
+ return ll_xdl_merge;
+}
+
+static void *git_path_check_merge(const char *path)
+{
+ static struct git_attr_check attr_merge_check;
+
+ if (!attr_merge_check.attr)
+ attr_merge_check.attr = git_attr("merge", 5);
+
+ if (git_checkattr(path, 1, &attr_merge_check))
+ return ATTR__UNSET;
+ return attr_merge_check.value;
+}
+
+static int ll_merge(mmbuffer_t *result_buf,
+ struct diff_filespec *o,
+ struct diff_filespec *a,
+ struct diff_filespec *b,
+ const char *branch1,
+ const char *branch2)
+{
+ mmfile_t orig, src1, src2;
+ char *name1, *name2;
+ int merge_status;
+ void *merge_attr;
+ ll_merge_fn fn;
+
+ name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+ name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+
+ fill_mm(o->sha1, &orig);
+ fill_mm(a->sha1, &src1);
+ fill_mm(b->sha1, &src2);
+
+ merge_attr = git_path_check_merge(a->path);
+ fn = find_ll_merge_fn(merge_attr);
+
+ merge_status = fn(&orig, &src1, name1, &src2, name2, result_buf);
+
+ free(name1);
+ free(name2);
+ free(orig.ptr);
+ free(src1.ptr);
+ free(src2.ptr);
+ return merge_status;
+}
+
static struct merge_file_info merge_file(struct diff_filespec *o,
struct diff_filespec *a, struct diff_filespec *b,
const char *branch1, const char *branch2)
else if (sha_eq(b->sha1, o->sha1))
hashcpy(result.sha, a->sha1);
else if (S_ISREG(a->mode)) {
- mmfile_t orig, src1, src2;
mmbuffer_t result_buf;
- xpparam_t xpp;
- char *name1, *name2;
int merge_status;
- name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
- name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
-
- fill_mm(o->sha1, &orig);
- fill_mm(a->sha1, &src1);
- fill_mm(b->sha1, &src2);
-
- memset(&xpp, 0, sizeof(xpp));
- merge_status = xdl_merge(&orig,
- &src1, name1,
- &src2, name2,
- &xpp, XDL_MERGE_ZEALOUS,
- &result_buf);
- free(name1);
- free(name2);
- free(orig.ptr);
- free(src1.ptr);
- free(src2.ptr);
+ merge_status = ll_merge(&result_buf, o, a, b,
+ branch1, branch2);
if ((merge_status < 0) || !result_buf.ptr)
die("Failed to execute internal merge");
ren2_dst, branch1, dst_name2);
remove_file(0, ren2_dst, 0);
}
- update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
- update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+ if (index_only) {
+ remove_file_from_cache(dst_name1);
+ remove_file_from_cache(dst_name2);
+ /*
+ * Uncomment to leave the conflicting names in the resulting tree
+ *
+ * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);
+ * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);
+ */
+ } else {
+ update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);
+ update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);
+ }
while (delp--)
free(del[delp]);
}
if (strcmp(ren1_dst, ren2_dst) != 0) {
clean_merge = 0;
output(1, "CONFLICT (rename/rename): "
- "Rename %s->%s in branch %s "
- "rename %s->%s in %s",
+ "Rename \"%s\"->\"%s\" in branch \"%s\" "
+ "rename \"%s\"->\"%s\" in \"%s\"%s",
src, ren1_dst, branch1,
- src, ren2_dst, branch2);
+ src, ren2_dst, branch2,
+ index_only ? " (left unresolved)": "");
+ if (index_only) {
+ remove_file_from_cache(src);
+ update_file(0, ren1->pair->one->sha1,
+ ren1->pair->one->mode, src);
+ }
conflict_rename_rename(ren1, branch1, ren2, branch2);
} else {
struct merge_file_info mfi;
struct tree **result)
{
int code, clean;
+
+ if (subtree_merge) {
+ merge = shift_tree_object(head, merge);
+ common = shift_tree_object(head, common);
+ }
+
if (sha_eq(common->object.sha1, merge->object.sha1)) {
output(0, "Already uptodate!");
*result = head;
tree->object.parsed = 1;
tree->object.type = OBJ_TREE;
- pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1);
+ pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1);
merged_common_ancestors = make_virtual_commit(tree, "ancestor");
}
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
int index_fd;
+ if (argv[0]) {
+ int namelen = strlen(argv[0]);
+ if (8 < namelen &&
+ !strcmp(argv[0] + namelen - 8, "-subtree"))
+ subtree_merge = 1;
+ }
+
git_config(merge_config);
if (getenv("GIT_MERGE_VERBOSITY"))
verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
if (show(3))
printf("Merging %s with %s\n", branch1, branch2);
- index_fd = hold_lock_file_for_update(lock, get_index_file(), 1);
+ index_fd = hold_locked_index(lock, 1);
for (i = 0; i < bases_count; i++) {
struct commit *ancestor = get_ref(bases[i]);
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
- close(index_fd) || commit_lock_file(lock)))
+ close(index_fd) || commit_locked_index(lock)))
die ("unable to write %s", get_index_file());
return clean ? 0: 1;