make 'got add' work on symlinks and let 'got status' display them
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 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
diff --git a/lib/worktree.c b/lib/worktree.c
index da56ac7..9014f14 100644
--- a/lib/worktree.c
+++ b/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 *parent_path, int dirfd)
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 report_unchanged)
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 char *path,
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_worktree *worktree,
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;
+ }
}
}
diff --git a/regress/cmdline/add.sh b/regress/cmdline/add.sh
index 030ece4..789676b 100755
--- a/regress/cmdline/add.sh
+++ b/regress/cmdline/add.sh
@@ -296,6 +296,71 @@ function test_add_clashes_with_submodule {
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"
+}
+
run_test test_add_basic
run_test test_double_add
run_test test_add_multiple
@@ -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
diff --git a/regress/cmdline/status.sh b/regress/cmdline/status.sh
index bf27874..580563c 100755
--- a/regress/cmdline/status.sh
+++ b/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