Commit Diff


commit - 57ee5d5084c5c07c321352b7412a51d2f89ef298
commit + 6353ad76e488c8d0df631a779571fdeb41fc9c70
blob - c4412341928e247e4ba1e74b8a4014eb2107252d
blob + ee6fe6460a2a0de9ae5395eb0de8571d8aa8a135
--- got/Makefile
+++ got/Makefile
@@ -5,7 +5,7 @@ SRCS=		got.c blame.c commit_graph.c delta.c diff.c dif
 		diffreg.c error.c fileindex.c object.c object_cache.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
-		inflate.c
+		inflate.c buf.c worklist.c rcsutil.c diff3.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \
 	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
blob - 7f9026a9b8d5c96a24479753a750d7e3d7ab7eec
blob + b8c74fd38cb835f3c4f177441861efc0fba475fb
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -23,6 +23,8 @@ struct got_worktree;
 #define GOT_STATUS_UPDATE	'U'
 #define GOT_STATUS_DELETE	'D'
 #define GOT_STATUS_MODIFIY	'M'
+#define GOT_STATUS_CONFLICT	'C'
+#define GOT_STATUS_MERGE	'G'
 #define GOT_STATUS_MISSING	'!'
 #define GOT_STATUS_UNVERSIONED	'?'
 #define GOT_STATUS_OBSTRUCTED	'~'
blob - c0ccc0d1604c7cfb8c922ce2a76a686a2443fc8d
blob + 9a944a8f829e0a7732e518c02a5246ce3e4f0c2c
--- lib/worktree.c
+++ lib/worktree.c
@@ -46,6 +46,7 @@
 #include "got_lib_inflate.h"
 #include "got_lib_delta.h"
 #include "got_lib_object.h"
+#include "got_lib_diff.h"
 
 #ifndef MIN
 #define	MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
