git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
To: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Cc: Davide Libenzi <davidel@xmailserver.org>,
	git@vger.kernel.org, Junio C Hamano <junkio@cox.net>
Subject: Re: [PATCH] xdiff: add xdl_merge()
Date: Wed, 22 Nov 2006 19:58:27 +0000	[thread overview]
Message-ID: <4564ABE3.6080605@ramsay1.demon.co.uk> (raw)
In-Reply-To: <Pine.LNX.4.63.0611212316080.26827@wbgn013.biozentrum.uni-wuerzburg.de>

[-- Attachment #1: Type: text/plain, Size: 3583 bytes --]

Hi Johannes,

Johannes Schindelin wrote:
> This new function implements the functionality of RCS merge, but
> in-memory. It returns < 0 on error, otherwise the number of conflicts.
> 

I had a similar idea (removing the RCS dependency) and, about two months
ago, implemented a git-diff3 that used the internal xdiff library.
Unfortunately, I then got distracted by other things and left this as a
WIP, without posting it to the list. [I had an email exchange with Junio
about it, but then dropped the ball - sorry Junio!]

> Finding the conflicting lines can be a very expensive task. You can
> control the eagerness of this algorithm:
> 
> - a level value of 0 means that all overlapping changes are treated
>   as conflicts,
> - a value of 1 means that if the overlapping changes are identical,
>   it is not treated as a conflict.
> - If you set level to 2, overlapping changes will be analyzed, so that
>   almost identical changes will not result in huge conflicts. Rather,
>   only the conflicting lines will be shown inside conflict markers.
> 
> With each increasing level, the algorithm gets slower, but more accurate.
> Note that the code for level 2 depends on the simple definition of
> mmfile_t specific to git, and therefore it will be harder to port that
> to LibXDiff.
> 

Erm, I guess I need to read the code! I haven't had an opportunity to
do that yet, but I hope to have time at the weekend.

> 	My hopes are that when I wake up in the morning, all bugs are
> 	fixed, git-merge-one-file is rewritten as a builtin,
> 	git-merge-index defaults to calling xdl_merge() directly when
> 	no program is passed with "-o", and git-merge-recursive also
> 	avoids fork()ing RCS' merge.

Yep, I had a similar todo list.

> 
> 	A funny side effect is that you can merge with white space
> 	corruption by setting the xpparam flags to ignore whitespace.
> 	The file passed as mf1 wins over mf2 in that case.

Yes, I had this on my todo list. In fact you can already try it out
by using --diff-program="diff -wb" (which uses the external diff).
[To be fair, you can also write an obvious script and use GNU diff3]

> 
>  Makefile       |    3 
>  xdiff/xdiff.h  |    7 +
>  xdiff/xdiffi.c |    3 
>  xdiff/xdiffi.h |    1 
>  xdiff/xmerge.c |  433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 444 insertions(+), 3 deletions(-)
> 

 From the above, I would guess that your code integrates well with the
xdiff library and, as a result, would be more memory efficient than my
code. I had considered doing this, but I wanted to support an external
diff option, so it was easier to implement the diff3 algorithm outside
the library, treating it just like an external diff (that I didn't need
to fork()/exec()). Also, I didn't need to understand the xdiff code, I
just treat it as a black box (and I'm lazy!).

[Having an external diff option was actually a debugging aid to ensure
that the main diff3 algorithm was implemented correctly. In the end I
didn't use it for debugging anyway!]

[snip patch]

I have attached a version of my patch, against v1.4.2, just for the
interested. Also it contains some tests which I found very helpful
in squashing bugs, so you might like to give them a try with your code.

Note: the fact that the patch is against v1.4.2 should not cause too
much of a problem, since it is mostly new files and the changes to
Makefile, builtin.h and git.c you could probably do in your sleep.

[Junio, this is almost exactly the same patch I sent you last time,
modulo some minor code clean-up/formatting]

All the best,

Ramsay



[-- Attachment #2: p0016.txt --]
[-- Type: text/plain, Size: 52987 bytes --]

>From f23d8df5d7774cb85b834357019c725cf8c1e231 Mon Sep 17 00:00:00 2001
From: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Date: Mon, 13 Nov 2006 23:04:37 +0000
Subject: [PATCH] Initial implementation of builtin diff3

The intention of this work is to remove the dependancy on
the rcs merge program. As a first step, we implement a
builtin clone of diff3. (A builtin merge replacement command
will be able to reuse this code, since merge is a thin
wrapper around diff3). So git-diff3 is only a stepping stone
to a merge replacement and is not meant to stay!

Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
---
 Makefile         |    5 
 builtin-diff3.c  | 1824 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h        |    2 
 git.c            |    1 
 t/txxxx-diff3.sh |  175 +++++
 t/txxxx/lao      |   11 
 t/txxxx/lao1     |    8 
 t/txxxx/lao2     |   16 
 t/txxxx/lao3     |   13 
 t/txxxx/lao4     |   12 
 t/txxxx/tao      |   14 
 t/txxxx/tao1     |   10 
 t/txxxx/tao2     |   16 
 t/txxxx/tao3     |   13 
 t/txxxx/tao4     |   15 
 t/txxxx/tzu      |   13 
 16 files changed, 2146 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 0761d6c..60f5adc 100644
--- a/Makefile
+++ b/Makefile
@@ -198,7 +198,7 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \
 	git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \
 	git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \
-	git-repo-config$X
+	git-repo-config$X git-diff3$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -255,7 +255,8 @@ BUILTIN_OBJS = \
 	builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
 	builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
 	builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \
-	builtin-mv.o builtin-prune-packed.o builtin-repo-config.o
+	builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \
+	builtin-diff3.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-diff3.c b/builtin-diff3.c
new file mode 100644
index 0000000..9f5698f
--- /dev/null
+++ b/builtin-diff3.c
@@ -0,0 +1,1824 @@
+/*
+ * Builtin "git diff3"
+ *
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "xdiff-interface.h"
+#include <sys/wait.h>
+#include <assert.h>
+
+
+#define MAX_DIFF_ARGS 32
+
+/* diff3 options */
+struct diff3_options {
+	unsigned show_all:1;     /* show all changes, including merged       */
+	unsigned overlap_only:1; /* restrict to overlapping changes          */
+	unsigned unmerged_only:1;/* restrict to unmerged non-overlapping     */
+	unsigned initial_tab:1;  /* output initial tab                       */
+	unsigned markers:1;      /* output conflict markers                  */
+	unsigned edscript:1;     /* output an ed script to perform the merge */
+	unsigned merge:1;        /* actually output the merged file          */
+	const char *diff_prog;   /* external diff program (command line)     */
+};
+
+/* input file info */
+struct finfo {
+	const char *name;   /* filename                       */
+	const char *label;  /* label to use for markers       */
+	int         num;    /* 0 = ours, 1 = base, 2 = theirs */
+	mmfile_t    mf;     /* file mmap() info               */
+};
+
+/* pipe info when running external diff */
+struct pinfo {
+	int   fd;   /* pipe read/write fd    */
+	pid_t pid;  /* child pid to wait for */
+};
+
+/* argv info for external diff */
+struct diff_argv {
+	char *args;         /* argument string memory */
+	const char **argv;  /* argument pointer array */
+	int argc;           /* argument count         */
+};
+
+/* callback info used with internal diff */
+struct ecb_info {
+	char *text;  /* pointer to text of diff output    */
+	long size;   /* size of memory block holding text */
+	long used;   /* length of diff output             */
+};
+
+/* type of diff */
+enum {
+	UNKNOWN_FORMAT,
+	NORMAL_FORMAT,
+	UNIFIED_FORMAT,
+	CONTEXT_FORMAT
+};
+
+/* normal format diff hunk header */
+struct hunk_header {
+	int fs, fe;  /* from-file start and end line numbers */
+	int type;    /* 'a' add, 'c' change, 'd' delete      */
+	int ts, te;  /* to-file start and end line numbers   */
+};
+
+/* unified format diff hunk header */
+struct unified_hunk_header {
+	int fs, fc;  /* from-file start line and count */
+	int ts, tc;  /* to-file start line and count   */
+};
+
+/* lines from one file referenced by a hunk */
+struct hunk_lines {
+	int  start;     /* start line number of hunk         */
+	int  end;       /* end line number of hunk           */
+	char **lines;   /* pointer to array of line pointers */
+	int  *lengths;  /* pointer to array of line lengths  */
+	int  size;      /* size of the above arrays          */
+};
+
+/* 2way diff hunk */
+struct diff2_hunk {
+	int               type;   /* hunk type: 'a', 'c', 'd'   */
+	struct hunk_lines from;   /* from-file hunk lines       */
+	struct hunk_lines to;     /* to-file hunk lines         */
+	struct diff2_hunk *next;  /* next hunk in list          */
+};
+
+/* 2way diff */
+struct diff2 {
+	struct finfo      *ff;      /* from file            */
+	struct finfo      *ft;      /* to file              */
+	mmbuffer_t        content;  /* text content of diff */
+	struct diff2_hunk *hunks;   /* list of diff hunks   */
+};
+
+/* a segment (sub-list) of diff2 hunks */
+struct segment {
+	int               from;   /* from-file number          */
+	int               to;     /* to-file number            */
+	struct diff2_hunk *head;  /* head of hunk list segment */
+	struct diff2_hunk *tail;  /* tail of hunk list segment */
+	int               count;  /* count of hunks            */
+};
+
+/* 3way diff hunk */
+struct diff3_hunk {
+	int               type;  /* 'a' all, '1' 1st, '2' 2nd, '3' 3rd */
+	struct hunk_lines f[3];  /* hunk lines for files 0, 1, 2       */
+	struct diff3_hunk *next; /* next hunk in list                  */
+};
+
+/* 3way diff */
+struct diff3 {
+	struct diff2      *diff01;  /* diff ours -> base   */
+	struct diff2      *diff21;  /* diff theirs -> base */
+	struct diff3_hunk *hunks;   /* list of diff3 hunks */
+};
+
+static void init_finfo(struct finfo *f, const char *name, const char *lab, int num)
+{
+	if (f) {
+		f->name  = name;
+		f->label = lab;
+		f->num   = num;
+
+		f->mf.ptr  = 0;
+		f->mf.size = 0;
+	}
+}
+
+static void alloc_line_arrays(int num, struct hunk_lines *h)
+{
+	assert(h);
+	h->lines   = xcalloc(num, sizeof(char *));
+	h->lengths = xcalloc(num, sizeof(int));
+	h->size    = num;
+}
+
+static struct diff2_hunk *new_diff2_hunk(void)
+{
+	struct diff2_hunk *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	return new;
+}
+
+static void free_diff2_hunk(struct diff2_hunk *p)
+{
+	if (p->from.lines)
+		free(p->from.lines);
+	if (p->from.lengths)
+		free(p->from.lengths);
+
+	if (p->to.lines)
+		free(p->to.lines);
+	if (p->to.lengths)
+		free(p->to.lengths);
+
+	free(p);
+}
+
+static struct diff2 *new_diff2(struct finfo *ff, struct finfo *ft)
+{
+	struct diff2 *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	new->ff = ff;
+	new->ft = ft;
+
+	return new;
+}
+
+static void free_diff2(struct diff2 *p)
+{
+	struct diff2_hunk *h, *next;
+
+	for (h = p->hunks; h; h = next) {
+		next = h->next;
+		free_diff2_hunk(h);
+	}
+
+	if (p->content.ptr)
+		free(p->content.ptr);
+
+	free(p);
+}
+
+static struct diff3_hunk *new_diff3_hunk(void)
+{
+	struct diff3_hunk *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	return new;
+}
+
+static void free_diff3_hunk(struct diff3_hunk *p)
+{
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		if (p->f[i].lines)
+			free(p->f[i].lines);
+		if (p->f[i].lengths)
+			free(p->f[i].lengths);
+	}
+
+	free(p);
+}
+
+static struct diff3 *new_diff3(struct diff2 *diff01, struct diff2 *diff21)
+{
+	struct diff3 *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	new->diff01 = diff01;
+	new->diff21 = diff21;
+
+	return new;
+}
+
+static void free_diff3(struct diff3 *p)
+{
+	struct diff3_hunk *h, *next;
+
+	for (h = p->hunks; h; h = next) {
+		next = h->next;
+		free_diff3_hunk(h);
+	}
+
+	free_diff2(p->diff01);
+	free_diff2(p->diff21);
+
+	free(p);
+}
+
+static int init_mmfile(mmfile_t *mf, const char *file)
+{
+	char *data;
+	long size;
+	struct stat st;
+	int fd;
+
+	assert(mf && file && *file);
+
+	mf->ptr  = 0;
+	mf->size = 0;
+
+	if (lstat(file, &st) < 0)
+		return -1;
+	size = st.st_size;
+	if (!size) {
+		mf->ptr = "";
+		return 0;
+	}
+
+	fd = open(file, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+	close(fd);
+	if (data == MAP_FAILED)
+		return -1;
+
+	mf->ptr  = data;
+	mf->size = size;
+	return 0;
+}
+
+static void free_mmfile(mmfile_t *mf)
+{
+	if (mf) {
+		if (mf->ptr && mf->size != 0)
+			munmap(mf->ptr, mf->size);
+		mf->ptr  = 0;
+		mf->size = 0;
+	}
+}
+
+static void map_file(struct finfo *f)
+{
+	if (!f->mf.ptr && (init_mmfile(&f->mf, f->name) < 0))
+		die("can't read file '%s': %s", f->name, strerror(errno));
+}
+
+static int popenv(int argc, const char *argv[], const char *type, struct pinfo *p)
+{
+	int pfd[2];
+	pid_t pid;
+	int fd;
+
+	if (!argv || !type || !p) {
+		errno = EINVAL;
+		return -1;
+	}
+	p->fd = -1;
+	p->pid = 0;
+
+	if (!*type || (*type != 'r' && *type != 'w') || type[1] != 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (pipe(pfd) < 0)
+		return -1;
+
+	if ((pid = fork()) < 0) {
+		return -1;
+	} else if (pid == 0) {  /* child */
+		if (*type == 'r') {
+			close(pfd[0]);
+			if(pfd[1] != STDOUT_FILENO) {
+				dup2(pfd[1], STDOUT_FILENO);
+				close(pfd[1]);
+			}
+		} else { /* *type == 'w' */
+			close(pfd[1]);
+			if(pfd[0] != STDIN_FILENO) {
+				dup2(pfd[0], STDIN_FILENO);
+				close(pfd[0]);
+			}
+
+		}
+
+#ifdef DUMMY
+		fd = open("/dev/null", O_RDWR);
+		dup2(fd, STDERR_FILENO);
+		close(fd);
+#endif
+
+		execvp(argv[0], (char *const *)argv);
+		_exit(127);
+	}
+
+	/* parent */
+	if (*type == 'r') {
+		close(pfd[1]);
+		fd = pfd[0];
+	} else {
+		close(pfd[0]);
+		fd = pfd[1];
+	}
+
+	p->fd = fd;
+	p->pid = pid;
+
+	return 0;
+}
+
+static int pclosev(struct pinfo *p)
+{
+	int stat;
+
+	if (!p || p->fd == -1 || p->pid == 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	close(p->fd);  /* XXX check for close() failure? */
+	p->fd = -1;
+
+	while (waitpid(p->pid, &stat, 0) < 0)
+		if (errno != EINTR)
+			return -1;
+
+	p->pid = 0;
+
+	return stat;
+}
+
+static int split_cmdline(const char *cmdline, int max, struct diff_argv *prog)
+{
+	assert(prog);
+
+	if (cmdline && *cmdline && (prog->args = xmalloc(strlen(cmdline)+2))) {
+
+		const char *p = cmdline;
+		char *a = prog->args;
+		int argc = 0;
+
+		prog->argv = xmalloc(max * sizeof(char *));
+
+		while( *p ) {
+
+			while (*p == ' ' || *p == '\t')
+				p++;
+			if ( !*p )
+				break;
+
+			if (argc >= max)
+				break;
+
+			prog->argv[argc++] = a;
+			while ((*a = *p) && *a != ' ' && *a != '\t')
+				a++,p++;
+			*a++ = 0;
+
+		}
+
+		*a++ = 0;
+		prog->argc = argc;
+		prog->argv[argc] = 0;
+
+		if (argc == 0) {
+			free(prog->args);
+			free(prog->argv);
+			return -1;
+		}
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static struct diff_argv *new_diff_argv(const char *cmdline)
+{
+	struct diff_argv *a = xmalloc(sizeof(*a));
+
+	memset(a, 0, sizeof(*a));
+
+	if (split_cmdline(cmdline, MAX_DIFF_ARGS, a) < 0) {
+		free(a);
+		return 0;
+	}
+
+	return a;
+}
+
+static void free_diff_argv(struct diff_argv *a)
+{
+	if (a) {
+		if (a->args)
+			free(a->args);
+		if (a->argv)
+			free(a->argv);
+		free(a);
+	}
+}
+
+static int read_from_pipe(int fd, mmbuffer_t *content)
+{
+	ssize_t n, size = 1024, total = 0;
+	char *text = xmalloc(size);
+
+	assert(content != 0);
+
+	while ((n = read(fd, text+total, size-total)) > 0) {
+		if (n == size-total) {
+			size *= 2;
+			text = xrealloc(text, size);
+		}
+		total += n;
+	}
+	if (n < 0)
+		return -1;
+
+	if (total == 0)
+		free(text);
+	else {
+		content->ptr  = text;
+		content->size = total;
+	}
+
+	return 0;
+}
+
+static void run_external_diff(struct diff_argv *prog, const char* file1, const char* file2, mmbuffer_t *content)
+{
+	char const **ap;
+	struct pinfo pi;
+	int stat, code;
+
+	if (!prog || !prog->args || !prog->argv || prog->argc < 1)
+		die("internal error: invalid diff argv");
+
+	if (prog->argc > MAX_DIFF_ARGS - 4)
+		die("too many arguments for external diff-program");
+
+	ap = &prog->argv[prog->argc];
+	*ap++ = "--";
+	*ap++ = file1;
+	*ap++ = file2;
+	*ap++ = 0;
+
+	if(popenv(prog->argc+4, prog->argv, "r", &pi) < 0)
+		die("popenv() failed: %s", strerror(errno));
+
+	if (read_from_pipe(pi.fd, content) < 0)
+		die("read_from_pipe() failed: %s", strerror(errno));
+
+	if ((stat = pclosev(&pi)) < 0)
+		die("pclosev() failed: %s", strerror(errno));
+
+	if (!WIFEXITED(stat))
+		die("diff-program '%s' terminated abnormally", prog->argv[0]);
+
+	if ((code = WEXITSTATUS(stat)) == 127)
+		die("can't execute external diff-program '%s'", prog->argv[0]);
+
+	if (code < 0 || code > 1)
+		die("external diff-program '%s' failed", prog->argv[0]);
+}
+
+#ifdef DUMMY
+static void print_buffer(mmbuffer_t *b)
+{
+	ssize_t count = 0, n;
+
+	if (b->size == 0)
+		return;
+
+	while ((n = xwrite(STDOUT_FILENO, b->ptr+count, b->size-count)) > 0)
+		count += n;
+}
+#endif
+
+static void external_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	assert(ff != 0 && ft != 0 && diff != 0);
+
+	run_external_diff(prog, ff->name, ft->name, diff);
+
+#ifdef DUMMY
+	printf("diff %s %s\n", ff->name, ft->name);
+	print_buffer(diff);
+	printf("--------\n");
+#endif
+}
+
+static int xdiff_output(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+	struct ecb_info *b = priv_;
+	int i;
+
+	for (i = 0; i < nbuf; i++) {
+
+		if (b->used + mb[i].size > b->size) {
+			if (b->size > mb[i].size)
+				b->size *= 2;
+			else
+				b->size += mb[i].size;
+			b->text = xrealloc(b->text, b->size);
+		}
+
+		memcpy(b->text+b->used, mb[i].ptr, mb[i].size);
+		b->used += mb[i].size;
+
+	}
+	return 0;
+}
+
+static void run_internal_diff(mmfile_t *mf1, mmfile_t *mf2, mmbuffer_t *diff)
+{
+	xpparam_t xpp;
+	xdemitconf_t xecfg;
+	xdemitcb_t ecb;
+	struct ecb_info ecb_data;
+
+	ecb_data.text = xmalloc(1024);
+	ecb_data.size = 1024;
+	ecb_data.used = 0;
+
+	xpp.flags = XDF_NEED_MINIMAL;
+
+	xecfg.ctxlen = 0;
+	xecfg.flags  = 0;
+
+	ecb.outf = xdiff_output;
+	ecb.priv = &ecb_data;
+
+	xdl_diff(mf1, mf2, &xpp, &xecfg, &ecb);
+
+	if (ecb_data.used == 0)
+		free(ecb_data.text);
+	else {
+		diff->ptr  = ecb_data.text;
+		diff->size = ecb_data.used;
+	}
+}
+
+static void internal_diff(struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	assert(ff != 0 && ft != 0 && diff != 0);
+
+	map_file(ff);
+	map_file(ft);
+
+	run_internal_diff(&ff->mf, &ft->mf, diff);
+
+#ifdef DUMMY
+	printf("diff %s %s\n", ff->name, ft->name);
+	print_buffer(diff);
+	printf("--------\n");
+#endif
+}
+
+
+static void read_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	if (prog)
+		external_diff(prog, ff, ft, diff);
+	else
+		internal_diff(ff, ft, diff);
+}
+
+static char *nextline(char *p, char *limit)
+{
+	while (p < limit)
+		if (*p++ == '\n')
+			return p;
+	return 0;
+}
+
+static int get_num(char **tp, int *np)
+{
+	char *p  = *tp;
+	int  neg = 0;
+	int  num = 0;
+
+	/* can't use: num = strtol(p, tp, 10); because strtol() will
+	 * skip using isspace() which includes '\n'.
+	 */
+
+	while (*p == ' ' || *p == '\t') p++;
+
+	if (*p == '-' || *p == '+') {
+		neg = *p == '-';
+		p++;
+	}
+
+	while (isdigit(*p))
+		num = num * 10 + (*p++ - '0');
+
+	if (*tp == p)
+		return 0;
+
+	*tp = p;
+	*np = (neg) ? -num : num;
+	return 1;
+}
+
+static int have(char **tp, char c)
+{
+	char *p = *tp;
+
+	while (*p == ' ' || *p == '\t') p++;
+
+	if (*p == c) {
+		*tp = ++p;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int at_eol(char **tp)
+{
+	char *p = *tp;
+
+	while (*p == ' ' || *p == '\t') p++;
+	*tp = p;
+
+	if (*p == '\n')
+		return 1;
+
+	return 0;
+}
+
+static int normal_header(char **curp, struct hunk_header *header)
+{
+	char *p = *curp;
+
+	if (!get_num(&p, &header->fs))
+		return 0;
+
+	header->fe = header->fs;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->fe))
+			return 0;
+	}
+
+	if (have(&p, 'a'))
+		header->type = 'a';
+	else if (have(&p, 'c'))
+		header->type = 'c';
+	else if (have(&p, 'd'))
+		header->type = 'd';
+	else
+		return 0;
+
+	if (header->type == 'a') {
+		assert(header->fs == header->fe);
+		header->fe--;  /* null (zero) range of lines */
+	}
+
+	if (!get_num(&p, &header->ts))
+		return 0;
+
+	header->te = header->ts;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->te))
+			return 0;
+	}
+
+	if (header->type == 'd') {
+		assert(header->ts == header->te);
+		header->te--;  /* null (zero) range of lines */
+	}
+
+	if(!at_eol(&p))
+		return 0;
+
+	*curp = ++p;
+	return 1;
+}
+
+static int determine_diff_format(mmbuffer_t *content, char** curp)
+{
+	char *p     = content->ptr;
+	char *limit = p + content->size;
+	char *p2, *p3, *p4;
+	struct hunk_header head;
+
+	*curp = p;
+
+	if((p2 = nextline(p, limit)) == 0)
+		return UNKNOWN_FORMAT;
+
+	/* internal (unified) diff, no header */
+	if (memcmp(p, "@@ ", 3) == 0)
+		return UNIFIED_FORMAT;
+
+	/* normal format diff */
+	if (normal_header(&p, &head))
+		return NORMAL_FORMAT;
+
+	if((p3 = nextline(p2, limit)) == 0)
+		return UNKNOWN_FORMAT;
+
+	/* external (unified) diff, with header */
+	if (memcmp(p,  "--- ", 4) == 0 &&
+	    memcmp(p2, "+++ ", 4) == 0 &&
+	    memcmp(p3, "@@ ",  3) == 0) {
+
+		*curp = p3;
+		return UNIFIED_FORMAT;
+	}
+
+	/* context diff */
+	if (memcmp(p,  "*** ", 4) == 0 &&
+	    memcmp(p2, "--- ", 4) == 0 &&
+	    memcmp(p3, "****", 4) == 0) {
+
+		if((p4 = nextline(p3, limit)) == 0)
+			return UNKNOWN_FORMAT;
+
+		*curp = p4;
+		return CONTEXT_FORMAT;
+	}
+
+	return UNKNOWN_FORMAT;
+}
+
+static void get_line(char **curp, char *limit, char type, int num, struct hunk_lines *hl)
+{
+	char *p = *curp;
+	int  normal = (type == '<') || (type == '>');
+	char *pn;
+
+	assert(p < limit);
+
+	if (*p++ != type || (normal && *p++ != ' '))
+		die("invalid diff: leading line chars");
+
+	hl->lines[num] = p;
+
+	if ((pn = nextline(p, limit)) == 0)
+		die("invalid diff: missing eol #3");
+
+	hl->lengths[num] = (int)(pn - p);
+
+	/* check for "\ No newline at end of file" */
+	if (pn < limit && *pn == '\\') {
+		/* remove nl from length and skip */
+		hl->lengths[num] = (int)(pn - p) - 1;
+		pn = nextline(pn, limit);
+	}
+
+	*curp = pn;
+}
+
+static void process_normal_diff(struct diff2 *d, char *p)
+{
+	mmbuffer_t *content = &d->content;
+	char *limit = content->ptr + content->size;
+	struct diff2_hunk *hunk, **last_hunk;
+	int i;
+
+	last_hunk = &d->hunks;
+
+	while (p < limit) {
+		struct hunk_header head;
+
+		hunk = new_diff2_hunk();
+
+		/* hunk header: <int>[,<int>]<type><int>[,<int>] */
+		if (!normal_header(&p, &head))
+			die("invalid diff: header");
+
+		hunk->type = head.type;
+
+		/* from-file hunk lines (if any) */
+		hunk->from.start = head.fs;
+		hunk->from.end   = head.fe;
+		if (head.type != 'a') {
+			int numlines = head.fe - head.fs + 1;
+
+			assert(numlines > 0);
+
+			alloc_line_arrays(numlines, &hunk->from);
+
+			for (i = 0; i < numlines; i++)
+				get_line(&p, limit, '<', i, &hunk->from);
+		}
+
+		/* skip change hunk separator (if any) */
+		if (head.type == 'c') {
+			if (memcmp(p, "---\n", 4) != 0)
+				die("invalid diff: missing change separator");
+			p += 4;
+		}
+
+		/* to-file hunk lines (if any) */
+		hunk->to.start = head.ts;
+		hunk->to.end   = head.te;
+		if (head.type != 'd') {
+			int numlines = head.te - head.ts + 1;
+
+			assert(numlines > 0);
+
+			alloc_line_arrays(numlines, &hunk->to);
+
+			for (i = 0; i < numlines; i++)
+				get_line(&p, limit, '>', i, &hunk->to);
+		}
+
+		/* add this hunk to the end of the list */
+		*last_hunk = hunk;
+		last_hunk  = &hunk->next;
+
+	}
+
+	*last_hunk = 0;
+}
+
+static int have_str(char **tp, char *s)
+{
+	char *p = *tp;
+	int  len = strlen(s);
+
+	if (memcmp(p, s, len) == 0) {
+		p += len;
+		*tp = p;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int unified_header(char **curp, struct unified_hunk_header *header)
+{
+	char *p = *curp;
+
+	if (!have_str(&p, "@@ -"))
+		return 0;
+
+	if (!get_num(&p, &header->fs))
+		return 0;
+
+	header->fc = 1;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->fc))
+			return 0;
+	}
+
+	if (!have_str(&p, " +"))
+		return 0;
+
+	if (!get_num(&p, &header->ts))
+		return 0;
+
+	header->tc = 1;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->tc))
+			return 0;
+	}
+
+	if (!have_str(&p, " @@"))
+		return 0;
+
+	if(!at_eol(&p))
+		return 0;
+
+	*curp = ++p;
+	return 1;
+}
+
+static int count_hunk_lines(char *p, char *limit, char type)
+{
+	int num = 0;
+
+	while (p < limit && *p == type) {
+		char *pn;
+
+		if ((pn = nextline(p, limit)) == 0)
+			die("invalid diff: missing eol #1");
+
+		num++;
+		p = pn;
+	}
+
+	return num;
+}
+
+static void process_unified_diff(struct diff2 *d, char *p)
+{
+	mmbuffer_t *content = &d->content;
+	char *limit = content->ptr + content->size;
+	struct diff2_hunk *hunk, **last_hunk;
+	int i;
+
+	last_hunk = &d->hunks;
+
+	while (p < limit) {
+		struct unified_hunk_header head;
+		int single_hunk = 0;
+
+		/* hunk header: @@ -<int>[,<int>] +<int>[,<int>] @@ */
+		if (!unified_header(&p, &head))
+			die("invalid diff: header");
+
+		single_hunk = (head.fc == 0 || head.tc == 0);
+
+		for (;;) {  /* for all hunks */
+			int cc = 0, fc = 0, tc = 0;
+
+			/* find begining of next hunk: skip common lines */
+			while (p < limit && *p == ' ') {
+				char *pn;
+
+				cc++;
+				head.fs++; head.fc--;
+				head.ts++; head.tc--;
+
+				if ((pn = nextline(p, limit)) == 0)
+					die("invalid diff: missing eol #2");
+				p = pn;
+			}
+
+			/* finished? */
+			if (limit <= p || *p == '@') {
+				assert(head.fc == 0 && head.tc == 0);
+				break;
+			}
+			if (*p != '-' && *p != '+')
+				die("invalid diff: missing -/+ lines");
+
+			hunk = new_diff2_hunk();
+
+			hunk->from.start = hunk->from.end = head.fs;
+			hunk->to.start   = hunk->to.end   = head.ts;
+
+			/* from-file hunk lines (if any) */
+			fc = count_hunk_lines(p, limit, '-');
+			if (fc > 0) {
+				alloc_line_arrays(fc, &hunk->from);
+
+				for (i = 0; i < fc; i++)
+					get_line(&p, limit, '-', i, &hunk->from);
+
+				hunk->from.end += fc-1;
+
+				head.fs += fc;
+				head.fc -= fc;
+			}
+
+			/* to-file hunk lines (if any) */
+			tc = count_hunk_lines(p, limit, '+');
+			if (tc > 0) {
+				alloc_line_arrays(tc, &hunk->to);
+
+				for (i = 0; i < tc; i++)
+					get_line(&p, limit, '+', i, &hunk->to);
+
+				hunk->to.end += tc-1;
+
+				head.ts += tc;
+				head.tc -= tc;
+			}
+
+			/* set hunk type */
+			assert(fc > 0 || tc > 0);
+			if (fc == 0) {
+				hunk->type = 'a';
+				if (!single_hunk)
+					hunk->from.start--;
+				hunk->from.end = hunk->from.start - 1;
+			} else if (tc == 0) {
+				hunk->type = 'd';
+				if (!single_hunk)
+					hunk->to.start--;
+				hunk->to.end = hunk->to.start - 1;
+			} else {
+				hunk->type = 'c';
+			}
+
+			/* add this hunk to the end of the list */
+			*last_hunk = hunk;
+			last_hunk  = &hunk->next;
+		}
+
+	}
+
+	*last_hunk = 0;
+
+}
+
+struct diff2* process_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft)
+{
+	struct diff2 *result = new_diff2(ff, ft);
+	char *scan;
+
+	read_diff(prog, ff, ft, &result->content);
+
+	if (result->content.size == 0)
+		return result;  /* empty diff is ok */
+
+	assert(result->content.size > 0);
+
+	if (result->content.ptr[result->content.size-1] != '\n')
+		die("invalid diff: missing eol on last line");
+
+	switch (determine_diff_format(&result->content, &scan)) {
+		case NORMAL_FORMAT:
+			process_normal_diff(result, scan);
+			break;
+		case UNIFIED_FORMAT:
+			process_unified_diff(result, scan);
+			break;
+		case CONTEXT_FORMAT:
+			die("context diff format not supported");
+			break;
+		case UNKNOWN_FORMAT:
+		default:
+			die("unknown diff format");
+			break;
+	}
+
+	return result;
+}
+
+static void adjust_diff2(struct diff2 *d)
+{
+	struct diff2_hunk *p;
+
+	for (p = d->hunks; p; p = p->next) {
+		if (p->type == 'a') {
+			p->from.start++;
+			p->from.end++;
+		} else if (p->type == 'd') {
+			p->to.start++;
+			p->to.end++;
+		}
+	}
+}
+
+static int copy_lines(struct hunk_lines *dst, int off, struct hunk_lines *src)
+{
+	char **s = src->lines;
+	int  *sl = src->lengths;
+	char **d = dst->lines + off;
+	int  *dl = dst->lengths + off;
+	int  n   = src->size;
+
+	assert(n <= dst->size - off);
+
+	while(n--) {
+		if (*d) {
+			if (*sl != *dl || memcmp(*s, *d, *sl)) {
+				return 0;
+			}
+		} else {
+			*d  = *s;
+			*dl = *sl;
+		}
+		d++; dl++;
+		s++; sl++;
+	}
+	return 1;
+}
+
+static int equal_lines(struct hunk_lines *lhs, struct hunk_lines *rhs)
+{
+	char **l = lhs->lines;
+	int  *ll = lhs->lengths;
+	char **r = rhs->lines;
+	int  *rl = rhs->lengths;
+	int  n   = lhs->size;
+
+	if (n != rhs->size)
+		return 0;
+
+	while (n--) {
+		if (!*l || !*r || *ll != *rl || memcmp(*l, *r, *ll))
+			return 0;
+		l++; ll++;
+		r++; rl++;
+	}
+	return 1;
+}
+
+static int get_low_thread(struct diff2_hunk *p[2])
+{
+	if (!p[0])
+		return 1;
+	if (!p[1])
+		return 0;
+	return (p[0]->to.start > p[1]->to.start) ? 1 : 0;
+}
+
+static void init_segment(struct segment *s, int from, int to)
+{
+	s->from  = from;
+	s->to    = to;
+	s->head  = 0;
+	s->tail  = 0;
+	s->count = 0;
+}
+
+static void add_segment(struct segment *s, struct diff2_hunk *d)
+{
+	assert(d != 0);
+
+	if (!s->head)
+		s->head = s->tail = d;
+	else
+		s->tail = d;
+	s->count++;
+}
+
+static struct diff3_hunk *make_diff3_hunk(struct segment use[2],
+		int low_thread, int high_thread, struct diff3_hunk *last)
+{
+	struct diff3_hunk *result = new_diff3_hunk();
+	int startc, endc;
+	int i, common;
+	int from0, from1;
+
+	assert(use[0].to == use[1].to);
+	assert(use[0].from != use[1].from);
+
+	from0 = use[0].from;
+	from1 = use[1].from;
+
+	common = use[0].to;
+	assert(common >= 0 && common <= 2);
+
+	/* find start/end line numbers from the common file */
+	startc = result->f[common].start = use[low_thread].head->to.start;
+	endc   = result->f[common].end   = use[high_thread].tail->to.end;
+
+	/* find start/end line numbers for other files */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		struct diff2_hunk *t = s->tail;
+		int file = s->from;
+		int start = startc, end = endc;
+
+		assert(file >= 0 && file <= 2);
+
+		if (h) {
+			start = startc - h->to.start + h->from.start;
+			end   = endc   - t->to.end   + t->from.end;
+		} else if (last) {
+			int d = last->f[file].end - last->f[common].end;
+			start = startc + d;
+			end   = endc   + d;
+		}
+		result->f[file].start = start;
+		result->f[file].end   = end;
+	}
+
+	/* allocate hunk_lines line pointer and lengths arrays */
+	for (i = 0; i < 3; i++) {
+		int numlines = result->f[i].end - result->f[i].start + 1;
+
+		if (numlines > 0)
+			alloc_line_arrays(numlines, &result->f[i]);
+	}
+
+	/* copy line info for common file */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		int j;
+
+		for (j = 0; j < s->count; j++,h = h->next) {
+			int offset = h->to.start - startc;
+
+			if (!copy_lines(&result->f[common], offset, &h->to))
+				die("internal error: copy_lines()");
+
+		}
+	}
+
+	/* copy line info for other files */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		struct hunk_lines *tf, *cf;
+		int start, end, file = s->from;
+		int j, k, l, m;
+
+		assert(file >= 0 && file <= 2);
+
+		tf = &result->f[file];
+		cf = &result->f[common];
+
+		start = tf->start;
+		end   = tf->end;
+
+		/* before first hunk */
+		k = (h) ? h->from.start : end+1;
+		for (j = 0; j + start < k; j++) {
+			tf->lines[j]   = cf->lines[j];
+			tf->lengths[j] = cf->lengths[j];
+		}
+
+		/* hunk list */
+		for (j = 0; j < s->count; j++,h = h->next) {
+			int offset = h->from.start - start;
+
+			if (!copy_lines(&result->f[file], offset, &h->from))
+				die("internal error:");
+
+			/* between this and next hunk */
+			l = h->to.end + 1 - startc;
+			if (j < s->count-1)
+				k = h->next->from.start - start;
+			else
+				k = end+1 - start;
+			for (m = h->from.end + 1 - start; m < k; m++,l++) {
+				tf->lines[m]   = cf->lines[l];
+				tf->lengths[m] = cf->lengths[l];
+			}
+
+		}
+
+	}
+
+	/* set diff3 hunk type */
+	if (!use[0].head)
+		result->type = '0' + from1;
+	else if (!use[1].head)
+		result->type = '0' + from0;
+	else {
+		if (equal_lines(&result->f[from0], &result->f[from1]))
+			result->type = '0' + common;
+		else
+			result->type = 'a';
+	}
+
+	return result;
+}
+
+static struct diff3 *make_3way_diff(struct diff2 *d01, struct diff2 *d21)
+{
+	struct diff3 *result = new_diff3(d01, d21);
+	struct diff2_hunk *diff01 = d01->hunks;
+	struct diff2_hunk *diff21 = d21->hunks;
+	struct diff2_hunk *p[2], *high_diff, *other_diff;
+	struct diff3_hunk *hunk, **last_hunk;
+	struct diff3_hunk *last;
+	struct segment use[2];
+	int low_thread, high_thread;
+	int other_thread, high_line;
+
+	assert(d01->ft->num == d21->ft->num);
+	assert(d01->ff->num != d21->ff->num);
+
+	adjust_diff2(d01);
+	adjust_diff2(d21);
+
+	last_hunk = &result->hunks;
+	last = 0;
+	p[0] = diff01;
+	p[1] = diff21;
+
+	while (p[0] || p[1]) {
+		init_segment(&use[0], d01->ff->num, d01->ft->num);
+		init_segment(&use[1], d21->ff->num, d21->ft->num);
+
+		high_thread = low_thread = get_low_thread(p);
+
+		high_diff = p[high_thread];
+		high_line = high_diff->to.end;
+
+		add_segment(&use[high_thread], high_diff);
+		p[high_thread] = high_diff->next;
+
+		other_thread = high_thread ^ 0x1;
+		other_diff   = p[other_thread];
+
+		while (other_diff && other_diff->to.start <= high_line+1) {
+
+			add_segment(&use[other_thread], other_diff);
+			p[other_thread] = p[other_thread]->next;
+
+			if (high_line < other_diff->to.end) {
+				high_thread ^= 0x1;
+				high_diff = other_diff;
+				high_line = other_diff->to.end;
+			}
+
+			other_thread = high_thread ^ 0x1;
+			other_diff   = p[other_thread];
+		}
+
+		hunk = make_diff3_hunk(use, low_thread, high_thread, last);
+
+		if (!hunk)
+			die("can't create diff3 hunk");
+
+		/* add this hunk to the end of the list */
+		*last_hunk = hunk;
+		last_hunk  = &hunk->next;
+
+		last = hunk;
+	}
+
+	*last_hunk = 0;
+
+	return result;
+}
+
+#ifdef DUMMY
+static void print_diff2_hunk(struct diff2_hunk *p)
+{
+	struct hunk_lines *f = &p->from;
+	struct hunk_lines *t = &p->to;
+	int i, type = p->type;
+
+	printf("%d,%d %c %d,%d\n", f->start, f->end, type, t->start, t->end);
+
+	for (i = 0; i < f->size; i++)
+		printf("< %.*s", f->lengths[i], f->lines[i]);
+
+	printf("---\n");
+
+	for (i = 0; i < t->size; i++)
+		printf("> %.*s", t->lengths[i], t->lines[i]);
+
+	printf("+++\n");
+}
+
+static void print_diff2(struct diff2 *d)
+{
+	struct diff2_hunk *p;
+
+	printf("<--- diff2 --->\n");
+	for (p = d->hunks; p; p = p->next)
+		print_diff2_hunk(p);
+	printf(">=============<\n");
+}
+#endif
+
+static void print_diff3_hunk(FILE *outf, struct diff3_hunk *p, char *prefix)
+{
+	int i, j, k, skip_print;
+
+	if (p->type == 'a')
+		fprintf(outf, "====\n");
+	else
+		fprintf(outf, "====%c\n", p->type+1);
+
+	switch (p->type) {
+		case '0': skip_print = 1; break;
+		case '1': skip_print = 0; break;
+		case '2': skip_print = 0; break;
+		case 'a': skip_print = 3; break;
+		default:
+			die("internal error: invalid diff3 hunk type");
+			break;
+	}
+
+	for (j = 0,k = 0; j < 3; j++,k++) {
+		int  numlines, start, end, len;
+		char *s;
+
+		if (p->type == '1' && j > 0)
+			k = (j == 1) ? 2 : 1;
+
+		start = p->f[k].start;
+		end   = p->f[k].end;
+
+		numlines = end - start + 1;
+
+		if (numlines == 0)
+			fprintf(outf, "%d:%da\n", k+1, start-1);
+		else if (numlines == 1)
+			fprintf(outf, "%d:%dc\n", k+1, start);
+		else
+			fprintf(outf, "%d:%d,%dc\n", k+1, start, end);
+
+		if (k == skip_print)
+			continue;
+
+		assert(numlines == p->f[k].size);
+
+		if (p->f[k].size <= 0)
+			continue;
+
+		s   = 0;
+		len = 0;
+
+		for (i = 0; i < p->f[k].size; i++) {
+			s = p->f[k].lines[i];
+			len = p->f[k].lengths[i];
+			fprintf(outf, "%s", prefix);
+			fwrite(s, 1, len, outf);
+		}
+
+		if (s && (len == 0 || s[len-1] != '\n'))
+			fprintf(outf, "\n\\ No newline at end of file\n");
+
+	}
+
+}
+
+static void print_diff3(FILE *outf, struct diff3 *d, struct diff3_options *o)
+{
+	struct diff3_hunk *p;
+	char *prefix = (o->initial_tab) ? "\t" : "  ";
+
+	for (p = d->hunks; p; p = p->next)
+		print_diff3_hunk(outf, p, prefix);
+}
+
+static int write_lines(FILE *outf, char **curp, char *limit, int num)
+{
+	char *p = *curp;
+	char *pn;
+	int  size;
+
+	assert(num >= 0);
+
+	while (num--) {
+		if ((pn = nextline(p, limit)) == 0) {
+			if (num > 0)
+				return -1;
+			pn = limit;
+		}
+		p = pn;
+	}
+	size = (int)(p - *curp);
+	if (size > 0)
+		fwrite(*curp, 1, size, outf);
+	*curp = p;
+	return 0;
+}
+
+static int skip_lines(char **curp, char *limit, int num)
+{
+	char *p = *curp;
+	char *pn;
+
+	assert(num >= 0);
+
+	while (num--) {
+		if ((pn = nextline(p, limit)) == 0) {
+			if (num > 0)
+				return -1;
+			pn = limit;
+		}
+		p = pn;
+	}
+	*curp = p;
+	return 0;
+}
+
+static int diff3_merge(FILE *outf, mmfile_t *inf, struct diff3 *d, struct finfo f[3], struct diff3_options *opts)
+{
+	struct diff3_hunk *p;
+	int conflicts = 0;
+	int inf_lines = 0;
+	char *infp, *inf_limit;
+
+	infp = inf->ptr;
+	inf_limit = infp + inf->size;
+
+	for (p = d->hunks; p; p = p->next) {
+		int conflict = 0;
+		int i, lines;
+
+		/* check if we can skip this block */
+		switch (p->type) {
+			default:
+			case '0': /* change unique to our file: skip */
+				continue;
+				break;
+			case '1': /* same in ours and theirs: merged change */
+				if (!opts->show_all)
+					continue;
+				conflict = 1;
+				break;
+			case '2': /* change only in theirs: un-merged change */
+				if (opts->overlap_only)
+					continue;
+				break;
+			case 'a': /* all files differ: overlapping change */
+				if (opts->unmerged_only)
+					continue;
+				conflict = opts->markers;
+				break;
+		}
+
+		/* write lines from '0' (our) file (if any) */
+		lines = p->f[0].start - inf_lines - 1;
+		if (lines > 0) {
+			if (write_lines(outf, &infp, inf_limit, lines) < 0)
+				die("unexpected eof on file");
+			inf_lines += lines;
+		}
+
+		if (conflict) {
+			/* write lines from files with conflict markers */
+			char *marker_fmt = "<<<<<<< %s\n";
+
+			conflicts++;
+
+			if (p->type == 'a') {
+				/* write lines from '0' (our) file */
+				fprintf(outf, marker_fmt, f[0].label);
+				for (i = 0; i < p->f[0].size; i++) {
+					char *s = p->f[0].lines[i];
+					int len = p->f[0].lengths[i];
+					fwrite(s, 1, len, outf);
+				}
+				marker_fmt = "||||||| %s\n";
+			}
+
+			if (opts->show_all) {
+				/* write lines from '1' (base) file */
+				fprintf(outf, marker_fmt, f[1].label);
+				for (i = 0; i < p->f[1].size; i++) {
+					char *s = p->f[1].lines[i];
+					int len = p->f[1].lengths[i];
+					fwrite(s, 1, len, outf);
+				}
+			}
+
+			fprintf(outf, "=======\n");
+
+		}
+
+		/* write lines from '2' (their) file */
+		for (i = 0; i < p->f[2].size; i++) {
+			char *s = p->f[2].lines[i];
+			int len = p->f[2].lengths[i];
+			fwrite(s, 1, len, outf);
+		}
+
+		/* add conflict marker */
+		if (conflict)
+			fprintf(outf, ">>>>>>> %s\n", f[2].label);
+
+		/* skip lines from '0' (our) file */
+		lines = p->f[0].size;
+		if (lines > 0) {
+			if (skip_lines(&infp, inf_limit, lines) < 0)
+				die("unexpected eof");
+			inf_lines += lines;
+		}
+
+	}
+
+	/* write remainder of '0' (our) file */
+	if (infp < inf_limit)
+		fwrite(infp, 1, (int)(inf_limit - infp), outf);
+
+	return conflicts;
+}
+
+int diff3(FILE *outf, const char *file[3], const char *label[3], struct diff3_options *opts)
+{
+	int i;
+	struct finfo f[3];
+	struct diff2 *d1, *d2;
+	struct diff3 *d3;
+	struct diff_argv *prog = 0;
+	int conflicts = 0;
+
+	for ( i = 0; i < 3; i++ )
+		init_finfo(&f[i], file[i], label[i], i);
+
+	if (opts->diff_prog)
+		prog = new_diff_argv(opts->diff_prog);
+
+	d2 = process_diff(prog, &f[2], &f[1]);
+	free_mmfile(&f[2].mf);
+
+	d1 = process_diff(prog, &f[0], &f[1]);
+	free_mmfile(&f[1].mf);
+
+	d3 = make_3way_diff(d1, d2);
+
+	if (opts->merge) {
+		map_file(&f[0]);
+		conflicts = diff3_merge(outf, &f[0].mf, d3, f, opts);
+	} else {
+		print_diff3(outf, d3, opts);
+	}
+
+	free_mmfile(&f[0].mf);
+	free_diff3(d3);  /* also free()'s d1 and d2 */
+	free_diff_argv(prog);
+
+	return conflicts;
+}
+
+
+static const char builtin_diff3_usage[] =
+"git-diff3 [options]... ours base theirs\n"
+"\n"
+"  -e  --ed            output unmerged changes from BASE to THEIRS into OURS\n"
+"  -E  --show-overlap  output unmerged changes, bracketing conflicts\n"
+"  -A  --show-all      output all changes, bracketing conflicts\n"
+"  -x  --overlap-only  output overlapping changes\n"
+"  -X                  output overlapping changes, bracketing them\n"
+"  -3  --easy-only     output unmerged non-overlapping changes\n"
+"\n"
+"  -m  --merge         output merged file instead of ed script\n"
+"  -T  --initial-tab   make tabs line up by prepending a tab\n"
+"\n"
+"  -L LABEL  --label=LABEL  use LABEL instead of filename\n"
+"  --diff-program=PROGRAM   use PROGRAM to compare files\n"
+;
+
+
+int cmd_diff3(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	const char *label_strings[3];
+	int label_count = 0;
+	int conflicts = 0;
+	int edarg = 0;
+	const char **file;
+	struct diff3_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+
+	for (i = 1 ; i < argc ; i++) {
+		const char *arg = argv[i];
+
+		if (*arg != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strcmp(arg, "-e") || !strcmp(arg, "--ed")) {
+			edarg++;
+			continue;
+		}
+		if (!strcmp(arg, "-E") || !strcmp(arg, "--show-overlap")) {
+			edarg++;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-A") || !strcmp(arg, "--show-all")) {
+			edarg++;
+			opts.show_all = 1;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-x") || !strcmp(arg, "--overlap-only")) {
+			edarg++;
+			opts.overlap_only = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-X")) {
+			edarg++;
+			opts.overlap_only = 1;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-3") || !strcmp(arg, "--easy-only")) {
+			edarg++;
+			opts.unmerged_only = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-m") || !strcmp(arg, "--merge")) {
+			opts.merge = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-T") || !strcmp(arg, "--initial-tab")) {
+			opts.initial_tab = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-L")) {
+			if ( argc <= i+1 )
+				die("-L needs a LABEL argument");
+			if ( label_count < 3 )
+				label_strings[label_count++] = argv[++i];
+			else
+				die("too many labels options specified");
+			continue;
+		}
+		if (!strncmp(arg, "--label=", 8)) {
+			if ( !*(arg+8) )
+				die("--label needs a LABEL argument");
+			if ( label_count < 3 )
+				label_strings[label_count++] = arg+8;
+			else
+				die("too many labels options specified");
+			continue;
+		}
+		if (!strncmp(arg, "--diff-program=", 15)) {
+			if ( !*(arg+15) )
+				die("--diff-program needs a PROGRAM argument");
+			opts.diff_prog = arg+15;
+			continue;
+		}
+		if (*(arg+1) != '-') {
+			const char *a = arg+1;
+
+			while (*a) {
+				switch (*a) {
+					case 'A':
+						edarg++;
+						opts.show_all = 1;
+						opts.markers = 1;
+						break;
+					case 'e':
+						edarg++;
+						break;
+					case 'E':
+						edarg++;
+						opts.markers = 1;
+						break;
+					case 'x':
+						edarg++;
+						opts.overlap_only = 1;
+						break;
+					case 'X':
+						edarg++;
+						opts.overlap_only = 1;
+						opts.markers = 1;
+						break;
+					case '3':
+						edarg++;
+						opts.unmerged_only = 1;
+						break;
+					case 'm':
+						opts.merge = 1;
+						break;
+					case 'T':
+						opts.initial_tab = 1;
+						break;
+					default:
+						error("unknown option: %c\n", *a);
+						usage(builtin_diff3_usage);
+						break;
+				}
+				a++;
+			}
+			continue;
+		}
+		error("unrecognised option: %s\n", arg);
+		usage(builtin_diff3_usage);
+	}
+
+	if (edarg > 1)
+		die("at most one of the AeExX3 options are allowed");
+
+	/* -AeExX3 without -m implies ed script */
+	opts.edscript = edarg && !opts.merge;
+	/* -m without -AeExX3 implies -A */
+	if (opts.merge && !edarg) {
+		opts.show_all = 1;
+		opts.markers = 1;
+	}
+
+	if (opts.edscript)
+		die("ed scripts not yet supported!");
+
+	if (argc != i+3) {
+		if ( argc < i+3 )
+			error("missing file argument(s)\n");
+		else
+			error("too many arguments ('%s'...)\n", argv[i+3]);
+		usage(builtin_diff3_usage);
+	}
+
+	file = &argv[i];
+
+	for ( i = label_count; i < 3; i++ )
+		label_strings[i] = file[i];
+
+	conflicts = diff3(stdout, file, label_strings, &opts);
+
+	return conflicts > 0;
+}
+
+
diff --git a/builtin.h b/builtin.h
index 26ebcaf..a0e9ab7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -12,6 +12,8 @@ extern void help_unknown_cmd(const char 
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 
+extern int cmd_diff3(int argc, const char **argv, const char *prefix);
+
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index 18ba14a..a76c7b6 100644
--- a/git.c
+++ b/git.c
@@ -233,6 +233,7 @@ static void handle_internal_command(int 
 		{ "format-patch", cmd_format_patch, NEEDS_PREFIX },
 		{ "count-objects", cmd_count_objects },
 		{ "diff", cmd_diff, NEEDS_PREFIX },
+		{ "diff3", cmd_diff3 },
 		{ "grep", cmd_grep, NEEDS_PREFIX },
 		{ "rm", cmd_rm, NEEDS_PREFIX },
 		{ "add", cmd_add, NEEDS_PREFIX },
diff --git a/t/txxxx-diff3.sh b/t/txxxx-diff3.sh
new file mode 100755
index 0000000..3b515e7
--- /dev/null
+++ b/t/txxxx-diff3.sh
@@ -0,0 +1,175 @@
+#!/bin/sh
+
+test_description='diff3 tests'
+
+. ./test-lib.sh
+
+###########################################################
+# initialise counts
+
+diff3_count=0
+diff3_fails=0
+diff3_stats=0
+total_count=0
+total_fails=0
+total_stats=0
+
+###########################################################
+# test that "git diff3" and "diff3" give the same output
+# and the same exit code for a given set of arguments.
+
+test_diff3 () {
+
+    diff3_count=$(expr $diff3_count + 1)
+
+    diff3 "$@" >out-diff3
+    dret="$?"
+
+    git diff3 "$@" >out-git
+    gret="$?"
+
+    diff out-diff3 out-git >out-diff 2>&1
+
+    if [ "$?" != 0 ]
+    then
+        diff3_fails=$(expr $diff3_fails + 1)
+        echo "*   FAIL: --- " "$@" " ---"
+    fi
+    if [ "$dret" != "$gret" ]
+    then
+        diff3_stats=$(expr $diff3_stats + 1)
+        echo "*   STAT: ($dret != $gret) --- " "$@" " ---"
+    fi
+
+}
+
+###########################################################
+# test that "git diff3" and "diff3" give the same output
+# and the same exit code for several invocations of git
+# with different external diff command lines. (ie "normal"
+# diff and several unified diffs with context lines 0->5)
+
+test_diff3_extra () {
+
+    diff3 "$@" >out-diff3
+    dret="$?"
+
+    for dopt in "" "-U0" "-U1" "-U2" "-u" "-U4" "-U5"
+    do
+        diff3_count=$(expr $diff3_count + 1)
+
+        git diff3 --diff-program="diff $dopt" "$@" >out-git
+        gret="$?"
+
+        diff out-diff3 out-git >out-diff 2>&1
+
+        if [ "$?" != 0 ]
+        then
+            diff3_fails=$(expr $diff3_fails + 1)
+            echo "*   FAIL: --- --diff-program='diff $dopt'" "$@" " ---"
+        fi
+        if [ "$dret" != "$gret" ]
+        then
+            diff3_stats=$(expr $diff3_stats + 1)
+            echo "*   STAT: ($dret != $gret) --- --diff-program='diff $dopt'" "$@" " ---"
+        fi
+    done
+
+}
+
+###########################################################
+# given some options ($1) and three input files ($2->$4)
+# test git diff3 on permutations of the input files.
+
+test_perm_files () {
+    test_diff3 $1 $2 $3 $4
+    test_diff3 $1 $4 $3 $2
+    test_diff3 $1 $3 $2 $4
+    test_diff3 $1 $4 $2 $3
+    test_diff3 $1 $2 $4 $3
+    test_diff3 $1 $3 $4 $2
+}
+
+###########################################################
+# given three input files ($1->$3) test git diff3 output
+# ($opt == "") and all merge types for several permutaions
+# of the input files.  also test the external diff support.
+
+test_files () {
+
+    diff3_count=0
+    diff3_fails=0
+    diff3_stats=0
+
+    for opt in "" "-mA" "-me" "-mE" "-mx" "-mX" "-m3"
+    do
+        test_perm_files $opt $1 $2 $3
+    done
+
+    test_diff3_extra -mA -L our                  $1 $2 $3
+    test_diff3_extra -mA -L our -L base          $1 $2 $3
+    test_diff3_extra -mA -L our -L base -L their $1 $2 $3
+
+    total_count=$(expr $total_count + $diff3_count)
+    total_fails=$(expr $total_fails + $diff3_fails)
+    total_stats=$(expr $total_stats + $diff3_stats)
+
+    fails_and_stats=$(expr $diff3_fails + $diff3_stats)
+
+    case "$fails_and_stats" in
+    0)
+        return 0 ;;
+    *)
+        echo "    tests: $diff3_count, fails: $diff3_fails, stats: $diff3_stats"
+        return 1 ;;
+    esac
+
+}
+
+###########################################################
+# ok so test some files.
+
+test_expect_success \
+    "test files lao tzu tao" \
+    'test_files ../txxxx/lao ../txxxx/tzu ../txxxx/tao'
+
+test_expect_success \
+    "test files lao1 tzu tao1" \
+    'test_files ../txxxx/lao1 ../txxxx/tzu ../txxxx/tao1'
+
+test_expect_success \
+    "test files lao2 tzu tao2" \
+    'test_files ../txxxx/lao2 ../txxxx/tzu ../txxxx/tao2'
+
+test_expect_success \
+    "test files lao3 tzu tao3" \
+    'test_files ../txxxx/lao3 ../txxxx/tzu ../txxxx/tao3'
+
+test_expect_success \
+    "test files lao4 tzu tao4" \
+    'test_files ../txxxx/lao4 ../txxxx/tzu ../txxxx/tao4'
+
+test_expect_success \
+    "test files tzu tzu tzu" \
+    'test_files ../txxxx/tzu ../txxxx/tzu ../txxxx/tzu'
+
+test_expect_success \
+    "test files lao tzu tzu" \
+    'test_files ../txxxx/lao ../txxxx/tzu ../txxxx/tzu'
+
+test_expect_success \
+    "test files lao tzu /dev/null" \
+    'test_files ../txxxx/lao ../txxxx/tzu /dev/null'
+
+test_expect_success \
+    "test files /dev/null tzu /dev/null" \
+    'test_files /dev/null ../txxxx/tzu /dev/null'
+
+###########################################################
+# ok we are done.
+
+echo "    tests: $total_count, fails: $total_fails, stats: $total_stats"
+
+test_done
+
+
diff --git a/t/txxxx/lao b/t/txxxx/lao
new file mode 100644
index 0000000..635ef2c
--- /dev/null
+++ b/t/txxxx/lao
@@ -0,0 +1,11 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
diff --git a/t/txxxx/lao1 b/t/txxxx/lao1
new file mode 100644
index 0000000..831654a
--- /dev/null
+++ b/t/txxxx/lao1
@@ -0,0 +1,8 @@
+The Nameless is the origin of Heaven and Earth;
+
+  so we may see their subtlety,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+They both may be called deep and profound.
+The door of all subtleties!
diff --git a/t/txxxx/lao2 b/t/txxxx/lao2
new file mode 100644
index 0000000..a7babdc
--- /dev/null
+++ b/t/txxxx/lao2
@@ -0,0 +1,16 @@
+The Nameless is the origin of Heaven and Earth;
+home, away
+The named is the mother of all things.
+
+Amy, Helen, Kym
+Therefore let there always be non-being,
+  so we may see their subtlety,
+fred, bill, george
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/lao3 b/t/txxxx/lao3
new file mode 100644
index 0000000..ea500e5
--- /dev/null
+++ b/t/txxxx/lao3
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of ALL things.
+
+Therefore let there ALWAYS be non-being,
+  so we may see their subtlety,
+And let there ALWAYS be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/lao4 b/t/txxxx/lao4
new file mode 100644
index 0000000..2ca7ddd
--- /dev/null
+++ b/t/txxxx/lao4
@@ -0,0 +1,12 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+unfinished symphony
\ No newline at end of file
diff --git a/t/txxxx/tao b/t/txxxx/tao
new file mode 100644
index 0000000..ffe1ba3
--- /dev/null
+++ b/t/txxxx/tao
@@ -0,0 +1,14 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their result.
+The two are the same,
+But after they are produced,
+  they have different names.
+
+  -- The Way of Lao-Tzu, tr. Wing-tsit Chan
diff --git a/t/txxxx/tao1 b/t/txxxx/tao1
new file mode 100644
index 0000000..7c0e98a
--- /dev/null
+++ b/t/txxxx/tao1
@@ -0,0 +1,10 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+Therefore let there always be non-being,
+And let there always be being,
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called DEEP and profound.
+Deeper and more profound,
+The door of ALL subtleties!
diff --git a/t/txxxx/tao2 b/t/txxxx/tao2
new file mode 100644
index 0000000..033360b
--- /dev/null
+++ b/t/txxxx/tao2
@@ -0,0 +1,16 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+mars, venus, neptune
+
+Therefore let there always be non-being,
+quartz, diamond, ruby
+  so we may see their subtlety,
+And let there always be being,
+lilly, rose, tulip
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/tao3 b/t/txxxx/tao3
new file mode 100644
index 0000000..0457551
--- /dev/null
+++ b/t/txxxx/tao3
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+THEIR
+Therefore let there always be non-being,
+  so we may see THEIR subtlety,
+And let there always be being,
+  so we may see THEIR outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/tao4 b/t/txxxx/tao4
new file mode 100644
index 0000000..fbe30fd
--- /dev/null
+++ b/t/txxxx/tao4
@@ -0,0 +1,15 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their result.
+The two are the same,
+But after they are produced,
+  they have different names.
+
+  -- The Way of Lao-Tzu, tr. Wing-tsit Chan
+unchained melody
\ No newline at end of file
diff --git a/t/txxxx/tzu b/t/txxxx/tzu
new file mode 100644
index 0000000..5af88a8
--- /dev/null
+++ b/t/txxxx/tzu
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
-- 
1.4.2


  parent reply	other threads:[~2006-11-22 20:01 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-11-21 22:24 (unknown) Johannes Schindelin
2006-11-21 23:15 ` [PATCH] xdiff: add xdl_merge() (was: (unknown)) Jakub Narebski
2006-11-22  9:29   ` Johannes Schindelin
2006-11-22 19:58 ` Ramsay Jones [this message]
2006-11-22 20:16 ` your mail Davide Libenzi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4564ABE3.6080605@ramsay1.demon.co.uk \
    --to=ramsay@ramsay1.demon.co.uk \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=davidel@xmailserver.org \
    --cc=git@vger.kernel.org \
    --cc=junkio@cox.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).