commit f35fa46a4c69eaeda5e106115e08b3cf8a0d3413 from: Stefan Sperling date: Thu Jul 23 14:21:27 2020 UTC add symlink support to 'got update' commit - 0ab20ee9ead0bcdb626ef9fb52d63f58e13082a5 commit + f35fa46a4c69eaeda5e106115e08b3cf8a0d3413 blob - 03438c0b820def82a8574d7f14c80d53cc19a1c7 blob + da56ac7df4e0636a6540fa7ee8fd9af961ec2f0a --- lib/worktree.c +++ lib/worktree.c @@ -1020,27 +1020,6 @@ install_symlink(struct got_worktree *worktree, const c } if (symlink(target_path, ondisk_path) == -1) { - if (errno == ENOENT) { - char *parent = dirname(ondisk_path); - if (parent == NULL) { - err = got_error_from_errno2("dirname", - ondisk_path); - goto done; - } - err = add_dir_on_disk(worktree, parent); - if (err) - goto done; - /* - * Retry, and fall through to error handling - * below if this second attempt fails. - */ - if (symlink(target_path, ondisk_path) != -1) { - err = NULL; /* success */ - goto done; - } - } - - /* Handle errors from first or second creation attempt. */ if (errno == EEXIST) { struct stat sb; ssize_t elen; @@ -1062,12 +1041,49 @@ install_symlink(struct got_worktree *worktree, const c goto done; } if (elen == target_len && - memcmp(etarget, target_path, target_len) == 0) - err = NULL; - else - err = got_error_path(ondisk_path, - GOT_ERR_FILE_OBSTRUCTED); - } else if (errno == ENAMETOOLONG) { + memcmp(etarget, target_path, target_len) == 0) { + err = NULL; /* nothing to do */ + goto done; + } else { + if (unlink(ondisk_path) == -1) { + err = got_error_from_errno2("unlink", + ondisk_path); + goto done; + } + if (symlink(target_path, ondisk_path) == -1) { + err = got_error_from_errno3("symlink", + target_path, ondisk_path); + goto done; + } + + err = (*progress_cb)(progress_arg, + GOT_STATUS_UPDATE, path); + goto done; + } + } + + if (errno == ENOENT) { + char *parent = dirname(ondisk_path); + if (parent == NULL) { + err = got_error_from_errno2("dirname", + ondisk_path); + goto done; + } + err = add_dir_on_disk(worktree, parent); + if (err) + goto done; + /* + * Retry, and fall through to error handling + * below if this second attempt fails. + */ + if (symlink(target_path, ondisk_path) != -1) { + err = NULL; /* success */ + goto done; + } + } + + /* Handle errors from first or second creation attempt. */ + if (errno == ENAMETOOLONG) { /* bad target path; install as a regular file */ got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, @@ -1081,7 +1097,8 @@ install_symlink(struct got_worktree *worktree, const c err = got_error_from_errno3("symlink", target_path, ondisk_path); } - } + } else + err = (*progress_cb)(progress_arg, GOT_STATUS_ADD, path); done: free(resolved_path); free(abspath); blob - 60a1a2ab40ff159845a71f43bd69ec3145dfa795 blob + b61d2a00448a591d108d2209b91d0e762aea07f2 --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -1824,8 +1824,115 @@ function test_update_conflict_wt_file_vs_repo_submodul fi test_done "$testroot" "$ret" } + +function test_update_adds_symlink { + local testroot=`test_init update_adds_symlink` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + echo "checkout failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + (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 && ln -s ../beta epsilon/beta.link) + (cd $testroot/repo && ln -s nonexistent nonexistent.link) + (cd $testroot/repo && git add .) + git_commit $testroot/repo -m "add symlinks" + + 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 -n "Updated to commit " >> $testroot/stdout.expected + git_show_head $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + + (cd $testroot/wt && got update > $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 + + if ! [ -h $testroot/wt/alpha.link ]; then + echo "alpha.link is not a symlink" + test_done "$testroot" "1" + return 1 + fi + + readlink $testroot/wt/alpha.link > $testroot/stdout + echo "alpha" > $testroot/stdout.expected + 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 + + if ! [ -h $testroot/wt/epsilon.link ]; then + echo "epsilon.link is not a symlink" + test_done "$testroot" "1" + return 1 + fi + readlink $testroot/wt/epsilon.link > $testroot/stdout + echo "epsilon" > $testroot/stdout.expected + 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 + if [ -h $testroot/wt/passwd.link ]; then + echo -n "passwd.link symlink points outside of work tree: " >&2 + readlink $testroot/wt/passwd.link >&2 + test_done "$testroot" "1" + return 1 + fi + + echo -n "/etc/passwd" > $testroot/content.expected + cp $testroot/wt/passwd.link $testroot/content + + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + readlink $testroot/wt/epsilon/beta.link > $testroot/stdout + echo "../beta" > $testroot/stdout.expected + 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 + + readlink $testroot/wt/nonexistent.link > $testroot/stdout + echo "nonexistent" > $testroot/stdout.expected + 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_update_basic run_test test_update_adds_file run_test test_update_deletes_file @@ -1861,3 +1968,4 @@ run_test test_update_preserves_conflicted_file run_test test_update_modified_submodules run_test test_update_adds_submodule run_test test_update_conflict_wt_file_vs_repo_submodule +run_test test_update_adds_symlink