@@ -593,26 +594,115 @@ add_dir_on_disk(struct got_worktree *worktree, const c
 
 done:
 	free(abspath);
+	return err;
+}
+
+/*
+ * Perform a 3-way merge where the file's version in the file index (blob2)
+ * acts as the common ancestor, the incoming blob (blob1) acts as the first
+ * derived version, and the file on disk acts as the second derived version.
+ */
+static const struct got_error *
+merge_blob(struct got_worktree *worktree, struct got_fileindex *fileindex,
+   struct got_fileindex_entry *ie, const char *ondisk_path, const char *path,
+   struct got_blob_object *blob1, struct got_repository *repo,
+   got_worktree_checkout_cb progress_cb, void *progress_arg)
+{
+	const struct got_error *err = NULL;
+	int merged_fd = -1;
+	struct got_blob_object *blob2 = NULL;
+	FILE *f1 = NULL, *f2 = NULL;
+	char *blob1_path = NULL, *blob2_path = NULL;
+	char *merged_path = NULL;
+	struct got_object_id id2;
+	char *id_str = NULL;
+	char *label1 = NULL;
+	int overlapcnt = 0;
+
+	err = got_opentemp_named_fd(&merged_path, &merged_fd, "/tmp/got-merged");
+	if (err)
+		return err;
+	err = got_opentemp_named(&blob1_path, &f1, "/tmp/got-merge-blob1");
+	if (err)
+		goto done;
+	err = got_object_blob_dump_to_file(NULL, NULL, f1, blob1);
+	if (err)
+		goto done;
+
+	err = got_opentemp_named(&blob2_path, &f2, "/tmp/got-merge-blob2");
+	if (err)
+		goto done;
+
+	memcpy(id2.sha1, ie->blob_sha1, SHA1_DIGEST_LENGTH);
+	err = got_object_open_as_blob(&blob2, repo, &id2, 8192);
+	if (err)
+		goto done;
+	err = got_object_blob_dump_to_file(NULL, NULL, f2, blob2);
+	if (err)
+		goto done;
+
+	err = got_object_id_str(&id_str, worktree->base_commit_id);
+	if (err)
+		goto done;
+	if (asprintf(&label1, "commit %s", id_str) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	err = got_merge_diff3(&overlapcnt, merged_fd, blob1_path,
+	    blob2_path, ondisk_path, label1, path);
+	if (err)
+		goto done;
+
+	(*progress_cb)(progress_arg,
+	    overlapcnt > 0 ? GOT_STATUS_CONFLICT : GOT_STATUS_MERGE, path);
+
+
+	fsync(merged_fd);
+
+	if (rename(merged_path, ondisk_path) != 0) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	err = got_fileindex_entry_update(ie, ondisk_path,
+	    blob1->id.sha1, worktree->base_commit_id->sha1);
+done:
+	if (merged_fd != -1)
+		close(merged_fd);
+	if (f1)
+		fclose(f1);
+	if (f2)
+		fclose(f2);
+	if (blob2)
+		got_object_blob_close(blob2);
+	free(merged_path);
+	if (blob1_path) {
+		unlink(blob1_path);
+		free(blob1_path);
+	}
+	if (blob2_path) {
+		unlink(blob2_path);
+		free(blob2_path);
+	}
+	free(id_str);
+	free(label1);
 	return err;
 }
 
 static const struct got_error *
 install_blob(struct got_worktree *worktree, struct got_fileindex *fileindex,
-   struct got_fileindex_entry *entry, const char *path,
+   struct got_fileindex_entry *entry, const char *ondisk_path, const char *path,
    struct got_blob_object *blob,
    struct got_repository *repo, got_worktree_checkout_cb progress_cb,
    void *progress_arg)
 {
 	const struct got_error *err = NULL;
-	char *ondisk_path;
 	int fd = -1;
 	size_t len, hdrlen;
 	int update = 0;
 	char *tmppath = NULL;
 
-	if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1)
-		return got_error_from_errno();
-
 	fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
 	    GOT_DEFAULT_FILE_MODE);
 	if (fd == -1) {
@@ -695,8 +785,83 @@ install_blob(struct got_worktree *worktree, struct got
 done:
 	if (fd != -1)
 		close(fd);
-	free(ondisk_path);
 	free(tmppath);
+	return err;
+}
+
+static const struct got_error *
+get_file_status(unsigned char *status, struct got_fileindex_entry *ie,
+    const char *abspath, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	struct got_object_id id;
+	size_t hdrlen;
+	FILE *f = NULL;
+	uint8_t fbuf[8192];
+	struct got_blob_object *blob = NULL;
+	size_t flen, blen;
+	struct stat sb;
+
+	*status = GOT_STATUS_NO_CHANGE;
+
+	if (lstat(abspath, &sb) == -1)
+		return got_error_from_errno();
+
+	if (!S_ISREG(sb.st_mode)) {
+		*status = GOT_STATUS_OBSTRUCTED;
+		return NULL;
+	}
+
+	if (ie->ctime_sec == sb.st_ctime &&
+	    ie->ctime_nsec == sb.st_ctimensec &&
+	    ie->mtime_sec == sb.st_mtime &&
+	    ie->mtime_sec == sb.st_mtime &&
+	    ie->mtime_nsec == sb.st_mtimensec &&
+	    ie->size == (sb.st_size & 0xffffffff))
+		return NULL;
+
+	memcpy(id.sha1, ie->blob_sha1, sizeof(id.sha1));
+	err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf));
+	if (err)
+		return err;
+
+	f = fopen(abspath, "r");
+	if (f == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	hdrlen = got_object_blob_get_hdrlen(blob);
+	while (1) {
+		const uint8_t *bbuf = got_object_blob_get_read_buf(blob);
+		err = got_object_blob_read_block(&blen, blob);
+		if (err)
+			break;
+		flen = fread(fbuf, 1, sizeof(fbuf), f);
+		if (blen == 0) {
+			if (flen != 0)
+				*status = GOT_STATUS_MODIFIY;
+			break;
+		} else if (flen == 0) {
+			if (blen != 0)
+				*status = GOT_STATUS_MODIFIY;
+			break;
+		} else if (blen - hdrlen == flen) {
+			/* Skip blob object header first time around. */
+			if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) {
+				*status = GOT_STATUS_MODIFIY;
+				break;
+			}
+		} else {
+			*status = GOT_STATUS_MODIFIY;
+			break;
+		}
+		hdrlen = 0;
+	}
+done:
+	if (blob)
+		got_object_blob_close(blob);
+	if (f)
+		fclose(f);
 	return err;
 }
 
@@ -709,26 +874,47 @@ update_blob(struct got_worktree *worktree,
 {
 	const struct got_error *err = NULL;
 	struct got_blob_object *blob = NULL;
+	char *ondisk_path;
+	unsigned char status = GOT_STATUS_NO_CHANGE;
 
+	if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, path) == -1)
+		return got_error_from_errno();
+
 	if (ie) {
 		if (memcmp(ie->commit_sha1, worktree->base_commit_id->sha1,
 		    SHA1_DIGEST_LENGTH) == 0) {
 			(*progress_cb)(progress_arg, GOT_STATUS_EXISTS,
 			    path);
-			return NULL;
+			goto done;
 		}
 		if (memcmp(ie->blob_sha1,
 		    te->id->sha1, SHA1_DIGEST_LENGTH) == 0)
-			return NULL;
+			goto done;
+
+		err = get_file_status(&status, ie, ondisk_path, repo);
+		if (err)
+			goto done;
+
+		if (status == GOT_STATUS_OBSTRUCTED) {
+			(*progress_cb)(progress_arg, status, path);
+			goto done;
+		}
 	}
 
 	err = got_object_open_as_blob(&blob, repo, te->id, 8192);
 	if (err)
