Commit Diff


commit - 0ab20ee9ead0bcdb626ef9fb52d63f58e13082a5
commit + f35fa46a4c69eaeda5e106115e08b3cf8a0d3413
blob - 03438c0b820def82a8574d7f14c80d53cc19a1c7
blob + da56ac7df4e0636a6540fa7ee8fd9af961ec2f0a
--- lib/worktree.c
+++ lib/worktree.c
@@ -1020,27 +1020,6 @@ install_symlink(struct got_worktree *worktree, const c
 	}
 
 	if (symlink(target_path, ondisk_path) == -1) {
-		if (errno == ENOENT) {
-			char *parent = dirname(ondisk_path);
-			if (parent == NULL) {
-				err = got_error_from_errno2("dirname",
-				    ondisk_path);
-				goto done;
-			}
-			err = add_dir_on_disk(worktree, parent);
-			if (err)
-				goto done;
-			/*
-			 * Retry, and fall through to error handling
-			 * below if this second attempt fails.
-			 */
-			if (symlink(target_path, ondisk_path) != -1) {
-				err = NULL; /* success */
-				goto done;
-			}
-		}
-
-		/* Handle errors from first or second creation attempt. */
 		if (errno == EEXIST) {
 			struct stat sb;
 			ssize_t elen;
@@ -1062,12 +1041,49 @@ install_symlink(struct got_worktree *worktree, const c
 				goto done;
 			}
 			if (elen == target_len &&
-			    memcmp(etarget, target_path, target_len) == 0)
-				err = NULL;
-			else
-				err = got_error_path(ondisk_path,
-				    GOT_ERR_FILE_OBSTRUCTED);
-		} else if (errno == ENAMETOOLONG) {
+			    memcmp(etarget, target_path, target_len) == 0) {
+				err = NULL; /* nothing to do */
+				goto done;
+			} else {
+				if (unlink(ondisk_path) == -1) {
+					err = got_error_from_errno2("unlink",
+					    ondisk_path);
+					goto done;
+				}
+				if (symlink(target_path, ondisk_path) == -1) {
+					err = got_error_from_errno3("symlink",
+					    target_path, ondisk_path);
+					goto done;
+				}
+
+				err = (*progress_cb)(progress_arg,
+				    GOT_STATUS_UPDATE, path);
+				goto done;
+			}
+		}
+
+		if (errno == ENOENT) {
+			char *parent = dirname(ondisk_path);
+			if (parent == NULL) {
+				err = got_error_from_errno2("dirname",
+				    ondisk_path);
+				goto done;
+			}
+			err = add_dir_on_disk(worktree, parent);
+			if (err)
+				goto done;
+			/*
+			 * Retry, and fall through to error handling
+			 * below if this second attempt fails.
+			 */
+			if (symlink(target_path, ondisk_path) != -1) {
+				err = NULL; /* success */
+				goto done;
+			}
+		}
+
+		/* Handle errors from first or second creation attempt. */
+		if (errno == ENAMETOOLONG) {
 			/* bad target path; install as a regular file */
 			got_object_blob_rewind(blob);
 			err = install_blob(worktree, ondisk_path, path,
@@ -1081,7 +1097,8 @@ install_symlink(struct got_worktree *worktree, const c
 			err = got_error_from_errno3("symlink",
 			    target_path, ondisk_path);
 		}
-	}
+	} else
+		err = (*progress_cb)(progress_arg, GOT_STATUS_ADD, path);
 done:
 	free(resolved_path);
 	free(abspath);
blob - 60a1a2ab40ff159845a71f43bd69ec3145dfa795
blob + b61d2a00448a591d108d2209b91d0e762aea07f2
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -1824,8 +1824,115 @@ function test_update_conflict_wt_file_vs_repo_submodul
 	fi
 	test_done "$testroot" "$ret"
 }
+
+function test_update_adds_symlink {
+	local testroot=`test_init update_adds_symlink`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "checkout failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/repo && ln -s alpha alpha.link)
+	(cd $testroot/repo && ln -s epsilon epsilon.link)
+	(cd $testroot/repo && ln -s /etc/passwd passwd.link)
+	(cd $testroot/repo && ln -s ../beta epsilon/beta.link)
+	(cd $testroot/repo && ln -s nonexistent nonexistent.link)
+	(cd $testroot/repo && git add .)
+	git_commit $testroot/repo -m "add symlinks"
+
+	echo "A  alpha.link" > $testroot/stdout.expected
+	echo "A  epsilon/beta.link" >> $testroot/stdout.expected
+	echo "A  epsilon.link" >> $testroot/stdout.expected
+	echo "A  nonexistent.link" >> $testroot/stdout.expected
+	echo "A  passwd.link" >> $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 -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	if ! [ -h $testroot/wt/alpha.link ]; then
+		echo "alpha.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	readlink $testroot/wt/alpha.link > $testroot/stdout
+	echo "alpha" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	if ! [ -h $testroot/wt/epsilon.link ]; then
+		echo "epsilon.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
 
+	readlink $testroot/wt/epsilon.link > $testroot/stdout
+	echo "epsilon" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
 
+	if [ -h $testroot/wt/passwd.link ]; then
+		echo -n "passwd.link symlink points outside of work tree: " >&2
+		readlink $testroot/wt/passwd.link >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo -n "/etc/passwd" > $testroot/content.expected
+	cp $testroot/wt/passwd.link $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	readlink $testroot/wt/epsilon/beta.link > $testroot/stdout
+	echo "../beta" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	readlink $testroot/wt/nonexistent.link > $testroot/stdout
+	echo "nonexistent" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_update_basic
 run_test test_update_adds_file
 run_test test_update_deletes_file
@@ -1861,3 +1968,4 @@ run_test test_update_preserves_conflicted_file
 run_test test_update_modified_submodules
 run_test test_update_adds_submodule
 run_test test_update_conflict_wt_file_vs_repo_submodule
+run_test test_update_adds_symlink