Commit Diff


commit - c90c8ce30c933a907f591ebe599ed6b7506f8217
commit + 3b9f0f87ff620ce7d6ad75195b0c67425bc96ab7
blob - fa03c453a41b27d2fc77686b381c798275eb9f9c
blob + ca0ab90afd597806d087a341444532719ec5a156
--- got/got.1
+++ got/got.1
@@ -543,6 +543,7 @@ Show the status of each affected file, using the follo
 .It \(a~ Ta versioned file is obstructed by a non-regular file
 .It ! Ta a missing versioned file was restored
 .It # Ta file was not updated because it contains merge conflicts
+.It ? Ta changes destined for an unversioned file were not merged
 .El
 .Pp
 If no
blob - 437116fcd6048a862de574f7a76e6a3ae5c11309
blob + 40f832a405d94d9b628752ef3ed637eeba54faf5
--- lib/worktree.c
+++ lib/worktree.c
@@ -742,6 +742,8 @@ merge_file(int *local_changes_subsumed, struct got_wor
 	char *merged_path = NULL, *base_path = NULL;
 	int overlapcnt = 0;
 	char *parent;
+	char *symlink_path = NULL;
+	FILE *symlinkf = NULL;
 
 	*local_changes_subsumed = 0;
 
@@ -779,8 +781,45 @@ merge_file(int *local_changes_subsumed, struct got_wor
 		 */
 	}
 
+	/* 
+	 * In order the run a 3-way merge with a symlink we copy the symlink's
+	 * target path into a temporary file and use that file with diff3.
+	 */
+	if (S_ISLNK(st_mode)) {
+		char target_path[PATH_MAX];
+		ssize_t target_len;
+		size_t n;
+
+		free(base_path);
+		if (asprintf(&base_path, "%s/got-symlink-merge",
+		    parent) == -1) {
+			err = got_error_from_errno("asprintf");
+			base_path = NULL;
+			goto done;
+		}
+		err = got_opentemp_named(&symlink_path, &symlinkf, base_path);
+		if (err)
+			goto done;
+		target_len = readlink(ondisk_path, target_path,
+		    sizeof(target_path));
+		if (target_len == -1) {
+			err = got_error_from_errno2("readlink", ondisk_path);
+			goto done;
+		}
+		n = fwrite(target_path, 1, target_len, symlinkf);
+		if (n != target_len) {
+			err = got_ferror(symlinkf, GOT_ERR_IO);
+			goto done;
+		}
+		if (fflush(symlinkf) == EOF) {
+			err = got_error_from_errno2("fflush", symlink_path);
+			goto done;
+		}
+	}
+
 	err = got_merge_diff3(&overlapcnt, merged_fd, deriv_path,
-	    blob_orig_path, ondisk_path, label_deriv, label_orig, NULL);
+	    blob_orig_path, symlink_path ? symlink_path : ondisk_path,
+	    label_deriv, label_orig, NULL);
 	if (err)
 		goto done;
 
@@ -816,7 +855,14 @@ done:
 	if (err) {
 		if (merged_path)
 			unlink(merged_path);
+	}
+	if (symlink_path) {
+		if (unlink(symlink_path) == -1 && err == NULL)
+			err = got_error_from_errno2("unlink", symlink_path);
 	}
+	if (symlinkf && fclose(symlinkf) == EOF && err == NULL)
+		err = got_error_from_errno2("fclose", symlink_path);
+	free(symlink_path);
 	if (merged_fd != -1 && close(merged_fd) != 0 && err == NULL)
 		err = got_error_from_errno("close");
 	if (f_orig && fclose(f_orig) != 0 && err == NULL)
@@ -1155,7 +1201,7 @@ install_blob(struct got_worktree *worktree, const char
     const char *path, mode_t te_mode, mode_t st_mode,
     struct got_blob_object *blob, int restoring_missing_file,
     int reverting_versioned_file, int installing_bad_symlink,
-    struct got_repository *repo,
+    int path_is_unversioned, struct got_repository *repo,
     got_worktree_checkout_cb progress_cb, void *progress_arg);
 
 /*
@@ -1232,7 +1278,8 @@ install_symlink(int *is_bad_symlink, struct got_worktr
 			return install_blob(worktree, ondisk_path, path,
 			    GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob,
 			    restoring_missing_file, reverting_versioned_file,
-			    1, repo, progress_cb, progress_arg);
+			    1, path_is_unversioned, repo, progress_cb,
+			    progress_arg);
 		}
 		if (len > 0) {
 			/* Skip blob object header first time around. */
@@ -1283,7 +1330,7 @@ install_symlink(int *is_bad_symlink, struct got_worktr
 		err = install_blob(worktree, ondisk_path, path,
 		    GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob,
 		    restoring_missing_file, reverting_versioned_file, 1,
-		    repo, progress_cb, progress_arg);
+		    path_is_unversioned, repo, progress_cb, progress_arg);
 		goto done;
 	}
 
