Commit ea7786be381853efad88beae518c19a391d13791

Stefan Sperling 2020-07-23T14:22:38

make 'got unstage' work with symlinks

diff --git a/lib/worktree.c b/lib/worktree.c
index cec70da..d94da1d 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1071,13 +1071,20 @@ merge_symlink(struct got_worktree *worktree,
 			goto done;
 		err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path);
 	} else if (have_local_change && have_incoming_change) {
-		err = install_symlink_conflict(deriv_target,
-		    deriv_base_commit_id, ancestor_target, label_orig,
-		    ondisk_target, ondisk_path);
-		if (err)
-			goto done;
-		err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT,
-		    path);
+		if (deriv_len == ondisk_len &&
+		    memcmp(deriv_target, ondisk_target, deriv_len) == 0) {
+			/* Both sides made the same change. */
+			err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE,
+			    path);
+		} else {
+			err = install_symlink_conflict(deriv_target,
+			    deriv_base_commit_id, ancestor_target, label_orig,
+			    ondisk_target, ondisk_path);
+			if (err)
+				goto done;
+			err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT,
+			    path);
+		}
 	}
 
 done:
@@ -4578,7 +4585,6 @@ collect_commitables(void *arg, unsigned char status,
 			ct->mode = S_IFLNK;
 			break;
 		default:
-			fprintf(stderr, "got: ie mode is 0x%x\n", ie->mode);
 			err = got_error_path(path, GOT_ERR_BAD_FILETYPE);
 			goto done;
 		}
@@ -7540,11 +7546,26 @@ unstage_path(void *arg, unsigned char status,
 		    staged_blob_id, 8192);
 		if (err)
 			break;
-		err = merge_blob(&local_changes_subsumed, a->worktree,
-		    blob_base, ondisk_path, relpath,
-		    got_fileindex_perms_to_st(ie), label_orig, blob_staged,
-		    commit_id ? commit_id : a->worktree->base_commit_id,
-		    a->repo, a->progress_cb, a->progress_arg);
+		switch (got_fileindex_entry_staged_filetype_get(ie)) {
+		case GOT_FILEIDX_MODE_BAD_SYMLINK:
+		case GOT_FILEIDX_MODE_REGULAR_FILE:
+			err = merge_blob(&local_changes_subsumed, a->worktree,
+			    blob_base, ondisk_path, relpath,
+			    got_fileindex_perms_to_st(ie), label_orig,
+			    blob_staged, commit_id ? commit_id :
+			    a->worktree->base_commit_id, a->repo,
+			    a->progress_cb, a->progress_arg);
+			break;
+		case GOT_FILEIDX_MODE_SYMLINK:
+			err = merge_symlink(a->worktree, blob_base, ondisk_path,
+			    relpath, got_fileindex_perms_to_st(ie), label_orig,
+			    blob_staged, a->worktree->base_commit_id, a->repo,
+			    a->progress_cb, a->progress_arg);
+			break;
+		default:
+			err = got_error_path(relpath, GOT_ERR_BAD_FILETYPE);
+			break;
+		}
 		if (err == NULL)
 			got_fileindex_entry_stage_set(ie,
 			    GOT_FILEIDX_STAGE_NONE);
diff --git a/regress/cmdline/unstage.sh b/regress/cmdline/unstage.sh
index ef2a91d..90b33b4 100755
--- a/regress/cmdline/unstage.sh
+++ b/regress/cmdline/unstage.sh
@@ -953,6 +953,188 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+function test_unstage_symlink {
+	local testroot=`test_init unstage_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 > /dev/null)
+
+	(cd $testroot/wt && got status > $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 unstage > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got unstage command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cat > $testroot/stdout.expected <<EOF
+G  alpha.link
+G  dotgotbar.link
+G  dotgotfoo.link
+G  epsilon/beta.link
+G  epsilon.link
+D  nonexistent.link
+G  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
+
+	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/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/epsilon/beta.link ]; then
+		echo "epsilon/beta.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	readlink $testroot/wt/epsilon/beta.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
+
+	if [ ! -f $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
+
+	# bad symlinks are allowed as-is for commit and stage/unstage
+	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 [ -e $testroot/wt/nonexistent.link ]; then
+		echo "nonexistent.link exists on disk"
+		test_done "$testroot" "1"
+		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_unstage_basic
 run_test test_unstage_unversioned
 run_test test_unstage_nonexistent
@@ -960,3 +1142,4 @@ run_test test_unstage_patch
 run_test test_unstage_patch_added
 run_test test_unstage_patch_removed
 run_test test_unstage_patch_quit
+run_test test_unstage_symlink