Improved three-way blob merging code
authorLinus Torvalds <torvalds@osdl.org>
Thu, 29 Jun 2006 05:06:36 +0000 (22:06 -0700)
committerJunio C Hamano <junkio@cox.net>
Thu, 29 Jun 2006 05:24:45 +0000 (22:24 -0700)
This fleshes out the code that generates a three-way merge of a set of
blobs.

It still actually does the three-way merge using an external executable
(ie just calling "merge"), but the interfaces have been cleaned up a lot
and are now fully based on the 'mmfile_t' interface, so if libxdiff were
to ever grow a compatible three-way-merge, it could probably be directly
plugged in.

It also uses the previous XDL_EMIT_COMMON functionality extension to
libxdiff to generate a made-up base file for the merge for the case where
no base file previously existed. This should be equivalent to what we
currently do in git-merge-one-file.sh:

diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add

except it should be much simpler and can be done using the direct libxdiff
interfaces.

Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
Makefile
merge-file.c [new file with mode: 0644]
merge-tree.c

index cde619c498da717ea665430f7d395358d0b1d06e..e880457526ea2fa42eef9577294c9f539499e7f6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -217,7 +217,7 @@ LIB_OBJS = \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
-       alloc.o $(DIFF_OBJS)
+       alloc.o merge-file.o $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
        builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
diff --git a/merge-file.c b/merge-file.c
new file mode 100644 (file)
index 0000000..f32c653
--- /dev/null
@@ -0,0 +1,166 @@
+#include "cache.h"
+#include "run-command.h"
+#include "xdiff-interface.h"
+#include "blob.h"
+
+static void rm_temp_file(const char *filename)
+{
+       unlink(filename);
+       free((void *)filename);
+}
+
+static const char *write_temp_file(mmfile_t *f)
+{
+       int fd;
+       const char *tmp = getenv("TMPDIR");
+       char *filename;
+
+       if (!tmp)
+               tmp = "/tmp";
+       filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX");
+       fd = mkstemp(filename);
+       if (fd < 0)
+               return NULL;
+       filename = strdup(filename);
+       if (f->size != xwrite(fd, f->ptr, f->size)) {
+               rm_temp_file(filename);
+               return NULL;
+       }
+       close(fd);
+       return filename;
+}
+
+static void *read_temp_file(const char *filename, unsigned long *size)
+{
+       struct stat st;
+       char *buf = NULL;
+       int fd = open(filename, O_RDONLY);
+       if (fd < 0)
+               return NULL;
+       if (!fstat(fd, &st)) {
+               *size = st.st_size;
+               buf = xmalloc(st.st_size);
+               if (st.st_size != xread(fd, buf, st.st_size)) {
+                       free(buf);
+                       buf = NULL;
+               }
+       }
+       close(fd);
+       return buf;
+}
+
+static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
+{
+       void *buf;
+       unsigned long size;
+       char type[20];
+
+       buf = read_sha1_file(obj->object.sha1, type, &size);
+       if (!buf)
+               return -1;
+       if (strcmp(type, blob_type))
+               return -1;
+       f->ptr = buf;
+       f->size = size;
+       return 0;
+}
+
+static void free_mmfile(mmfile_t *f)
+{
+       free(f->ptr);
+}
+
+static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
+{
+       void *res;
+       const char *t1, *t2, *t3;
+
+       t1 = write_temp_file(base);
+       t2 = write_temp_file(our);
+       t3 = write_temp_file(their);
+       res = NULL;
+       if (t1 && t2 && t3) {
+               int code = run_command("merge", t2, t1, t3, NULL);
+               if (!code || code == -1)
+                       res = read_temp_file(t2, size);
+       }
+       rm_temp_file(t1);
+       rm_temp_file(t2);
+       rm_temp_file(t3);
+       return res;
+}
+
+static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       mmfile_t *dst = priv_;
+
+       for (i = 0; i < nbuf; i++) {
+               memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size);
+               dst->size += mb[i].size;
+       }
+       return 0;
+}
+
+static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
+{
+       unsigned long size = f1->size < f2->size ? f1->size : f2->size;
+       void *ptr = xmalloc(size);
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = XDL_EMIT_COMMON;
+       ecb.outf = common_outf;
+
+       res->ptr = ptr;
+       res->size = 0;
+
+       ecb.priv = res;
+       return xdl_diff(f1, f2, &xpp, &xecfg, &ecb);
+}
+
+void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size)
+{
+       void *res = NULL;
+       mmfile_t f1, f2, common;
+
+       /*
+        * Removed in either branch?
+        *
+        * NOTE! This depends on the caller having done the
+        * proper warning about removing a file that got
+        * modified in the other branch!
+        */
+       if (!our || !their) {
+               char type[20];
+               if (base)
+                       return NULL;
+               if (!our)
+                       our = their;
+               return read_sha1_file(our->object.sha1, type, size);
+       }
+
+       if (fill_mmfile_blob(&f1, our) < 0)
+               goto out_no_mmfile;
+       if (fill_mmfile_blob(&f2, their) < 0)
+               goto out_free_f1;
+
+       if (base) {
+               if (fill_mmfile_blob(&common, base) < 0)
+                       goto out_free_f2_f1;
+       } else {
+               if (generate_common_file(&common, &f1, &f2) < 0)
+                       goto out_free_f2_f1;
+       }
+       res = three_way_filemerge(&common, &f1, &f2, size);
+       free_mmfile(&common);
+out_free_f2_f1:
+       free_mmfile(&f2);
+out_free_f1:
+       free_mmfile(&f1);
+out_no_mmfile:
+       return res;
+}
index fd0c2111c48dc13b9d0d713ec7aa4a181daedb6a..7cf00be6d51b17ca60979a9c856e67405906d9a7 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "tree-walk.h"
+#include "xdiff-interface.h"
 #include "blob.h"
 
 static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