@@ -1301,7 +1348,7 @@ install_symlink(int *is_bad_symlink, struct got_worktr
 		err = install_blob(worktree, ondisk_path, path,
 		    GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob,
 		    restoring_missing_file, reverting_versioned_file, 1,
-		    repo, progress_cb, progress_arg);
+		    path_is_unversioned, repo, progress_cb, progress_arg);
 		goto done;
 	}
 
@@ -1351,7 +1398,8 @@ install_symlink(int *is_bad_symlink, struct got_worktr
 			err = install_blob(worktree, ondisk_path, path,
 			    GOT_DEFAULT_FILE_MODE, GOT_DEFAULT_FILE_MODE, blob,
 			    restoring_missing_file, reverting_versioned_file, 1,
-			    repo, progress_cb, progress_arg);
+			    path_is_unversioned, repo,
+			    progress_cb, progress_arg);
 		} else if (errno == ENOTDIR) {
 			err = got_error_path(ondisk_path,
 			    GOT_ERR_FILE_OBSTRUCTED);
@@ -1373,8 +1421,8 @@ install_blob(struct got_worktree *worktree, const char
     const char *path, mode_t te_mode, mode_t st_mode,
     struct got_blob_object *blob, int restoring_missing_file,
     int reverting_versioned_file, int installing_bad_symlink,
-    struct got_repository *repo, got_worktree_checkout_cb progress_cb,
-    void *progress_arg)
+    int path_is_unversioned, struct got_repository *repo,
+    got_worktree_checkout_cb progress_cb, void *progress_arg)
 {
 	const struct got_error *err = NULL;
 	int fd = -1;
@@ -1399,6 +1447,11 @@ install_blob(struct got_worktree *worktree, const char
 				return got_error_from_errno2("open",
 				    ondisk_path);
 		} else if (errno == EEXIST) {
+			if (path_is_unversioned) {
+				err = (*progress_cb)(progress_arg,
+				    GOT_STATUS_UNVERSIONED, path);
+				goto done;
+			}
 			if (!S_ISREG(st_mode) && !installing_bad_symlink) {
 				/* TODO file is obstructed; do something */
 				err = got_error_path(ondisk_path,
@@ -1905,7 +1958,8 @@ update_blob(struct got_worktree *worktree,
 		} else {
 			err = install_blob(worktree, ondisk_path, path,
 			    te->mode, sb.st_mode, blob,
-			    status == GOT_STATUS_MISSING, 0, 0, repo,
+			    status == GOT_STATUS_MISSING, 0, 0,
+			    status == GOT_STATUS_UNVERSIONED, repo,
 			    progress_cb, progress_arg);
 		}
 		if (err)
@@ -2780,7 +2834,7 @@ merge_file_cb(void *arg, struct got_blob_object *blob1
 				    0, 1, repo, a->progress_cb, a->progress_arg);
 			} else {
 				err = install_blob(a->worktree, ondisk_path, path2,
-				    mode2, sb.st_mode, blob2, 0, 0, 0, repo,
+				    mode2, sb.st_mode, blob2, 0, 0, 0, 1, repo,
 				    a->progress_cb, a->progress_arg);
 			}
 			if (err)
@@ -4309,8 +4363,9 @@ revert_file(void *arg, unsigned char status, unsigned 
 				err = install_blob(a->worktree, ondisk_path,
 				    ie->path,
 				    te ? te->mode : GOT_DEFAULT_FILE_MODE,
-				    got_fileindex_perms_to_st(ie), blob, 0, 1, 0,
-				    a->repo, a->progress_cb, a->progress_arg);
+				    got_fileindex_perms_to_st(ie), blob,
+				    0, 1, 0, 0, a->repo,
+				    a->progress_cb, a->progress_arg);
 			}
 			if (err)
 				goto done;
blob - 3cf2adea08569d237c3ae2792cad85f134715215
blob + 4ed5d4635a5d84136d563642565378542947f5fc
--- regress/cmdline/checkout.sh
+++ regress/cmdline/checkout.sh
@@ -397,10 +397,10 @@ function test_checkout_into_nonempty_dir {
 		return 1
 	fi
 
-	echo "U  $testroot/wt/alpha" > $testroot/stdout.expected
-	echo "U  $testroot/wt/beta" >> $testroot/stdout.expected
-	echo "U  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
-	echo "U  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "?  $testroot/wt/alpha" > $testroot/stdout.expected
+	echo "?  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "?  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "?  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
 	echo "Now shut up and hack" >> $testroot/stdout.expected
 
 	got checkout -E $testroot/repo $testroot/wt > $testroot/stdout
blob - f9b695d8cdef6863a73038a1bdbbe643d583bb80
blob + 55663aa76dce7c64b71dc3d173d4daefb0d66870
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
@@ -495,6 +495,7 @@ function test_cherrypick_symlink_conflicts {
 	(cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link)
 	# added regular file A vs added bad symlink to file A
 	(cd $testroot/wt && ln -sf .got/bar dotgotfoo.link)
+	(cd $testroot/wt && got add dotgotfoo.link > /dev/null)
 	# added bad symlink to file A vs added regular file A
 	echo 'this is regular file bar' > $testroot/wt/dotgotbar.link
 	(cd $testroot/wt && got add dotgotbar.link > /dev/null)
@@ -520,12 +521,12 @@ function test_cherrypick_symlink_conflicts {
 	echo "?  boo.link" >> $testroot/stdout.expected
 	echo "C  epsilon.link" >> $testroot/stdout.expected
 	echo "C  dotgotbar.link" >> $testroot/stdout.expected
-	echo "U  dotgotfoo.link" >> $testroot/stdout.expected
+	echo "C  dotgotfoo.link" >> $testroot/stdout.expected
 	echo "D  nonexistent.link" >> $testroot/stdout.expected
 	echo "!  zeta.link" >> $testroot/stdout.expected
 	echo "C  new.link" >> $testroot/stdout.expected
 	echo "Merged commit $commit_id2" >> $testroot/stdout.expected
-	echo "Files with new merge conflicts: 5" >> $testroot/stdout.expected
+	echo "Files with new merge conflicts: 6" >> $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
@@ -668,7 +669,12 @@ EOF
 		return 1
 	fi
 
-	echo "this is regular file foo" > $testroot/content.expected
+	echo "<<<<<<< merged change: commit $commit_id2" \
+		> $testroot/content.expected
+	echo "this is regular file foo" >> $testroot/content.expected
+	echo "=======" >> $testroot/content.expected
+	echo -n ".got/bar" >> $testroot/content.expected
+	echo '>>>>>>>' >> $testroot/content.expected
 	cp $testroot/wt/dotgotfoo.link $testroot/content
 	cmp -s $testroot/content.expected $testroot/content
 	ret="$?"
blob - 2ee2f8f53e19a50823b280fff77f85ce353b0389
blob + 64b8fab61a492d7a1035d3082da449c5bb41d027
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -2016,8 +2016,10 @@ function test_update_symlink_conflicts {
 	(cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link)
 	# added regular file A vs added bad symlink to file A
 	(cd $testroot/wt && ln -sf .got/bar dotgotfoo.link)
+	(cd $testroot/wt && got add dotgotfoo.link > /dev/null)
 	# added bad symlink to file A vs added regular file A
 	echo 'this is regular file bar' > $testroot/wt/dotgotbar.link
+	(cd $testroot/wt && got add dotgotbar.link > /dev/null)
 	# removed symlink to non-existent file A vs modified symlink
 	# to nonexistent file B
 	(cd $testroot/wt && ln -sf nonexistent2 nonexistent.link)
@@ -2030,8 +2032,8 @@ function test_update_symlink_conflicts {
 	(cd $testroot/wt && got update > $testroot/stdout)
 
 	echo "C  alpha.link" >> $testroot/stdout.expected
-	echo "U  dotgotbar.link" >> $testroot/stdout.expected
-	echo "U  dotgotfoo.link" >> $testroot/stdout.expected
+	echo "C  dotgotbar.link" >> $testroot/stdout.expected
+	echo "C  dotgotfoo.link" >> $testroot/stdout.expected
 	echo "C  epsilon/beta.link" >> $testroot/stdout.expected
 	echo "C  epsilon.link" >> $testroot/stdout.expected
 	echo "C  new.link" >> $testroot/stdout.expected
@@ -2040,7 +2042,7 @@ function test_update_symlink_conflicts {
 	echo -n "Updated to commit " >> $testroot/stdout.expected
 	git_show_head $testroot/repo >> $testroot/stdout.expected
 	echo >> $testroot/stdout.expected
-	echo "Files with new merge conflicts: 5" >> $testroot/stdout.expected
+	echo "Files with new merge conflicts: 7" >> $testroot/stdout.expected
 
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
@@ -2186,7 +2188,14 @@ EOF
 		return 1
 	fi
 
-	echo "this is regular file foo" > $testroot/content.expected
+	echo "<<<<<<< merged change: commit $commit_id2" \
+		> $testroot/content.expected
+	echo "this is regular file foo" >> $testroot/content.expected
+	echo "=======" >> $testroot/content.expected
+	echo -n ".got/bar" >> $testroot/content.expected
+	echo '>>>>>>>' >> $testroot/content.expected
+	echo -n "" >> $testroot/content.expected
+
 	cp $testroot/wt/dotgotfoo.link $testroot/content
 	cmp -s $testroot/content.expected $testroot/content
 	ret="$?"
@@ -2201,7 +2210,14 @@ EOF
 		test_done "$testroot" "1"
 		return 1
 	fi
-	echo -n ".got/bar" > $testroot/content.expected
+	echo "<<<<<<< merged change: commit $commit_id2" \
+		> $testroot/content.expected
+	echo -n ".got/bar" >> $testroot/content.expected
+	echo "=======" >> $testroot/content.expected
+	echo "this is regular file bar" >> $testroot/content.expected
+	echo '>>>>>>>' >> $testroot/content.expected
+	echo -n "" >> $testroot/content.expected
+
 	cp $testroot/wt/dotgotbar.link $testroot/content
 	cmp -s $testroot/content.expected $testroot/content
 	ret="$?"