commit a919d5c4ebaba5cf7b48888745d8b554acf6fe3e from: Stefan Sperling date: Thu Jul 23 14:21:27 2020 UTC make 'got rm' work on symlinks; test case written by tracey 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