Commit Diff


commit - cf34e6e70511929983a7a035e946966e8a833b8f
commit + c631b1152565e4c18cb2123e1d3d7c08d513c02f
blob - 315fadb7928641092f567960ffa58125ab66b04a
blob + bde4162e571552b42ca8aea844c0fb7d81ee59e3
--- lib/worktree.c
+++ lib/worktree.c
@@ -1736,11 +1736,6 @@ get_file_status(unsigned char *status, struct stat *sb
 		goto done;
 
 	if (S_ISLNK(sb->st_mode)) {
-		/* Staging changes to symlinks is not yet(?) supported. */
-		if (staged_status != GOT_STATUS_NO_CHANGE) {
-			err = got_error_path(abspath, GOT_ERR_FILE_STATUS);
-			goto done;
-		}
 		err = get_symlink_status(status, sb, ie, abspath, dirfd,
 		    de_name, blob);
 		goto done;
@@ -5041,9 +5036,18 @@ reinstall_symlink_after_commit(int *is_bad_symlink, st
 	struct got_tree_entry *te;
 	char *entry_name;
 
-	err = got_object_open_as_blob(&blob, repo, ct->blob_id, PATH_MAX);
-	if (err)
-		return err;
+	if (ct->staged_status == GOT_STATUS_ADD ||
+	    ct->staged_status == GOT_STATUS_MODIFY) {
+		err = got_object_open_as_blob(&blob, repo, ct->staged_blob_id,
+		    PATH_MAX);
+		if (err)
+			return err;
+	} else {
+		err = got_object_open_as_blob(&blob, repo, ct->blob_id,
+		    PATH_MAX);
+		if (err)
+			return err;
+	}
 
 	err = got_path_dirname(&tree_path, ct->in_repo_path);
 	if (err) {
@@ -5108,7 +5112,9 @@ reinstall_symlinks_after_commit(struct got_pathlist_he
 		struct got_fileindex_entry *ie;
 		int is_bad_symlink = 0;
 	
-		if (!S_ISLNK(get_ct_file_mode(ct)))
+		if (!S_ISLNK(get_ct_file_mode(ct)) ||
+		    ct->staged_status == GOT_STATUS_DELETE ||
+		    ct->status == GOT_STATUS_DELETE)
 			continue;
 
 		err = reinstall_symlink_after_commit(&is_bad_symlink,
blob - a537bcbfccf7f2f23f1ffd8cf0407deef0991edd
blob + 199eaf6bbf1859c535e340a5c5f30777378ba090
--- regress/cmdline/stage.sh
+++ regress/cmdline/stage.sh
@@ -2350,7 +2350,292 @@ EOF
 		diff -u $testroot/stdout.expected $testroot/stdout
 	fi
 	test_done "$testroot" "$ret"
+
+}
+
+function test_stage_symlink {
+	local testroot=`test_init stage_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)
+
+	(cd $testroot/wt && got stage > $testroot/stdout)
+
+	cat > $testroot/stdout.expected <<EOF
+ M alpha.link
+ A dotgotbar.link
+ A dotgotfoo.link
+ M epsilon/beta.link
+ M epsilon.link
+ D nonexistent.link
+ A zeta.link
+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
+
+	(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 dotgotbar.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- /dev/null' >> $testroot/stdout.expected
+	echo '+++ dotgotbar.link' >> $testroot/stdout.expected
+	echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected
+	echo '+.got/bar' >> $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 epsilon | grep 'beta.link@ -> ../beta$' | \
+		cut -d' ' -f 1 >> $testroot/stdout.expected
+	echo -n 'blob + ' >> $testroot/stdout.expected
+	(cd $testroot/wt && got stage -l epsilon/beta.link) | cut -d' ' -f 1 \
+		>> $testroot/stdout.expected
+	echo '--- epsilon/beta.link' >> $testroot/stdout.expected
+	echo '+++ epsilon/beta.link' >> $testroot/stdout.expected
+	echo '@@ -1 +1 @@' >> $testroot/stdout.expected
+	echo '-../beta' >> $testroot/stdout.expected
+	echo '\ No newline at end of file' >> $testroot/stdout.expected
+	echo '+../gamma/delta' >> $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 '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  dotgotbar.link" > $testroot/stdout.expected
+	echo "A  dotgotfoo.link" >> $testroot/stdout.expected
+	echo "A  zeta.link" >> $testroot/stdout.expected
+	echo "M  alpha.link" >> $testroot/stdout.expected
+	echo "M  epsilon/beta.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
+dotgotbar.link@ -> .got/bar
+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 not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	readlink $testroot/wt/alpha.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
+
+	if [ -h $testroot/wt/dotgotbar.link ]; then
+		echo "dotgotbar.link is a symlink"
+		test_done "$testroot" "1"
+		return 1
+	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"
 }
 
 run_test test_stage_basic
@@ -2380,3 +2665,4 @@ run_test test_stage_patch_removed
 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