commit fa3cef63799016195e8a917f39c82815522692aa from: Stefan Sperling date: Thu Jul 23 14:22:37 2020 UTC make 'got stage -p' work with symlinks commit - b448fd00850830156870881effc6cafd6233b8b6 commit + fa3cef63799016195e8a917f39c82815522692aa blob - fc7b322b0c224ff327dbe3d499fab10f2c8c7c96 blob + df57239f50ac1caa3457f59d6df054cab52a05bc --- lib/worktree.c +++ lib/worktree.c @@ -4067,6 +4067,8 @@ create_patched_content(char **path_outfile, int revers struct got_blob_object *blob = NULL; FILE *f1 = NULL, *f2 = NULL, *outfile = NULL; int fd2 = -1; + char link_target[PATH_MAX]; + ssize_t link_len = 0; char *path1 = NULL, *id_str = NULL; struct stat sb1, sb2; struct got_diff_changes *changes = NULL; @@ -4085,27 +4087,62 @@ create_patched_content(char **path_outfile, int revers if (dirfd2 != -1) { fd2 = openat(dirfd2, de_name2, O_RDONLY | O_NOFOLLOW); if (fd2 == -1) { - err = got_error_from_errno2("openat", path2); - goto done; + if (errno != ELOOP) { + err = got_error_from_errno2("openat", path2); + goto done; + } + link_len = readlinkat(dirfd2, de_name2, + link_target, sizeof(link_target)); + if (link_len == -1) + return got_error_from_errno2("readlinkat", path2); + sb2.st_mode = S_IFLNK; + sb2.st_size = link_len; } } else { fd2 = open(path2, O_RDONLY | O_NOFOLLOW); if (fd2 == -1) { - err = got_error_from_errno2("open", path2); - goto done; + if (errno != ELOOP) { + err = got_error_from_errno2("open", path2); + goto done; + } + link_len = readlink(path2, link_target, + sizeof(link_target)); + if (link_len == -1) + return got_error_from_errno2("readlink", path2); + sb2.st_mode = S_IFLNK; + sb2.st_size = link_len; } } - if (fstat(fd2, &sb2) == -1) { - err = got_error_from_errno2("fstat", path2); - goto done; - } + if (fd2 != -1) { + if (fstat(fd2, &sb2) == -1) { + err = got_error_from_errno2("fstat", path2); + goto done; + } - f2 = fdopen(fd2, "r"); - if (f2 == NULL) { - err = got_error_from_errno2("fdopen", path2); - goto done; + f2 = fdopen(fd2, "r"); + if (f2 == NULL) { + err = got_error_from_errno2("fdopen", path2); + goto done; + } + fd2 = -1; + } else { + size_t n; + f2 = got_opentemp(); + if (f2 == NULL) { + err = got_error_from_errno2("got_opentemp", path2); + goto done; + } + n = fwrite(link_target, 1, link_len, f2); + if (n != link_len) { + err = got_ferror(f2, GOT_ERR_IO); + goto done; + } + if (fflush(f2) == EOF) { + err = got_error_from_errno("fflush"); + goto done; + } + rewind(f2); } - fd2 = -1; err = got_object_open_as_blob(&blob, repo, blob_id, 8192); if (err) @@ -4159,9 +4196,11 @@ create_patched_content(char **path_outfile, int revers if (err) goto done; - if (chmod(*path_outfile, sb2.st_mode) == -1) { - err = got_error_from_errno2("chmod", path2); - goto done; + if (!S_ISLNK(sb2.st_mode)) { + if (chmod(*path_outfile, sb2.st_mode) == -1) { + err = got_error_from_errno2("chmod", path2); + goto done; + } } } done: blob - cf4a6d0c34ede0224a4b154ee7f65844ddf9f51a blob + 937cb0bffe198932b2348672a1f25bf74819b672 --- regress/cmdline/stage.sh +++ regress/cmdline/stage.sh @@ -2568,6 +2568,284 @@ EOF fi echo -n ".got/bar" > $testroot/content.expected cp $testroot/wt/dotgotbar.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 + + if [ -h $testroot/wt/dotgotfoo.link ]; then + echo "dotgotfoo.link is a symlink" + test_done "$testroot" "1" + return 1 + fi + echo "this is regular file foo" > $testroot/content.expected + cp $testroot/wt/dotgotfoo.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 + + 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 "gamma" > $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 "passwd.link is a symlink" + 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 + + if ! [ -h $testroot/wt/zeta.link ]; then + echo "zeta.link is not a symlink" + test_done "$testroot" "1" + return 1 + fi + + readlink $testroot/wt/zeta.link > $testroot/stdout + echo "gamma/delta" > $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 + + test_done "$testroot" "0" +} + +function test_stage_patch_symlink { + local testroot=`test_init stage_patch_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 && 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" + local head_commit=`git_show_head $testroot/repo` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && ln -sf beta alpha.link) + (cd $testroot/wt && ln -sfh gamma epsilon.link) + (cd $testroot/wt && ln -sf ../gamma/delta epsilon/beta.link) + echo 'this is regular file foo' > $testroot/wt/dotgotfoo.link + (cd $testroot/wt && got add dotgotfoo.link > /dev/null) + (cd $testroot/wt && ln -sf .got/bar dotgotbar.link) + (cd $testroot/wt && got add dotgotbar.link > /dev/null) + (cd $testroot/wt && got rm nonexistent.link > /dev/null) + (cd $testroot/wt && ln -sf gamma/delta zeta.link) + (cd $testroot/wt && got add zeta.link > /dev/null) + + printf "y\nn\ny\nn\ny\ny\ny" > $testroot/patchscript + (cd $testroot/wt && got stage -F $testroot/patchscript -p \ + > $testroot/stdout) + + cat > $testroot/stdout.expected < $testroot/wt/alpha.link + + (cd $testroot/wt && got diff -s > $testroot/stdout) + + echo "diff $head_commit $testroot/wt (staged changes)" \ + > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i | grep 'alpha.link@ -> alpha$' | \ + cut -d' ' -f 1 >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l alpha.link) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo '--- alpha.link' >> $testroot/stdout.expected + echo '+++ alpha.link' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-alpha' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $testroot/stdout.expected + echo '+beta' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $testroot/stdout.expected + echo 'blob - /dev/null' >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l dotgotfoo.link) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo '--- /dev/null' >> $testroot/stdout.expected + echo '+++ dotgotfoo.link' >> $testroot/stdout.expected + echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected + echo '+this is regular file foo' >> $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i | grep 'epsilon.link@ -> epsilon$' | \ + cut -d' ' -f 1 >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l epsilon.link) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo '--- epsilon.link' >> $testroot/stdout.expected + echo '+++ epsilon.link' >> $testroot/stdout.expected + echo '@@ -1 +1 @@' >> $testroot/stdout.expected + echo '-epsilon' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $testroot/stdout.expected + echo '+gamma' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i | grep 'nonexistent.link@ -> nonexistent$' | \ + cut -d' ' -f 1 >> $testroot/stdout.expected + echo 'blob + /dev/null' >> $testroot/stdout.expected + echo '--- nonexistent.link' >> $testroot/stdout.expected + echo '+++ /dev/null' >> $testroot/stdout.expected + echo '@@ -1 +0,0 @@' >> $testroot/stdout.expected + echo '-nonexistent' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $testroot/stdout.expected + echo 'blob - /dev/null' >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l zeta.link) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo '--- /dev/null' >> $testroot/stdout.expected + echo '+++ zeta.link' >> $testroot/stdout.expected + echo '@@ -0,0 +1 @@' >> $testroot/stdout.expected + echo '+gamma/delta' >> $testroot/stdout.expected + echo '\ No newline at end of file' >> $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 + + (cd $testroot/wt && got commit -m "staged symlink" \ + > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got commit command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + local commit_id=`git_show_head $testroot/repo` + echo "A dotgotfoo.link" > $testroot/stdout.expected + echo "A zeta.link" >> $testroot/stdout.expected + echo "M alpha.link" >> $testroot/stdout.expected + echo "M epsilon.link" >> $testroot/stdout.expected + echo "D nonexistent.link" >> $testroot/stdout.expected + echo "Created commit $commit_id" >> $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 + + got tree -r $testroot/repo -c $commit_id > $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + echo "got tree command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cat > $testroot/stdout.expected < beta +beta +dotgotfoo.link +epsilon/ +epsilon.link@ -> gamma +gamma/ +passwd.link@ -> /etc/passwd +zeta.link@ -> gamma/delta +EOF + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + return 1 + fi + + if [ -h $testroot/wt/alpha.link ]; then + echo "alpha.link is a symlink" + test_done "$testroot" "1" + return 1 + fi + + echo 'this is regular file alpha.link' > $testroot/content.expected + cp $testroot/wt/alpha.link $testroot/content cmp -s $testroot/content.expected $testroot/content ret="$?" if [ "$ret" != "0" ]; then @@ -2576,6 +2854,21 @@ EOF return 1 fi + if [ ! -h $testroot/wt/dotgotbar.link ]; then + echo "dotgotbar.link is not a symlink" + test_done "$testroot" "1" + return 1 + fi + readlink $testroot/wt/dotgotbar.link > $testroot/stdout + echo ".got/bar" > $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/dotgotfoo.link ]; then echo "dotgotfoo.link is a symlink" test_done "$testroot" "1" @@ -2669,3 +2962,4 @@ run_test test_stage_patch_removed_twice run_test test_stage_patch_quit run_test test_stage_patch_incomplete_script run_test test_stage_symlink +run_test test_stage_patch_symlink