@@ -52,6 +53,77 @@ static const char *explanation(struct merge_list *entry)
        return "removed in remote";
 }
 
+extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *);
+
+static void *result(struct merge_list *entry, unsigned long *size)
+{
+       char type[20];
+       struct blob *base, *our, *their;
+
+       if (!entry->stage)
+               return read_sha1_file(entry->blob->object.sha1, type, size);
+       base = NULL;
+       if (entry->stage == 1) {
+               base = entry->blob;
+               entry = entry->link;
+       }
+       our = NULL;
+       if (entry && entry->stage == 2) {
+               our = entry->blob;
+               entry = entry->link;
+       }
+       their = NULL;
+       if (entry)
+               their = entry->blob;
+       return merge_file(base, our, their, size);
+}
+
+static void *origin(struct merge_list *entry, unsigned long *size)
+{
+       char type[20];
+       while (entry) {
+               if (entry->stage == 2)
+                       return read_sha1_file(entry->blob->object.sha1, type, size);
+               entry = entry->link;
+       }
+       return NULL;
+}
+
+static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               printf("%.*s", (int) mb[i].size, mb[i].ptr);
+       return 0;
+}
+
+static void show_diff(struct merge_list *entry)
+{
+       unsigned long size;
+       mmfile_t src, dst;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = 0;
+       ecb.outf = show_outf;
+       ecb.priv = NULL;
+
+       src.ptr = origin(entry, &size);
+       if (!src.ptr)
+               size = 0;
+       src.size = size;
+       dst.ptr = result(entry, &size);
+       if (!dst.ptr)
+               size = 0;
+       dst.size = size;
+       xdl_diff(&src, &dst, &xpp, &xecfg, &ecb);
+       free(src.ptr);
+       free(dst.ptr);
+}
+
 static void show_result_list(struct merge_list *entry)
 {
        printf("%s\n", explanation(entry));
@@ -70,6 +142,7 @@ static void show_result(void)
        walk = merge_result;
        while (walk) {
                show_result_list(walk);
+               show_diff(walk);
                walk = walk->next;
        }
 }