diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Documentation/git-merge-index.txt | 4 | ||||
-rw-r--r-- | INSTALL | 9 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | builtin-merge-file.c | 79 | ||||
-rw-r--r-- | builtin.h | 1 | ||||
-rwxr-xr-x | git-cvsserver.perl | 2 | ||||
-rwxr-xr-x | git-rerere.perl | 2 | ||||
-rw-r--r-- | git.c | 1 | ||||
-rw-r--r-- | git.spec.in | 2 | ||||
-rw-r--r-- | merge-file.c | 75 | ||||
-rw-r--r-- | merge-recursive.c | 142 | ||||
-rwxr-xr-x | t/t0000-basic.sh | 14 | ||||
-rw-r--r-- | t/t6023-merge-file.sh | 116 | ||||
-rw-r--r--[-rwxr-xr-x] | t/t6024-recursive-merge.sh | 12 | ||||
-rw-r--r-- | xdiff/xdiff.h | 7 | ||||
-rw-r--r-- | xdiff/xdiffi.c | 3 | ||||
-rw-r--r-- | xdiff/xdiffi.h | 1 | ||||
-rw-r--r-- | xdiff/xmerge.c | 419 |
19 files changed, 715 insertions, 179 deletions
diff --git a/.gitignore b/.gitignore index 7f2cd5508..d706dd92c 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ git-mailsplit git-merge git-merge-base git-merge-index +git-merge-file git-merge-tree git-merge-octopus git-merge-one-file diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6cd060108..0cf505ea8 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling the merge command from -the RCS package. +Typically this is run with the a script calling git's imitation of +the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the distribution. @@ -82,15 +82,6 @@ Issues of note: do that even if it wasn't for git. There's no point in living in the dark ages any more. - - "merge", the standard UNIX three-way merge program. It usually - comes with the "rcs" package on most Linux distributions, so if - you have a developer install you probably have it already, but a - "graphical user desktop" install might have left it out. - - You'll only need the merge program if you do development using - git, and if you only use git to track other peoples work you'll - never notice the lack of it. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically @@ -279,6 +279,7 @@ BUILTIN_OBJS = \ builtin-ls-tree.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ + builtin-merge-file.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ @@ -737,7 +738,8 @@ $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h diff --git a/builtin-merge-file.c b/builtin-merge-file.c new file mode 100644 index 000000000..6c4c3a351 --- /dev/null +++ b/builtin-merge-file.c @@ -0,0 +1,79 @@ +#include "cache.h" +#include "xdiff/xdiff.h" + +static const char merge_file_usage[] = +"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; + +static int read_file(mmfile_t *ptr, const char *filename) +{ + struct stat st; + FILE *f; + + if (stat(filename, &st)) + return error("Could not stat %s", filename); + if ((f = fopen(filename, "rb")) == NULL) + return error("Could not open %s", filename); + ptr->ptr = xmalloc(st.st_size); + if (fread(ptr->ptr, st.st_size, 1, f) != 1) + return error("Could not read %s", filename); + fclose(f); + ptr->size = st.st_size; + return 0; +} + +int cmd_merge_file(int argc, char **argv, char **envp) +{ + char *names[3]; + mmfile_t mmfs[3]; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + int ret = 0, i = 0, to_stdout = 0; + + while (argc > 4) { + if (!strcmp(argv[1], "-L") && i < 3) { + names[i++] = argv[2]; + argc--; + argv++; + } else if (!strcmp(argv[1], "-p") || + !strcmp(argv[1], "--stdout")) + to_stdout = 1; + else if (!strcmp(argv[1], "-q") || + !strcmp(argv[1], "--quiet")) + freopen("/dev/null", "w", stderr); + else + usage(merge_file_usage); + argc--; + argv++; + } + + if (argc != 4) + usage(merge_file_usage); + + for (; i < 3; i++) + names[i] = argv[i + 1]; + + for (i = 0; i < 3; i++) + if (read_file(mmfs + i, argv[i + 1])) + return -1; + + ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], + &xpp, XDL_MERGE_ZEALOUS, &result); + + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + + if (ret >= 0) { + char *filename = argv[1]; + FILE *f = to_stdout ? stdout : fopen(filename, "wb"); + + if (!f) + ret = error("Could not open %s for writing", filename); + else if (fwrite(result.ptr, result.size, 1, f) != 1) + ret = error("Could not write to %s", filename); + else if (fclose(f)) + ret = error("Could not close %s", filename); + free(result.ptr); + } + + return ret; +} @@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 197014d9e..2a8447e25 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -946,7 +946,7 @@ sub req_update $log->debug("Temporary directory for merge is $dir"); - my $return = system("merge", $file_local, $file_old, $file_new); + my $return = system("git merge-file", $file_local, $file_old, $file_new); $return >>= 8; if ( $return == 0 ) diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff49..2e8dbbd4e 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -154,7 +154,7 @@ sub find_conflict { sub merge { my ($name, $path) = @_; record_preimage($path, "$rr_dir/$name/thisimage"); - unless (system('merge', map { "$rr_dir/$name/${_}image" } + unless (system('git merge-file', map { "$rr_dir/$name/${_}image" } qw(this pre post))) { my $in; open $in, "<$rr_dir/$name/thisimage" or @@ -247,6 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge-file", cmd_merge_file }, { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/git.spec.in b/git.spec.in index f2374b733..fb95e3759 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/merge-file.c b/merge-file.c index fc9b14899..69dc1ebbf 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,52 +3,6 @@ #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 = xstrdup(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; @@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f) 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; + mmbuffer_t res; + xpparam_t xpp; + int merge_status; + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(base, our, ".our", their, ".their", + &xpp, XDL_MERGE_ZEALOUS, &res); + + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; } static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) diff --git a/merge-recursive.c b/merge-recursive.c index 866a4e480..6dd6e2e5a 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -21,6 +21,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -604,24 +605,21 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) - die("unable to read blob object %s", sha1_to_hex(sha1)); + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +650,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + 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); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); } else { if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); @@ -1061,38 +1051,17 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path, 0); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ + const char *reason = "content"; struct merge_file_info mfi; struct diff_filespec o, a, b; + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); @@ -1109,7 +1078,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output("CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 81f3bedc9..3260d1d7a 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here. ' ################################################################ -# It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions). -# Check this and error out before running any tests. Also catch -# the bogosity of trying to run tests without building while we -# are at it. +# It appears that people try to run tests without building... ../git >/dev/null if test $? != 1 @@ -32,14 +28,6 @@ then exit 1 fi -merge >/dev/null 2>/dev/null -if test $? = 127 -then - echo >&2 'You do not seem to have "merge" installed. -Please check INSTALL document.' - exit 1 -fi - . ./test-lib.sh ################################################################ diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh new file mode 100644 index 000000000..5d9b6f34b --- /dev/null +++ b/t/t6023-merge-file.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +cat > orig.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new1.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +cat > new2.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new3.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new4.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +echo -n "propter nomen suum." >> new4.txt + +cp new1.txt test.txt +test_expect_success "merge without conflict" \ + "git-merge-file test.txt orig.txt new2.txt" + +cp new1.txt test2.txt +test_expect_success "merge without conflict (missing LF at EOF)" \ + "git-merge-file test2.txt orig.txt new2.txt" + +test_expect_success "merge result added missing LF" \ + "diff -u test.txt test2.txt" + +cp test.txt backup.txt +test_expect_failure "merge with conflicts" \ + "git-merge-file test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< test.txt +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers" "diff -u test.txt expect.txt" + +cp backup.txt test.txt +test_expect_failure "merge with conflicts, using -L" \ + "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< 1 +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers, with -L" \ + "diff -u test.txt expect.txt" + +test_done + diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 9416c271e..964010e76 100755..100644 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -58,9 +58,19 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F test_expect_failure "combined merge conflicts" "git merge -m final G" +cat > expect << EOF +<<<<<<< HEAD/a1 +F +======= +G +>>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1 +EOF + +test_expect_success "result contains a conflict" "diff -u expect a1" + git ls-files --stage > out cat > expect << EOF -100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 1 a1 +100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1 a1 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 EOF diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c9f817818..fa409d523 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -49,6 +49,9 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 typedef struct s_mmfile { char *ptr; @@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result); + #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index d76e76a0e..9aeebc473 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, xdalgoenv_t *xenv); static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); @@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; xrecord_t **recs = xdf->recs; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index d3b72716b..472aeaecf 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c new file mode 100644 index 000000000..352207e51 --- /dev/null +++ b/xdiff/xmerge.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + */ + int mode; + long i1, i2; + long chg1, chg2; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i1, long chg1, long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs = xe->xdf2.recs + i; + int size = 0; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + const int marker_size = 7; + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int conflict_marker_size = 3 * (marker_size + 1) + + marker1_size + marker2_size; + int size, i1, j; + + for (size = i1 = 0; m; m = m->next) { + if (m->mode == 0) { + size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, + marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } else + size += conflict_marker_size; + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, + marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + } else if (m->mode == 1) + size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, + dest ? dest + size : NULL); + else if (m->mode == 2) + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, + m->i1 + m->chg2 - i1, 0, + dest ? dest + size : NULL); + i1 = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, it's a bug. */ + xdl_free_env(&xe); + return -2; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, + xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, + int level, xpparam_t const *xpp, mmbuffer_t *result) { + xdmerge_t *changes, *c; + int i1, i2, chg1, chg2; + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level < 1 || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) + i1 -= off; + else + i2 += off; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo > 0) + chg2 += ffo; + else + chg1 -= ffo; + if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + changes, NULL); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + result->ptr); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result) { + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (xscr1 || xscr2) { + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + } + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} |