make 'got rm' work on symlinks; test case written by tracey
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
diff --git a/lib/worktree.c b/lib/worktree.c
index 9014f14..d893d9c 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1283,6 +1283,59 @@ get_staged_status(struct got_fileindex_entry *ie)
}
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 *
get_file_status(unsigned char *status, struct stat *sb,
struct got_fileindex_entry *ie, const char *abspath,
int dirfd, const char *de_name, struct got_repository *repo)
@@ -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) {
diff --git a/regress/cmdline/rm.sh b/regress/cmdline/rm.sh
index d96dfe3..6a7dff9 100755
--- a/regress/cmdline/rm.sh
+++ b/regress/cmdline/rm.sh
@@ -404,6 +404,39 @@ function test_rm_subtree {
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
run_test test_rm_with_local_mods
run_test test_double_rm
@@ -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