-		return err;
+		goto done;
 
-	err = install_blob(worktree, fileindex, ie, path, blob, repo,
-	    progress_cb, progress_arg);
+	if (status == GOT_STATUS_MODIFIY)
+		err = merge_blob(worktree, fileindex, ie, ondisk_path, path,
+		    blob, repo, progress_cb, progress_arg);
+	else
+		err = install_blob(worktree, fileindex, ie, ondisk_path, path,
+		    blob, repo, progress_cb, progress_arg);
+
 	got_object_blob_close(blob);
+done:
+	free(ondisk_path);
 	return err;
 }
 
@@ -944,82 +1130,6 @@ struct diff_dir_cb_arg {
 };
 
 static const struct got_error *
-get_file_status(unsigned char *status, struct got_fileindex_entry *ie,
-    const char *abspath, struct got_repository *repo)
-{
-	const struct got_error *err = NULL;
-	struct got_object_id id;
-	size_t hdrlen;
-	FILE *f = NULL;
-	uint8_t fbuf[8192];
-	struct got_blob_object *blob = NULL;
-	size_t flen, blen;
-	struct stat sb;
-
-	*status = GOT_STATUS_NO_CHANGE;
-
-	if (lstat(abspath, &sb) == -1)
-		return got_error_from_errno();
-
-	if (!S_ISREG(sb.st_mode)) {
-		*status = GOT_STATUS_OBSTRUCTED;
-		return NULL;
-	}
-
-	if (ie->ctime_sec == sb.st_ctime &&
-	    ie->ctime_nsec == sb.st_ctimensec &&
-	    ie->mtime_sec == sb.st_mtime &&
-	    ie->mtime_sec == sb.st_mtime &&
-	    ie->mtime_nsec == sb.st_mtimensec &&
-	    ie->size == (sb.st_size & 0xffffffff))
-		return NULL;
-
-	memcpy(id.sha1, ie->blob_sha1, sizeof(id.sha1));
-	err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf));
-	if (err)
-		return err;
-
-	f = fopen(abspath, "r");
-	if (f == NULL) {
-		err = got_error_from_errno();
-		goto done;
-	}
-	hdrlen = got_object_blob_get_hdrlen(blob);
-	while (1) {
-		const uint8_t *bbuf = got_object_blob_get_read_buf(blob);
-		err = got_object_blob_read_block(&blen, blob);
-		if (err)
-			break;
-		flen = fread(fbuf, 1, sizeof(fbuf), f);
-		if (blen == 0) {
-			if (flen != 0)
-				*status = GOT_STATUS_MODIFIY;
-			break;
-		} else if (flen == 0) {
-			if (blen != 0)
-				*status = GOT_STATUS_MODIFIY;
-			break;
-		} else if (blen - hdrlen == flen) {
-			/* Skip blob object header first time around. */
-			if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) {
-				*status = GOT_STATUS_MODIFIY;
-				break;
-			}
-		} else {
-			*status = GOT_STATUS_MODIFIY;
-			break;
-		}
-		hdrlen = 0;
-	}
-done:
-	if (blob)
-		got_object_blob_close(blob);
-	if (f)
-		fclose(f);
-	return err;
-}
-
-static const struct got_error *
 status_old_new(void *arg, struct got_fileindex_entry *ie,
     struct dirent *de, const char *parent_path)
 {
blob - 32c4c6ac4ab75a05b1469c969541296100c928ae
blob + de6d7654c62255aae15280ee7a09d127ca06b949
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -607,7 +607,81 @@ function test_update_file_in_subsubdir {
 
 	test_done "$testroot" "0"
 }
+
+function test_update_merges_file_edits {
+	local testroot=`test_init update_merges_file_edits`
+
+	echo "1" > $testroot/repo/numbers
+	echo "2" >> $testroot/repo/numbers
+	echo "3" >> $testroot/repo/numbers
+	echo "4" >> $testroot/repo/numbers
+	echo "5" >> $testroot/repo/numbers
+	echo "6" >> $testroot/repo/numbers
+	echo "7" >> $testroot/repo/numbers
+	echo "8" >> $testroot/repo/numbers
+	(cd $testroot/repo && git add numbers)
+	git_commit $testroot/repo -m "added numbers file"
 
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	if [ "$?" != "0" ]; then
+		test_done "$testroot" "$?"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/repo/alpha
+	echo "modified beta" > $testroot/repo/beta
+	sed -i 's/2/22/' $testroot/repo/numbers
+	git_commit $testroot/repo -m "modified 3 files"
+
+	echo "modified alpha, too" > $testroot/wt/alpha
+	touch $testroot/wt/beta
+	sed -i 's/7/77/' $testroot/wt/numbers
+
+	echo "C  alpha" > $testroot/stdout.expected
+	echo "U  beta" >> $testroot/stdout.expected
+	echo "G  numbers" >> $testroot/stdout.expected
+	echo -n "Updated to commit " >> $testroot/stdout.expected
+	git_show_head $testroot/repo >> $testroot/stdout.expected
+	echo >> $testroot/stdout.expected
+
+	(cd $testroot/wt && got update > $testroot/stdout)
+
+	cmp $testroot/stdout.expected $testroot/stdout
+	if [ "$?" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$?"
+		return 1
+	fi
+
+	echo -n "<<<<<<< commit " > $testroot/content.expected
+	git_show_head $testroot/repo >> $testroot/content.expected
+	echo >> $testroot/content.expected
+	echo "modified alpha" >> $testroot/content.expected
+	echo "=======" >> $testroot/content.expected
+	echo "modified alpha, too" >> $testroot/content.expected
+	echo '>>>>>>> alpha' >> $testroot/content.expected
+	echo "modified beta" >> $testroot/content.expected
+	echo "1" >> $testroot/content.expected
+	echo "22" >> $testroot/content.expected
+	echo "3" >> $testroot/content.expected
+	echo "4" >> $testroot/content.expected
+	echo "5" >> $testroot/content.expected
+	echo "6" >> $testroot/content.expected
+	echo "77" >> $testroot/content.expected
+	echo "8" >> $testroot/content.expected
+
+	cat $testroot/wt/alpha > $testroot/content
+	cat $testroot/wt/beta >> $testroot/content
+	cat $testroot/wt/numbers >> $testroot/content
+
+	cmp $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_update_basic
 run_test test_update_adds_file
 run_test test_update_deletes_file
@@ -621,3 +695,4 @@ run_test test_update_moves_files_to_new_dir
 run_test test_update_creates_missing_parent
 run_test test_update_creates_missing_parent_with_subdir
 run_test test_update_file_in_subsubdir
+run_test test_update_merges_file_edits
blob - 622918d65a3235175fbd16aaffcc60725d7d93cc
blob + 8fb53dfe4ebce9973fd7098c98914117b3baf3bf
--- regress/idset/Makefile
+++ regress/idset/Makefile
@@ -3,7 +3,8 @@
 PROG = idset_test
 SRCS = error.c object.c privsep.c sha1.c pack.c inflate.c path.c opentemp.c \
 	delta.c repository.c reference.c worktree.c fileindex.c object_cache.c \
-	object_idset.c object_parse.c idset_test.c
+	object_idset.c object_parse.c idset_test.c \
+	buf.c worklist.c rcsutil.c diff.c diffreg.c diff3.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - 5da0a61342d57368a2ad4a97cf0cfcacf13e6028
blob + d75f821ba3e7ed1050713ad1cc994f972eda1ee1
--- regress/repository/Makefile
+++ regress/repository/Makefile
@@ -4,6 +4,7 @@ PROG = repository_test
 SRCS = path.c repository.c error.c reference.c object.c object_cache.c \
 	object_idset.c object_parse.c opentemp.c sha1.c diff.c diffreg.c \
 	pack.c privsep.c delta.c fileindex.c worktree.c inflate.c \
+	buf.c worklist.c rcsutil.c diff3.c \
 	repository_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \
blob - f31fa65e08a2297db4491f0a7c4c42af2674c65e
blob + 21476afed2b0930f65f44d151ae98a2ba3c55081
--- regress/worktree/Makefile
+++ regress/worktree/Makefile
@@ -3,7 +3,9 @@
 PROG = worktree_test
 SRCS = worktree.c repository.c object.c object_cache.c object_idset.c \
 	object_parse.c opentemp.c path.c error.c reference.c sha1.c pack.c \
-	privsep.c delta.c inflate.c fileindex.c worktree_test.c
+	privsep.c delta.c inflate.c fileindex.c \
+	buf.c worklist.c rcsutil.c diff.c diffreg.c diff3.c \
+	worktree_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib \
 	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}
blob - 72f8d6d7e4c381ebb68b1e052998d0843ff100aa
blob + 87516af1da37593f11b27b5440b5d09a61937e7e
--- tog/Makefile
+++ tog/Makefile
@@ -5,7 +5,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c dif
 		diffreg.c error.c fileindex.c object.c object_cache.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
-		utf8.c inflate.c
+		utf8.c inflate.c buf.c worklist.c rcsutil.c diff3.c
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib \
 	-DGOT_LIBEXECDIR=${GOT_LIBEXECDIR}