commit 36bf999ca5297d07cc17f79a154b0875c1574148 from: Stefan Sperling date: Thu Jul 23 14:22:40 2020 UTC make 'got unstage -p' work with symlinks commit - aa0926921193099319156437044e75e4b7b702e1 commit + 36bf999ca5297d07cc17f79a154b0875c1574148 blob - 22c99171fca9a1866a27a828b37e35a9114f2a8f blob + d7c970469aa82014707191e80167f76aa30fb17e --- lib/worktree.c +++ lib/worktree.c @@ -972,20 +972,19 @@ merge_blob(int *, struct got_worktree *, struct got_bl /* * Merge a symlink into the work tree, where blob_orig acts as the common - * ancestor, blob_deriv acts as the first derived version, and the symlink - * on disk acts as the second derived version. + * ancestor, deriv_target is the link target of the first derived version, + * and the symlink on disk acts as the second derived version. * Assume that contents of both blobs represent symlinks. */ static const struct got_error * merge_symlink(struct got_worktree *worktree, struct got_blob_object *blob_orig, const char *ondisk_path, - const char *path, const char *label_orig, - struct got_blob_object *blob_deriv, + const char *path, const char *label_orig, const char *deriv_target, struct got_object_id *deriv_base_commit_id, struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; - char *ancestor_target = NULL, *deriv_target = NULL; + char *ancestor_target = NULL; struct stat sb; ssize_t ondisk_len, deriv_len; char ondisk_target[PATH_MAX]; @@ -1009,10 +1008,6 @@ merge_symlink(struct got_worktree *worktree, if (err) goto done; } - - err = got_object_blob_read_to_str(&deriv_target, blob_deriv); - if (err) - goto done; if (ancestor_target == NULL || (ondisk_len != strlen(ancestor_target) || @@ -1071,7 +1066,6 @@ merge_symlink(struct got_worktree *worktree, done: free(ancestor_target); - free(deriv_target); return err; } @@ -1913,10 +1907,14 @@ update_blob(struct got_worktree *worktree, } } if (S_ISLNK(te->mode) && S_ISLNK(sb.st_mode)) { - err = merge_symlink(worktree, blob2, - ondisk_path, path, label_orig, blob, - worktree->base_commit_id, repo, - progress_cb, progress_arg); + char *link_target; + err = got_object_blob_read_to_str(&link_target, blob); + if (err) + goto done; + err = merge_symlink(worktree, blob2, ondisk_path, path, + label_orig, link_target, worktree->base_commit_id, + repo, progress_cb, progress_arg); + free(link_target); } else { err = merge_blob(&update_timestamps, worktree, blob2, ondisk_path, path, sb.st_mode, label_orig, blob, @@ -2718,9 +2716,14 @@ merge_file_cb(void *arg, struct got_blob_object *blob1 } if (S_ISLNK(mode1) && S_ISLNK(mode2)) { + char *link_target2; + err = got_object_blob_read_to_str(&link_target2, blob2); + if (err) + goto done; err = merge_symlink(a->worktree, blob1, ondisk_path, - path2, a->label_orig, blob2, a->commit_id2, repo, - a->progress_cb, a->progress_arg); + path2, a->label_orig, link_target2, a->commit_id2, + repo, a->progress_cb, a->progress_arg); + free(link_target2); } else { err = merge_blob(&local_changes_subsumed, a->worktree, blob1, ondisk_path, path2, sb.st_mode, @@ -2800,10 +2803,16 @@ merge_file_cb(void *arg, struct got_blob_object *blob1 goto done; } if (S_ISLNK(mode2) && S_ISLNK(sb.st_mode)) { + char *link_target2; + err = got_object_blob_read_to_str(&link_target2, + blob2); + if (err) + goto done; err = merge_symlink(a->worktree, NULL, ondisk_path, path2, a->label_orig, - blob2, a->commit_id2, repo, + link_target2, a->commit_id2, repo, a->progress_cb, a->progress_arg); + free(link_target2); } else if (S_ISREG(sb.st_mode)) { err = merge_blob(&local_changes_subsumed, a->worktree, NULL, ondisk_path, path2, @@ -7457,12 +7466,51 @@ unstage_path(void *arg, unsigned char status, staged_blob_id->sha1, SHA1_DIGEST_LENGTH); } - err = merge_file(&local_changes_subsumed, - a->worktree, blob_base, ondisk_path, - relpath, got_fileindex_perms_to_st(ie), - path_unstaged_content, label_orig, - "unstaged", a->repo, a->progress_cb, - a->progress_arg); + if (got_fileindex_entry_staged_filetype_get(ie) + == GOT_FILEIDX_MODE_SYMLINK) { + char unstaged_target[PATH_MAX]; + FILE *f; + size_t r; + f = fopen(path_unstaged_content, "r"); + if (f == NULL) { + err = got_error_from_errno2( + "fopen", + path_unstaged_content); + goto done; + } + r = fread(unstaged_target, 1, + sizeof(unstaged_target), f); + if (r == 0 && ferror(f)) { + err = got_error_from_errno( + "fread"); + fclose(f); + break; + } + if (fclose(f) == EOF) { + err = got_error_from_errno2( + "fclose", + path_unstaged_content); + } + if (r >= sizeof(unstaged_target)) { + err = got_error( + GOT_ERR_NO_SPACE); + goto done; + } + unstaged_target[r] = '\0'; + err = merge_symlink(a->worktree, + blob_base, ondisk_path, relpath, + label_orig, unstaged_target, + a->worktree->base_commit_id, + a->repo, a->progress_cb, + a->progress_arg); + } else { + err = merge_file(&local_changes_subsumed, + a->worktree, blob_base, ondisk_path, + relpath, got_fileindex_perms_to_st(ie), + path_unstaged_content, label_orig, + "unstaged", a->repo, a->progress_cb, + a->progress_arg); + } if (err == NULL && path_new_staged_content == NULL) got_fileindex_entry_stage_set(ie, @@ -7486,11 +7534,17 @@ unstage_path(void *arg, unsigned char status, break; case GOT_FILEIDX_MODE_SYMLINK: if (S_ISLNK(got_fileindex_perms_to_st(ie))) { + char *staged_target; + err = got_object_blob_read_to_str( + &staged_target, blob_staged); + if (err) + goto done; err = merge_symlink(a->worktree, blob_base, ondisk_path, relpath, label_orig, - blob_staged, commit_id ? commit_id : + staged_target, commit_id ? commit_id : a->worktree->base_commit_id, a->repo, a->progress_cb, a->progress_arg); + free(staged_target); } else { err = merge_blob(&local_changes_subsumed, a->worktree, blob_base, ondisk_path, blob - d01d1ed083c9dd96adb1abead9c4bc6d42fa5c87 blob + 3cd20d37557eceb704929d98cb9c43eab91e88eb --- regress/cmdline/unstage.sh +++ regress/cmdline/unstage.sh @@ -1134,7 +1134,289 @@ EOF test_done "$testroot" "0" } + +function test_unstage_patch_symlink { + local testroot=`test_init unstage_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 && ln -sf epsilon/zeta zeta.link) + (cd $testroot/repo && ln -sf epsilon/zeta zeta2.link) + (cd $testroot/repo && git add .) + git_commit $testroot/repo -m "add symlinks" + local commit_id1=`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 + + # symlink to file A now points to file B + (cd $testroot/wt && ln -sf gamma/delta alpha.link) + # symlink to a directory A now points to file B + (cd $testroot/wt && ln -sfh beta epsilon.link) + # "bad" symlink now contains a different target path + echo "foo" > $testroot/wt/passwd.link + # relative symlink to directory A now points to relative directory B + (cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link) + # an unversioned symlink + (cd $testroot/wt && ln -sf .got/foo dotgotfoo.link) + # symlink to file A now points to non-existent file B + (cd $testroot/wt && ln -sf nonexistent2 nonexistent.link) + # removed symlink + (cd $testroot/wt && got rm zeta.link > /dev/null) + (cd $testroot/wt && got rm zeta2.link > /dev/null) + # added symlink + (cd $testroot/wt && ln -sf beta new.link) + (cd $testroot/wt && got add new.link > /dev/null) + (cd $testroot/wt && ln -sf beta zeta3.link) + (cd $testroot/wt && got add zeta3.link > /dev/null) + (cd $testroot/wt && got stage -S > /dev/null) + + (cd $testroot/wt && got status > $testroot/stdout) + cat > $testroot/stdout.expected < $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got unstage command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + cat > $testroot/stdout.expected < $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 + + 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 "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/wt/passwd.link ]; then + echo "passwd.link should not be a symlink" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "foo" > $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 "../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 + + readlink $testroot/wt/nonexistent.link > $testroot/stdout + echo "nonexistent2" > $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 not a symlink " >&2 + test_done "$testroot" "1" + return 1 + fi + readlink $testroot/wt/dotgotfoo.link > $testroot/stdout + echo ".got/foo" > $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 [ -e $testroot/wt/zeta.link ]; then + echo -n "zeta.link should not exist on disk" >&2 + test_done "$testroot" "1" + return 1 + fi + + if [ -e $testroot/wt/zeta2.link ]; then + echo -n "zeta2.link exists on disk" >&2 + test_done "$testroot" "1" + return 1 + fi + + if [ ! -h $testroot/wt/zeta3.link ]; then + echo -n "zeta3.link is not a symlink" >&2 + test_done "$testroot" "1" + return 1 + fi + + readlink $testroot/wt/zeta3.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/wt/new.link ]; then + echo -n "new.link is not a symlink" >&2 + test_done "$testroot" "1" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + echo "M alpha.link" > $testroot/stdout.expected + echo "? dotgotfoo.link" >> $testroot/stdout.expected + echo " M epsilon/beta.link" >> $testroot/stdout.expected + echo "M epsilon.link" >> $testroot/stdout.expected + echo " A new.link" >> $testroot/stdout.expected + echo "M nonexistent.link" >> $testroot/stdout.expected + echo "M passwd.link" >> $testroot/stdout.expected + echo " D zeta.link" >> $testroot/stdout.expected + echo "D zeta2.link" >> $testroot/stdout.expected + echo "A zeta3.link" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + return 1 + fi + test_done "$testroot" "$ret" +} + run_test test_unstage_basic run_test test_unstage_unversioned run_test test_unstage_nonexistent @@ -1143,3 +1425,4 @@ run_test test_unstage_patch_added run_test test_unstage_patch_removed run_test test_unstage_patch_quit run_test test_unstage_symlink +run_test test_unstage_patch_symlink