commit bd6aa3590530fb1684f2ca8ded49f431bf0e9a18 from: Stefan Sperling date: Thu Jul 23 14:21:30 2020 UTC convert bad symlinks to regular files during 'got commit' commit - 2f63b34ce3d2db78052d19284337414845664b6f commit + bd6aa3590530fb1684f2ca8ded49f431bf0e9a18 blob - 4f2a83243d33c26ba5cf655c2b3e82a61f8d5517 blob + 8d06a93d5428544c51f0757dc50b19a388cb67d8 --- lib/worktree.c +++ lib/worktree.c @@ -1025,7 +1025,8 @@ static const struct got_error * install_blob(struct got_worktree *worktree, const char *ondisk_path, const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, - int reverting_versioned_file, struct got_repository *repo, + int reverting_versioned_file, int installing_bad_symlink, + struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg); static const struct got_error * @@ -1057,7 +1058,7 @@ install_symlink(struct got_worktree *worktree, const c return install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, st_mode, blob, restoring_missing_file, reverting_versioned_file, - repo, progress_cb, progress_arg); + 1, repo, progress_cb, progress_arg); } if (len > 0) { /* Skip blob object header first time around. */ @@ -1106,7 +1107,7 @@ install_symlink(struct got_worktree *worktree, const c got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, st_mode, blob, - restoring_missing_file, reverting_versioned_file, + restoring_missing_file, reverting_versioned_file, 1, repo, progress_cb, progress_arg); goto done; } @@ -1123,7 +1124,7 @@ install_symlink(struct got_worktree *worktree, const c got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, st_mode, blob, - restoring_missing_file, reverting_versioned_file, + restoring_missing_file, reverting_versioned_file, 1, repo, progress_cb, progress_arg); goto done; } @@ -1158,8 +1159,10 @@ install_symlink(struct got_worktree *worktree, const c target_len); if (err) goto done; - err = (*progress_cb)(progress_arg, - GOT_STATUS_UPDATE, path); + if (progress_cb) { + err = (*progress_cb)(progress_arg, + GOT_STATUS_UPDATE, path); + } goto done; } } @@ -1190,7 +1193,7 @@ install_symlink(struct got_worktree *worktree, const c got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, GOT_DEFAULT_FILE_MODE, st_mode, blob, - restoring_missing_file, reverting_versioned_file, + restoring_missing_file, reverting_versioned_file, 1, repo, progress_cb, progress_arg); } else if (errno == ENOTDIR) { err = got_error_path(ondisk_path, @@ -1199,7 +1202,7 @@ install_symlink(struct got_worktree *worktree, const c err = got_error_from_errno3("symlink", target_path, ondisk_path); } - } else + } else if (progress_cb) err = (*progress_cb)(progress_arg, GOT_STATUS_ADD, path); done: free(resolved_path); @@ -1212,8 +1215,9 @@ static const struct got_error * install_blob(struct got_worktree *worktree, const char *ondisk_path, const char *path, mode_t te_mode, mode_t st_mode, struct got_blob_object *blob, int restoring_missing_file, - int reverting_versioned_file, struct got_repository *repo, - got_worktree_checkout_cb progress_cb, void *progress_arg) + int reverting_versioned_file, int installing_bad_symlink, + struct got_repository *repo, got_worktree_checkout_cb progress_cb, + void *progress_arg) { const struct got_error *err = NULL; int fd = -1; @@ -1243,7 +1247,7 @@ install_blob(struct got_worktree *worktree, const char return got_error_from_errno2("open", ondisk_path); } else if (errno == EEXIST) { - if (!S_ISREG(st_mode)) { + if (!S_ISREG(st_mode) && !installing_bad_symlink) { /* TODO file is obstructed; do something */ err = got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED); @@ -1259,15 +1263,19 @@ install_blob(struct got_worktree *worktree, const char return got_error_from_errno2("open", ondisk_path); } - if (restoring_missing_file) - err = (*progress_cb)(progress_arg, GOT_STATUS_MISSING, path); - else if (reverting_versioned_file) - err = (*progress_cb)(progress_arg, GOT_STATUS_REVERT, path); - else - err = (*progress_cb)(progress_arg, - update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path); - if (err) - goto done; + if (progress_cb) { + if (restoring_missing_file) + err = (*progress_cb)(progress_arg, GOT_STATUS_MISSING, + path); + else if (reverting_versioned_file) + err = (*progress_cb)(progress_arg, GOT_STATUS_REVERT, + path); + else + err = (*progress_cb)(progress_arg, + update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path); + if (err) + goto done; + } hdrlen = got_object_blob_get_hdrlen(blob); do { @@ -1727,7 +1735,7 @@ update_blob(struct got_worktree *worktree, goto done; } else { err = install_blob(worktree, ondisk_path, path, te->mode, - sb.st_mode, blob, status == GOT_STATUS_MISSING, 0, + sb.st_mode, blob, status == GOT_STATUS_MISSING, 0, 0, repo, progress_cb, progress_arg); if (err) goto done; @@ -2559,7 +2567,7 @@ merge_file_cb(void *arg, struct got_blob_object *blob1 err = install_blob(a->worktree, ondisk_path, path2, /* XXX get this from parent tree! */ GOT_DEFAULT_FILE_MODE, - sb.st_mode, blob2, 0, 0, repo, + sb.st_mode, blob2, 0, 0, 0, repo, a->progress_cb, a->progress_arg); if (err) goto done; @@ -4073,7 +4081,7 @@ revert_file(void *arg, unsigned char status, unsigned } else { err = install_blob(a->worktree, ondisk_path, ie->path, te ? te->mode : GOT_DEFAULT_FILE_MODE, - got_fileindex_perms_to_st(ie), blob, 0, 1, + got_fileindex_perms_to_st(ie), blob, 0, 1, 0, a->repo, a->progress_cb, a->progress_arg); if (err) goto done; @@ -4889,9 +4897,35 @@ commit_worktree(struct got_object_id **new_commit_id, goto done; } err = got_object_blob_create(&ct->blob_id, ondisk_path, repo); - free(ondisk_path); - if (err) + if (err) { + free(ondisk_path); goto done; + } + + /* + * When comitting a symlink we convert "bad" symlinks (those + * which point outside the work tree or into .got) to regular + * files. This way, the post-commit work tree state matches + * a fresh checkout of the tree which was committed. + */ + if (S_ISLNK(get_ct_file_mode(ct))) { + struct got_blob_object *blob; + err = got_object_open_as_blob(&blob, repo, ct->blob_id, + PATH_MAX); + if (err) { + free(ondisk_path); + goto done; + } + err = install_symlink(worktree, ondisk_path, ct->path, + get_ct_file_mode(ct), GOT_DEFAULT_FILE_MODE, blob, + 0, 0, repo, NULL, NULL); + got_object_blob_close(blob); + if (err) { + free(ondisk_path); + goto done; + } + } + free(ondisk_path); } /* Recursively write new tree objects. */ blob - 9bf3085d0b6f3078e98d58aaff1f4e4e4525bff9 blob + 45cd97ff9844d5541bd8f229053371132ae23913 --- regress/cmdline/commit.sh +++ regress/cmdline/commit.sh @@ -902,101 +902,107 @@ function test_commit_with_unrelated_submodule { test_done "$testroot" "$ret" } -function test_commit_symlink { - local testroot=`test_init commit_symlink` +function check_symlinks { + local wtpath="$1" + if ! [ -h $wtpath/alpha.link ]; then + echo "alpha.link is not a symlink" + return 1 + fi - got checkout $testroot/repo $testroot/wt > /dev/null + readlink $wtpath/alpha.link > $testroot/stdout + echo "alpha" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" if [ "$ret" != "0" ]; then - test_done "$testroot" "$ret" + diff -u $testroot/stdout.expected $testroot/stdout 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) - - (cd $testroot/wt && got commit -m 'test commit_symlink' > $testroot/stdout) - - local head_rev=`git_show_head $testroot/repo` - echo "A alpha.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 "A epsilon/beta.link" >> $testroot/stdout.expected - echo "Created commit $head_rev" >> $testroot/stdout.expected + if ! [ -h $wtpath/epsilon.link ]; then + echo "epsilon.link is not a symlink" + return 1 + fi + readlink $wtpath/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 - got checkout $testroot/repo $testroot/wt2 > /dev/null + if [ -h $wtpath/passwd.link ]; then + echo -n "passwd.link is a symlink and points outside of work tree: " >&2 + readlink $wtpath/passwd.link >&2 + return 1 + fi + + echo -n "/etc/passwd" > $testroot/content.expected + cp $wtpath/passwd.link $testroot/content ret="$?" if [ "$ret" != "0" ]; then - test_done "$testroot" "$ret" + echo "cp command failed unexpectedly" >&2 return 1 fi - if ! [ -h $testroot/wt2/alpha.link ]; then - echo "alpha.link is not a symlink" - test_done "$testroot" "1" + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content return 1 fi - readlink $testroot/wt2/alpha.link > $testroot/stdout - echo "alpha" > $testroot/stdout.expected + readlink $wtpath/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 - if ! [ -h $testroot/wt2/epsilon.link ]; then - echo "epsilon.link is not a symlink" - test_done "$testroot" "1" - return 1 - fi - - readlink $testroot/wt2/epsilon.link > $testroot/stdout - echo "epsilon" > $testroot/stdout.expected + readlink $wtpath/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 - test_done "$testroot" "$ret" return 1 fi - if [ -h $testroot/wt2/passwd.link ]; then - echo -n "passwd.link symlink points outside of work tree: " >&2 - readlink $testroot/wt2/passwd.link >&2 - test_done "$testroot" "1" - return 1 - fi + return 0 +} - echo -n "/etc/passwd" > $testroot/content.expected - cp $testroot/wt2/passwd.link $testroot/content +function test_commit_symlink { + local testroot=`test_init commit_symlink` - cmp -s $testroot/content.expected $testroot/content + got checkout $testroot/repo $testroot/wt > /dev/null ret="$?" if [ "$ret" != "0" ]; then - diff -u $testroot/content.expected $testroot/content test_done "$testroot" "$ret" return 1 fi - readlink $testroot/wt2/epsilon/beta.link > $testroot/stdout - echo "../beta" > $testroot/stdout.expected + (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) + + (cd $testroot/wt && got commit -m 'test commit_symlink' > $testroot/stdout) + #(cd $testroot/wt && egdb --args got commit -m 'test commit_symlink') + + local head_rev=`git_show_head $testroot/repo` + echo "A alpha.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 "A epsilon/beta.link" >> $testroot/stdout.expected + echo "Created commit $head_rev" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" if [ "$ret" != "0" ]; then @@ -1005,14 +1011,28 @@ function test_commit_symlink { return 1 fi - readlink $testroot/wt2/nonexistent.link > $testroot/stdout - echo "nonexistent" > $testroot/stdout.expected - cmp -s $testroot/stdout.expected $testroot/stdout + # verify created in-repository tree + got checkout $testroot/repo $testroot/wt2 > /dev/null ret="$?" if [ "$ret" != "0" ]; then - diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 fi - test_done "$testroot" "$ret" + check_symlinks $testroot/wt2 + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + # verify post-commit work tree state matches a fresh checkout + check_symlinks $testroot/wt + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + test_done "$testroot" "0" } run_test test_commit_basic