diff options
| author | Linus Torvalds <torvalds@osdl.org> | 2006-06-28 22:06:36 -0700 | 
|---|---|---|
| committer | Junio C Hamano <junkio@cox.net> | 2006-06-28 22:24:45 -0700 | 
| commit | 0c7993839b92f466278439a724023f8be50391c1 (patch) | |
| tree | fd1fc5395f2dce9c742b0902c82fe60e4b9da718 | |
| parent | 83788070a3dd0a4905dd76c865320c51f7d74a83 (diff) | |
| download | git-0c7993839b92f466278439a724023f8be50391c1.tar.gz | |
Improved three-way blob merging code
This fleshes out the code that generates a three-way merge of a set of
blobs.
It still actually does the three-way merge using an external executable
(ie just calling "merge"), but the interfaces have been cleaned up a lot
and are now fully based on the 'mmfile_t' interface, so if libxdiff were
to ever grow a compatible three-way-merge, it could probably be directly
plugged in.
It also uses the previous XDL_EMIT_COMMON functionality extension to
libxdiff to generate a made-up base file for the merge for the case where
no base file previously existed. This should be equivalent to what we
currently do in git-merge-one-file.sh:
	diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add
except it should be much simpler and can be done using the direct libxdiff
interfaces.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | merge-file.c | 166 | ||||
| -rw-r--r-- | merge-tree.c | 73 | 
3 files changed, 240 insertions, 1 deletions
| @@ -217,7 +217,7 @@ LIB_OBJS = \  	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \  	tag.o tree.o usage.o config.o environment.o ctype.o copy.o \  	fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ -	alloc.o $(DIFF_OBJS) +	alloc.o merge-file.o $(DIFF_OBJS)  BUILTIN_OBJS = \  	builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ diff --git a/merge-file.c b/merge-file.c new file mode 100644 index 0000000000..f32c653825 --- /dev/null +++ b/merge-file.c @@ -0,0 +1,166 @@ +#include "cache.h" +#include "run-command.h" +#include "xdiff-interface.h" +#include "blob.h" + +static void rm_temp_file(const char *filename) +{ +	unlink(filename); +	free((void *)filename); +} + +static const char *write_temp_file(mmfile_t *f) +{ +	int fd; +	const char *tmp = getenv("TMPDIR"); +	char *filename; + +	if (!tmp) +		tmp = "/tmp"; +	filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX"); +	fd = mkstemp(filename); +	if (fd < 0) +		return NULL; +	filename = strdup(filename); +	if (f->size != xwrite(fd, f->ptr, f->size)) { +		rm_temp_file(filename); +		return NULL; +	} +	close(fd); +	return filename; +} + +static void *read_temp_file(const char *filename, unsigned long *size) +{ +	struct stat st; +	char *buf = NULL; +	int fd = open(filename, O_RDONLY); +	if (fd < 0) +		return NULL; +	if (!fstat(fd, &st)) { +		*size = st.st_size; +		buf = xmalloc(st.st_size); +		if (st.st_size != xread(fd, buf, st.st_size)) { +			free(buf); +			buf = NULL; +		} +	} +	close(fd); +	return buf; +} + +static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) +{ +	void *buf; +	unsigned long size; +	char type[20]; + +	buf = read_sha1_file(obj->object.sha1, type, &size); +	if (!buf) +		return -1; +	if (strcmp(type, blob_type)) +		return -1; +	f->ptr = buf; +	f->size = size; +	return 0; +} + +static void free_mmfile(mmfile_t *f) +{ +	free(f->ptr); +} + +static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) +{ +	void *res; +	const char *t1, *t2, *t3; + +	t1 = write_temp_file(base); +	t2 = write_temp_file(our); +	t3 = write_temp_file(their); +	res = NULL; +	if (t1 && t2 && t3) { +		int code = run_command("merge", t2, t1, t3, NULL); +		if (!code || code == -1) +			res = read_temp_file(t2, size); +	} +	rm_temp_file(t1); +	rm_temp_file(t2); +	rm_temp_file(t3); +	return res; +} + +static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) +{ +	int i; +	mmfile_t *dst = priv_; + +	for (i = 0; i < nbuf; i++) { +		memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size); +		dst->size += mb[i].size; +	} +	return 0; +} + +static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) +{ +	unsigned long size = f1->size < f2->size ? f1->size : f2->size; +	void *ptr = xmalloc(size); +	xpparam_t xpp; +	xdemitconf_t xecfg; +	xdemitcb_t ecb; + +	xpp.flags = XDF_NEED_MINIMAL; +	xecfg.ctxlen = 3; +	xecfg.flags = XDL_EMIT_COMMON; +	ecb.outf = common_outf; + +	res->ptr = ptr; +	res->size = 0; + +	ecb.priv = res; +	return xdl_diff(f1, f2, &xpp, &xecfg, &ecb); +} + +void *merge_file(struct blob *base, struct blob *our, struct blob *their, unsigned long *size) +{ +	void *res = NULL; +	mmfile_t f1, f2, common; + +	/* +	 * Removed in either branch? +	 * +	 * NOTE! This depends on the caller having done the +	 * proper warning about removing a file that got +	 * modified in the other branch! +	 */ +	if (!our || !their) { +		char type[20]; +		if (base) +			return NULL; +		if (!our) +			our = their; +		return read_sha1_file(our->object.sha1, type, size); +	} + +	if (fill_mmfile_blob(&f1, our) < 0) +		goto out_no_mmfile; +	if (fill_mmfile_blob(&f2, their) < 0) +		goto out_free_f1; + +	if (base) { +		if (fill_mmfile_blob(&common, base) < 0) +			goto out_free_f2_f1; +	} else { +		if (generate_common_file(&common, &f1, &f2) < 0) +			goto out_free_f2_f1; +	} +	res = three_way_filemerge(&common, &f1, &f2, size); +	free_mmfile(&common); +out_free_f2_f1: +	free_mmfile(&f2); +out_free_f1: +	free_mmfile(&f1); +out_no_mmfile: +	return res; +} diff --git a/merge-tree.c b/merge-tree.c index fd0c2111c4..7cf00be6d5 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -1,5 +1,6 @@  #include "cache.h"  #include "tree-walk.h" +#include "xdiff-interface.h"  #include "blob.h"  static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>"; @@ -52,6 +53,77 @@ static const char *explanation(struct merge_list *entry)  	return "removed in remote";  } +extern void *merge_file(struct blob *, struct blob *, struct blob *, unsigned long *); + +static void *result(struct merge_list *entry, unsigned long *size) +{ +	char type[20]; +	struct blob *base, *our, *their; + +	if (!entry->stage) +		return read_sha1_file(entry->blob->object.sha1, type, size); +	base = NULL; +	if (entry->stage == 1) { +		base = entry->blob; +		entry = entry->link; +	} +	our = NULL; +	if (entry && entry->stage == 2) { +		our = entry->blob; +		entry = entry->link; +	} +	their = NULL; +	if (entry) +		their = entry->blob; +	return merge_file(base, our, their, size); +} + +static void *origin(struct merge_list *entry, unsigned long *size) +{ +	char type[20]; +	while (entry) { +		if (entry->stage == 2) +			return read_sha1_file(entry->blob->object.sha1, type, size); +		entry = entry->link; +	} +	return NULL; +} + +static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf) +{ +	int i; +	for (i = 0; i < nbuf; i++) +		printf("%.*s", (int) mb[i].size, mb[i].ptr); +	return 0; +} + +static void show_diff(struct merge_list *entry) +{ +	unsigned long size; +	mmfile_t src, dst; +	xpparam_t xpp; +	xdemitconf_t xecfg; +	xdemitcb_t ecb; + +	xpp.flags = XDF_NEED_MINIMAL; +	xecfg.ctxlen = 3; +	xecfg.flags = 0; +	ecb.outf = show_outf; +	ecb.priv = NULL; + +	src.ptr = origin(entry, &size); +	if (!src.ptr) +		size = 0; +	src.size = size; +	dst.ptr = result(entry, &size); +	if (!dst.ptr) +		size = 0; +	dst.size = size; +	xdl_diff(&src, &dst, &xpp, &xecfg, &ecb); +	free(src.ptr); +	free(dst.ptr); +} +  static void show_result_list(struct merge_list *entry)  {  	printf("%s\n", explanation(entry)); @@ -70,6 +142,7 @@ static void show_result(void)  	walk = merge_result;  	while (walk) {  		show_result_list(walk); +		show_diff(walk);  		walk = walk->next;  	}  } | 
