Commit Diff


commit - b448fd00850830156870881effc6cafd6233b8b6
commit + fa3cef63799016195e8a917f39c82815522692aa
blob - fc7b322b0c224ff327dbe3d499fab10f2c8c7c96
blob + df57239f50ac1caa3457f59d6df054cab52a05bc
--- lib/worktree.c
+++ lib/worktree.c
@@ -4067,6 +4067,8 @@ create_patched_content(char **path_outfile, int revers
 	struct got_blob_object *blob = NULL;
 	FILE *f1 = NULL, *f2 = NULL, *outfile = NULL;
 	int fd2 = -1;
+	char link_target[PATH_MAX];
+	ssize_t link_len = 0;
 	char *path1 = NULL, *id_str = NULL;
 	struct stat sb1, sb2;
 	struct got_diff_changes *changes = NULL;
@@ -4085,27 +4087,62 @@ create_patched_content(char **path_outfile, int revers
 	if (dirfd2 != -1) {
 		fd2 = openat(dirfd2, de_name2, O_RDONLY | O_NOFOLLOW);
 		if (fd2 == -1) {
-			err = got_error_from_errno2("openat", path2);
-			goto done;
+			if (errno != ELOOP) {
+				err = got_error_from_errno2("openat", path2);
+				goto done;
+			}
+			link_len = readlinkat(dirfd2, de_name2,
+			    link_target, sizeof(link_target));
+			if (link_len == -1)
+				return got_error_from_errno2("readlinkat", path2);
+			sb2.st_mode = S_IFLNK;
+			sb2.st_size = link_len;
 		}
 	} else {
 		fd2 = open(path2, O_RDONLY | O_NOFOLLOW);
 		if (fd2 == -1) {
-			err = got_error_from_errno2("open", path2);
-			goto done;
+			if (errno != ELOOP) {
+				err = got_error_from_errno2("open", path2);
+				goto done;
+			}
+			link_len = readlink(path2, link_target,
+			    sizeof(link_target));
+			if (link_len == -1)
+				return got_error_from_errno2("readlink", path2);
+			sb2.st_mode = S_IFLNK;
+			sb2.st_size = link_len;
 		}
 	}
-	if (fstat(fd2, &sb2) == -1) {
-		err = got_error_from_errno2("fstat", path2);
-		goto done;
-	}
+	if (fd2 != -1) {
+		if (fstat(fd2, &sb2) == -1) {
+			err = got_error_from_errno2("fstat", path2);
+			goto done;
+		}
 
-	f2 = fdopen(fd2, "r");
-	if (f2 == NULL) {
-		err = got_error_from_errno2("fdopen", path2);
-		goto done;
+		f2 = fdopen(fd2, "r");
+		if (f2 == NULL) {
+			err = got_error_from_errno2("fdopen", path2);
+			goto done;
+		}
+		fd2 = -1;
+	} else {
+		size_t n;
+		f2 = got_opentemp();
+		if (f2 == NULL) {
+			err = got_error_from_errno2("got_opentemp", path2);
+			goto done;
+		}
+		n = fwrite(link_target, 1, link_len, f2);
+		if (n != link_len) {
+			err = got_ferror(f2, GOT_ERR_IO);
+			goto done;
+		}
+		if (fflush(f2) == EOF) {
+			err = got_error_from_errno("fflush");
+			goto done;
+		}
+		rewind(f2);
 	}
-	fd2 = -1;
 
 	err = got_object_open_as_blob(&blob, repo, blob_id, 8192);
 	if (err)
@@ -4159,9 +4196,11 @@ create_patched_content(char **path_outfile, int revers
 		if (err)
 			goto done;
 
-		if (chmod(*path_outfile, sb2.st_mode) == -1) {
-			err = got_error_from_errno2("chmod", path2);
-			goto done;
+		if (!S_ISLNK(sb2.st_mode)) {
+			if (chmod(*path_outfile, sb2.st_mode) == -1) {
+				err = got_error_from_errno2("chmod", path2);
+				goto done;
+			}
 		}
 	}
 done:
blob - cf4a6d0c34ede0224a4b154ee7f65844ddf9f51a
blob + 937cb0bffe198932b2348672a1f25bf74819b672
--- regress/cmdline/stage.sh
+++ regress/cmdline/stage.sh
@@ -2568,6 +2568,284 @@ EOF
 	fi
 	echo -n ".got/bar" > $testroot/content.expected
 	cp $testroot/wt/dotgotbar.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
+
+	if [ -h $testroot/wt/dotgotfoo.link ]; then
+		echo "dotgotfoo.link is a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+	echo "this is regular file foo" > $testroot/content.expected
+	cp $testroot/wt/dotgotfoo.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
+
+	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 "gamma" > $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 "passwd.link is a symlink"
+		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
+
+	if ! [ -h $testroot/wt/zeta.link ]; then
+		echo "zeta.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	readlink $testroot/wt/zeta.link > $testroot/stdout
+	echo "gamma/delta" > $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
+
+	test_done "$testroot" "0"
+}
+
+function test_stage_patch_symlink {
+	local testroot=`test_init stage_patch_symlink`
+
+	(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"
+	local head_commit=`git_show_head $testroot/repo`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/wt && ln -sf beta alpha.link)
+	(cd $testroot/wt && ln -sfh gamma epsilon.link)
+	(cd $testroot/wt && ln -sf ../gamma/delta epsilon/beta.link)
+	echo 'this is regular file foo' > $testroot/wt/dotgotfoo.link
+	(cd $testroot/wt && got add dotgotfoo.link > /dev/null)
+	(cd $testroot/wt && ln -sf .got/bar dotgotbar.link)
+	(cd $testroot/wt && got add dotgotbar.link > /dev/null)
+	(cd $testroot/wt && got rm nonexistent.link > /dev/null)
+	(cd $testroot/wt && ln -sf gamma/delta zeta.link)
+	(cd $testroot/wt && got add zeta.link > /dev/null)
+
+	printf "y\nn\ny\nn\ny\ny\ny" > $testroot/patchscript
+	(cd $testroot/wt && got stage -F $testroot/patchscript -p \
+		> $testroot/stdout)
+
+	cat > $testroot/stdout.expected <<EOF
+-----------------------------------------------
+@@ -1 +1 @@
+-alpha
+\ No newline at end of file
++beta
+\ No newline at end of file
+-----------------------------------------------
+M  alpha.link (change 1 of 1)
+stage this change? [y/n/q] y
+A  dotgotbar.link
+stage this addition? [y/n] n
+A  dotgotfoo.link
+stage this addition? [y/n] y
+-----------------------------------------------
+@@ -1 +1 @@
+-../beta
+\ No newline at end of file
++../gamma/delta
+\ No newline at end of file
+-----------------------------------------------
+M  epsilon/beta.link (change 1 of 1)
+stage this change? [y/n/q] n
+-----------------------------------------------
+@@ -1 +1 @@
+-epsilon
+\ No newline at end of file
++gamma
+\ No newline at end of file
+-----------------------------------------------
+M  epsilon.link (change 1 of 1)
+stage this change? [y/n/q] y
+D  nonexistent.link
+stage this deletion? [y/n] y
+A  zeta.link
+stage this addition? [y/n] y
+EOF
+	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
+
+	rm $testroot/wt/alpha.link
+	echo 'this is regular file alpha.link' > $testroot/wt/alpha.link
+
+	(cd $testroot/wt && got diff -s > $testroot/stdout)
+
+	echo "diff $head_commit $testroot/wt (staged changes)" \
+		> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'alpha.link@ -> alpha$' | \
+		cut -d' ' -f 1 >> $testroot/stdout.expected
+	echo -n 'blob + ' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage -l alpha.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- alpha.link' >> $testroot/stdout.expected
+	echo '+++ alpha.link' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-alpha' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo '+beta' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo -n 'blob + ' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage -l dotgotfoo.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ dotgotfoo.link' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+this is regular file foo' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'epsilon.link@ -> epsilon$' | \
+		cut -d' ' -f 1 >> $testroot/stdout.expected
+	echo -n 'blob + ' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage -l epsilon.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- epsilon.link' >> $testroot/stdout.expected
+	echo '+++ epsilon.link' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-epsilon' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo '+gamma' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo -n 'blob - ' >> $testroot/stdout.expected
+	got tree -r $testroot/repo -i | grep 'nonexistent.link@ -> nonexistent$' | \
+		cut -d' ' -f 1 >> $testroot/stdout.expected
+	echo 'blob + /dev/null' >> $testroot/stdout.expected
+	echo '--- nonexistent.link' >> $testroot/stdout.expected
+	echo '+++ /dev/null' >> $testroot/stdout.expected
+	echo '@@ -1 +0,0 @@' >> $testroot/stdout.expected
+	echo '-nonexistent' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo 'blob - /dev/null' >> $testroot/stdout.expected
+	echo -n 'blob + ' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage -l zeta.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ zeta.link' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+gamma/delta' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $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
+
+	(cd $testroot/wt && got commit -m "staged symlink" \
+		> $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got commit command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	local commit_id=`git_show_head $testroot/repo`
+	echo "A  dotgotfoo.link" > $testroot/stdout.expected
+	echo "A  zeta.link" >> $testroot/stdout.expected
+	echo "M  alpha.link" >> $testroot/stdout.expected
+	echo "M  epsilon.link" >> $testroot/stdout.expected
+	echo "D  nonexistent.link" >> $testroot/stdout.expected
+	echo "Created commit $commit_id" >> $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
+
+	got tree -r $testroot/repo -c $commit_id > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got tree command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cat > $testroot/stdout.expected <<EOF
+alpha
+alpha.link@ -> beta
+beta
+dotgotfoo.link
+epsilon/
+epsilon.link@ -> gamma
+gamma/
+passwd.link@ -> /etc/passwd
+zeta.link@ -> gamma/delta
+EOF
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		return 1
+	fi
+
+	if [ -h $testroot/wt/alpha.link ]; then
+		echo "alpha.link is a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	echo 'this is regular file alpha.link' > $testroot/content.expected
+	cp $testroot/wt/alpha.link $testroot/content
 	cmp -s $testroot/content.expected $testroot/content
 	ret="$?"
 	if [ "$ret" != "0" ]; then
@@ -2576,6 +2854,21 @@ EOF
 		return 1
 	fi
 
+	if [ ! -h $testroot/wt/dotgotbar.link ]; then
+		echo "dotgotbar.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+	readlink $testroot/wt/dotgotbar.link > $testroot/stdout
+	echo ".got/bar" > $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/dotgotfoo.link ]; then
 		echo "dotgotfoo.link is a symlink"
 		test_done "$testroot" "1"
@@ -2669,3 +2962,4 @@ run_test test_stage_patch_removed_twice
 run_test test_stage_patch_quit
 run_test test_stage_patch_incomplete_script
 run_test test_stage_symlink
+run_test test_stage_patch_symlink