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 7f2cd55088..d706dd92c6 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 6cd0601082..0cf505ea84 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 0000000000..6c4c3a3513 --- /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 197014d9e6..2a8447e253 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 d3664ff491..2e8dbbd4ea 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 f2374b7331..fb95e37594 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 fc9b148993..69dc1ebbf7 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 866a4e480b..6dd6e2e5af 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 81f3bedc90..3260d1d7a7 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 0000000000..5d9b6f34b8 --- /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 9416c271eb..964010e764 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 c9f817818a..fa409d5234 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 d76e76a0e6..9aeebc473b 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 d3b72716b5..472aeaecfa 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 0000000000..352207e516 --- /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; +} | 
