commit - aa0926921193099319156437044e75e4b7b702e1
commit + 36bf999ca5297d07cc17f79a154b0875c1574148
blob - 22c99171fca9a1866a27a828b37e35a9114f2a8f
blob + d7c970469aa82014707191e80167f76aa30fb17e
--- lib/worktree.c
+++ lib/worktree.c
/*
* 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];
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) ||
done:
free(ancestor_target);
- free(deriv_target);
return err;
}
}
}
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,
}
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,
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,
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,
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
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 <<EOF
+ M alpha.link
+? dotgotfoo.link
+ M epsilon/beta.link
+ M epsilon.link
+ A new.link
+ M nonexistent.link
+ M passwd.link
+ D zeta.link
+ D zeta2.link
+ A zeta3.link
+EOF
+ 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
+
+ printf "y\nn\ny\nn\ny\ny\nn\ny\ny\n" > $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 <<EOF
+-----------------------------------------------
+@@ -1 +1 @@
+-alpha
+\ No newline at end of file
++gamma/delta
+\ No newline at end of file
+-----------------------------------------------
+M alpha.link (change 1 of 1)
+unstage this change? [y/n/q] y
+G alpha.link
+-----------------------------------------------
+@@ -1 +1 @@
+-../beta
+\ No newline at end of file
++../gamma
+\ No newline at end of file
+-----------------------------------------------
+M epsilon/beta.link (change 1 of 1)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -1 +1 @@
+-epsilon
+\ No newline at end of file
++beta
+\ No newline at end of file
+-----------------------------------------------
+M epsilon.link (change 1 of 1)
+unstage this change? [y/n/q] y
+G epsilon.link
+A new.link
+unstage this addition? [y/n] n
+-----------------------------------------------
+@@ -1 +1 @@
+-nonexistent
+\ No newline at end of file
++nonexistent2
+\ No newline at end of file
+-----------------------------------------------
+M nonexistent.link (change 1 of 1)
+unstage this change? [y/n/q] y
+G nonexistent.link
+-----------------------------------------------
+@@ -1 +1 @@
+-/etc/passwd
+\ No newline at end of file
++foo
+-----------------------------------------------
+M passwd.link (change 1 of 1)
+unstage this change? [y/n/q] y
+G passwd.link
+D zeta.link
+unstage this deletion? [y/n] n
+D zeta2.link
+unstage this deletion? [y/n] y
+D zeta2.link
+A zeta3.link
+unstage this addition? [y/n] y
+G zeta3.link
+EOF
+ 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 "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
run_test test_unstage_patch_removed
run_test test_unstage_patch_quit
run_test test_unstage_symlink
+run_test test_unstage_patch_symlink