commit 00bb5ea05eb54b4ec01ed195104765f7baf80169 from: Stefan Sperling date: Thu Jul 23 14:21:27 2020 UTC make 'got add' work on symlinks and let 'got status' display them commit - f35fa46a4c69eaeda5e106115e08b3cf8a0d3413 commit + 00bb5ea05eb54b4ec01ed195104765f7baf80169 blob - da56ac7df4e0636a6540fa7ee8fd9af961ec2f0a blob + 9014f142cbca2af04a79adbc2efa7223ad338695 --- lib/worktree.c +++ lib/worktree.c @@ -1333,7 +1333,7 @@ get_file_status(unsigned char *status, struct stat *sb } } - if (!S_ISREG(sb->st_mode)) { + if (!S_ISREG(sb->st_mode) && !S_ISLNK(sb->st_mode)) { *status = GOT_STATUS_OBSTRUCTED; goto done; } @@ -2850,10 +2850,6 @@ status_new(void *arg, struct dirent *de, const char *p if (a->cancel_cb && a->cancel_cb(a->cancel_arg)) return got_error(GOT_ERR_CANCELLED); - /* XXX ignore symlinks for now */ - if (de->d_type == DT_LNK) - return NULL; - if (parent_path[0]) { if (asprintf(&path, "%s/%s", parent_path, de->d_name) == -1) return got_error_from_errno("asprintf"); @@ -2912,7 +2908,7 @@ void *status_arg, struct got_repository *repo, int rep return NULL; } - if (S_ISREG(sb.st_mode)) + if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)) return (*status_cb)(status_arg, GOT_STATUS_UNVERSIONED, GOT_STATUS_NO_CHANGE, path, NULL, NULL, NULL, -1, NULL); @@ -2988,7 +2984,8 @@ worktree_status(struct got_worktree *worktree, const c fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW | O_DIRECTORY); if (fd == -1) { - if (errno != ENOTDIR && errno != ENOENT && errno != EACCES) + if (errno != ENOTDIR && errno != ENOENT && errno != EACCES && + errno != ELOOP) err = got_error_from_errno2("open", ondisk_path); else err = report_single_file_status(path, ondisk_path, @@ -3058,21 +3055,58 @@ got_worktree_resolve_path(char **wt_path, struct got_w const char *arg) { const struct got_error *err = NULL; - char *resolved, *cwd = NULL, *path = NULL; + char *resolved = NULL, *cwd = NULL, *path = NULL; size_t len; + struct stat sb; *wt_path = NULL; - resolved = realpath(arg, NULL); - if (resolved == NULL) { - if (errno != ENOENT) - return got_error_from_errno2("realpath", arg); - cwd = getcwd(NULL, 0); - if (cwd == NULL) - return got_error_from_errno("getcwd"); - if (asprintf(&resolved, "%s/%s", cwd, arg) == -1) { - err = got_error_from_errno("asprintf"); + cwd = getcwd(NULL, 0); + if (cwd == NULL) + return got_error_from_errno("getcwd"); + + if (lstat(arg, &sb) == -1) { + if (errno != ENOENT) { + err = got_error_from_errno2("lstat", arg); + goto done; + } + } + if (S_ISLNK(sb.st_mode)) { + /* + * We cannot use realpath(3) with symlinks since we want to + * operate on the symlink itself. + * But we can make the path absolute, assuming it is relative + * to the current working directory, and then canonicalize it. + */ + char *abspath = NULL; + char canonpath[PATH_MAX]; + if (!got_path_is_absolute(arg)) { + if (asprintf(&abspath, "%s/%s", cwd, arg) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } + + } + err = got_canonpath(abspath ? abspath : arg, canonpath, + sizeof(canonpath)); + if (err) goto done; + resolved = strdup(canonpath); + if (resolved == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + } else { + resolved = realpath(arg, NULL); + if (resolved == NULL) { + if (errno != ENOENT) { + err = got_error_from_errno2("realpath", arg); + goto done; + } + if (asprintf(&resolved, "%s/%s", cwd, arg) == -1) { + err = got_error_from_errno("asprintf"); + goto done; + } } } blob - 030ece4e122124a89fa7523bf23c419719dcb231 blob + 789676b33649267773f4b3b3f09800cc68d7bb1b --- regress/cmdline/add.sh +++ regress/cmdline/add.sh @@ -292,7 +292,72 @@ function test_add_clashes_with_submodule { ret="$?" if [ "$ret" != "0" ]; then diff -u $testroot/stderr.expected $testroot/stderr + fi + test_done "$testroot" "$ret" +} + +function test_add_symlink { + local testroot=`test_init add_symlink` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && ln -s alpha alpha.link) + (cd $testroot/wt && ln -s epsilon epsilon.link) + (cd $testroot/wt && ln -s /etc/passwd passwd.link) + (cd $testroot/wt && ln -s ../beta epsilon/beta.link) + (cd $testroot/wt && ln -s nonexistent nonexistent.link) + + echo "A alpha.link" > $testroot/stdout.expected + (cd $testroot/wt && got add alpha.link > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "A epsilon.link" > $testroot/stdout.expected + (cd $testroot/wt && got add epsilon.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 + + echo "A passwd.link" > $testroot/stdout.expected + (cd $testroot/wt && got add 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 + + echo "A epsilon/beta.link" > $testroot/stdout.expected + (cd $testroot/wt && got add epsilon/beta.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 + + echo "A nonexistent.link" > $testroot/stdout.expected + (cd $testroot/wt && got add nonexistent.link > $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" } @@ -303,3 +368,4 @@ run_test test_add_file_in_new_subdir run_test test_add_deleted run_test test_add_directory run_test test_add_clashes_with_submodule +run_test test_add_symlink blob - bf27874f97a3459375523f37a2f63d9a83168936 blob + 580563c3ab5e905a482db9ff295dfb1eaaa2123e --- regress/cmdline/status.sh +++ regress/cmdline/status.sh @@ -258,9 +258,8 @@ function test_status_unversioned_subdirs { test_done "$testroot" "$ret" } -# 'got status' ignores symlinks at present; this might change eventually -function test_status_ignores_symlink { - local testroot=`test_init status_ignores_symlink 1` +function test_status_symlink { + local testroot=`test_init status_symlink` mkdir $testroot/repo/ramdisk/ touch $testroot/repo/ramdisk/Makefile @@ -276,7 +275,32 @@ function test_status_ignores_symlink { ln -s /usr/obj/distrib/i386/ramdisk $testroot/wt/ramdisk/obj - echo -n > $testroot/stdout.expected + echo "? ramdisk/obj" > $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 + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && ln -s alpha alpha.link) + (cd $testroot/wt && ln -s epsilon epsilon.link) + (cd $testroot/wt && ln -s /etc/passwd passwd.link) + (cd $testroot/wt && ln -s ../beta epsilon/beta.link) + (cd $testroot/wt && ln -s nonexistent nonexistent.link) + (cd $testroot/wt && got add alpha.link epsilon.link \ + passwd.link epsilon/beta.link nonexistent.link > /dev/null) + + echo 'A alpha.link' > $testroot/stdout.expected + echo 'A epsilon/beta.link' >> $testroot/stdout.expected + echo 'A epsilon.link' >> $testroot/stdout.expected + echo 'A nonexistent.link' >> $testroot/stdout.expected + echo 'A passwd.link' >> $testroot/stdout.expected + echo "? ramdisk/obj" >> $testroot/stdout.expected (cd $testroot/wt && got status > $testroot/stdout) @@ -612,7 +636,7 @@ run_test test_status_subdir_no_mods2 run_test test_status_obstructed run_test test_status_shows_local_mods_after_update run_test test_status_unversioned_subdirs -run_test test_status_ignores_symlink +run_test test_status_symlink run_test test_status_shows_no_mods_after_complete_merge run_test test_status_shows_conflict run_test test_status_empty_dir