Commit Diff


commit - 00bb5ea05eb54b4ec01ed195104765f7baf80169
commit + a919d5c4ebaba5cf7b48888745d8b554acf6fe3e
blob - 9014f142cbca2af04a79adbc2efa7223ad338695
blob + d893d9cfadfcf9aec8b015b5ba0dec0d5eaa887e
--- lib/worktree.c
+++ lib/worktree.c
@@ -1279,7 +1279,60 @@ get_staged_status(struct got_fileindex_entry *ie)
 		return GOT_STATUS_MODIFY;
 	default:
 		return GOT_STATUS_NO_CHANGE;
+	}
+}
+
+static const struct got_error *
+get_symlink_status(unsigned char *status, struct stat *sb,
+    struct got_fileindex_entry *ie, const char *abspath,
+    int dirfd, const char *de_name, struct got_blob_object *blob)
+{
+	const struct got_error *err = NULL;
+	char target_path[PATH_MAX];
+	char etarget[PATH_MAX];
+	ssize_t elen;
+	size_t len, target_len = 0;
+	const uint8_t *buf = got_object_blob_get_read_buf(blob);
+	size_t hdrlen = got_object_blob_get_hdrlen(blob);
+
+	*status = GOT_STATUS_NO_CHANGE;
+
+	/* Blob object content specifies the target path of the link. */
+	do {
+		err = got_object_blob_read_block(&len, blob);
+		if (err)
+			return err;
+		if (len + target_len >= sizeof(target_path)) {
+			/*
+			 * Should not happen. The blob contents were OK
+			 * when this symlink was installed.
+			 */
+			return got_error(GOT_ERR_NO_SPACE);
+		}
+		if (len > 0) {
+			/* Skip blob object header first time around. */
+			memcpy(target_path + target_len, buf + hdrlen,
+			    len - hdrlen);
+			target_len += len - hdrlen;
+			hdrlen = 0;
+		}
+	} while (len != 0);
+	target_path[target_len] = '\0';
+
+	if (dirfd != -1) {
+		elen = readlinkat(dirfd, de_name, etarget, sizeof(etarget));
+		if (elen == -1)
+			return got_error_from_errno2("readlinkat", abspath);
+	} else {
+		elen = readlink(abspath, etarget, sizeof(etarget));
+		if (elen == -1)
+			return got_error_from_errno2("readlinkat", abspath);
 	}
+
+	if (elen != target_len || memcmp(etarget, target_path, target_len) != 0)
+		*status = GOT_STATUS_MODIFY;
+
+	return NULL;
 }
 
 static const struct got_error *
@@ -1318,9 +1371,12 @@ get_file_status(unsigned char *status, struct stat *sb
 		}
 	} else {
 		fd = open(abspath, O_RDONLY | O_NOFOLLOW);
-		if (fd == -1 && errno != ENOENT)
+		if (fd == -1 && errno != ENOENT && errno != ELOOP)
 			return got_error_from_errno2("open", abspath);
-		if (fd == -1 || fstat(fd, sb) == -1) {
+		else if (fd == -1 && errno == ELOOP) {
+			if (lstat(abspath, sb) == -1)
+				return got_error_from_errno2("lstat", abspath);
+		} else if (fd == -1 || fstat(fd, sb) == -1) {
 			if (errno == ENOENT) {
 				if (got_fileindex_entry_has_file_on_disk(ie))
 					*status = GOT_STATUS_MISSING;
@@ -1360,6 +1416,18 @@ get_file_status(unsigned char *status, struct stat *sb
 	if (err)
 		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;
+	}
+
+
 	if (dirfd != -1) {
 		fd = openat(dirfd, de_name, O_RDONLY | O_NOFOLLOW);
 		if (fd == -1) {
blob - d96dfe3602ef3a7ee2a20198083c20e833f9567a
blob + 6a7dff907ff3ed86eb1cd3bffb039353bcb2d29b
--- regress/cmdline/rm.sh
+++ regress/cmdline/rm.sh
@@ -395,13 +395,46 @@ function test_rm_subtree {
 	echo "D  epsilon/foo/bar/baz/f.o" >> $testroot/stdout.expected
 
 	(cd $testroot/wt && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_rm_symlink {
+	local testroot=`test_init rm_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 && git add .)
+	git_commit $testroot/repo -m "add a symlink"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
 
+	echo 'D  alpha.link' > $testroot/stdout.expected
+	echo 'D  epsilon.link' >> $testroot/stdout.expected
+	echo 'D  passwd.link' >> $testroot/stdout.expected
+	(cd $testroot/wt && got rm alpha.link epsilon.link passwd.link > \
+	    $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
 	test_done "$testroot" "$ret"
+
 }
 
 run_test test_rm_basic
@@ -411,3 +444,4 @@ run_test test_rm_and_add_elsewhere
 run_test test_rm_directory
 run_test test_rm_directory_keep_files
 run_test test_rm_subtree
+run_test test_rm_symlink