Download raw body.
allow bad symlinks to survive a merge
At present, got merge and other commands which use the file merge code (rebase/histedit/cherrypick/backout) convert bad symlinks into regular files in the work tree. When this gets committed the bad symlink is converted into a regular file in the repository. I think we should allow such symlinks to survive a merge as symlinks. They are already in the repository so either 'got commit -S' or Git were used to create such a link. In the latter case, people who use Git and Got on the same project might be surprised to learn that Got is always replacing such symlinks with regular files during merges. With this patch bad symlinks are preserved in resulting commits. For 'got merge' this happens automatically. For 'got cherrypick' the subsequent commit command needs to use the '-S' option to succeed. I am only testing 'got merge' and 'got cherrypick' for now. Test for other affected commands do not yet cover bad symlinks at all. ok? diff f6764181d7e0fc68673b90cbc93d6064509d0bd7 /home/stsp/src/got blob - 8699471c6069c7d4bc2a233ca24d70202cb6d22b file + lib/worktree.c --- lib/worktree.c +++ lib/worktree.c @@ -1372,7 +1372,8 @@ static const struct got_error * install_symlink(int *is_bad_symlink, struct got_worktree *worktree, const char *ondisk_path, const char *path, struct got_blob_object *blob, int restoring_missing_file, int reverting_versioned_file, - int path_is_unversioned, struct got_repository *repo, + int path_is_unversioned, int allow_bad_symlinks, + struct got_repository *repo, got_worktree_checkout_cb progress_cb, void *progress_arg) { const struct got_error *err = NULL; @@ -1417,7 +1418,7 @@ install_symlink(int *is_bad_symlink, struct got_worktr if (err) return err; - if (*is_bad_symlink) { + if (*is_bad_symlink && !allow_bad_symlinks) { /* install as a regular file */ got_object_blob_rewind(blob); err = install_blob(worktree, ondisk_path, path, @@ -2081,8 +2082,8 @@ update_blob(struct got_worktree *worktree, err = install_symlink(&is_bad_symlink, worktree, ondisk_path, path, blob, status == GOT_STATUS_MISSING, 0, - status == GOT_STATUS_UNVERSIONED, repo, - progress_cb, progress_arg); + status == GOT_STATUS_UNVERSIONED, 0, + repo, progress_cb, progress_arg); } else { err = install_blob(worktree, ondisk_path, path, te->mode, sb.st_mode, blob, @@ -2859,6 +2860,7 @@ struct merge_file_cb_arg { void *cancel_arg; const char *label_orig; struct got_object_id *commit_id2; + int allow_bad_symlinks; }; static const struct got_error * @@ -3103,7 +3105,8 @@ merge_file_cb(void *arg, struct got_blob_object *blob1 if (S_ISLNK(mode2)) { err = install_symlink(&is_bad_symlink, a->worktree, ondisk_path, path2, blob2, 0, - 0, 1, repo, a->progress_cb, a->progress_arg); + 0, 1, a->allow_bad_symlinks, repo, + a->progress_cb, a->progress_arg); } else { err = install_blob(a->worktree, ondisk_path, path2, mode2, sb.st_mode, blob2, 0, 0, 0, 1, repo, @@ -3266,6 +3269,7 @@ merge_files(struct got_worktree *worktree, struct got_ arg.cancel_arg = cancel_arg; arg.label_orig = label_orig; arg.commit_id2 = commit_id2; + arg.allow_bad_symlinks = 1; /* preserve bad symlinks across merges */ err = got_diff_tree(tree1, tree2, "", "", repo, merge_file_cb, &arg, 1); sync_err = sync_fileindex(fileindex, fileindex_path); if (sync_err && err == NULL) @@ -4782,7 +4786,7 @@ revert_file(void *arg, unsigned char status, unsigned } err = install_symlink(&is_bad_symlink, a->worktree, ondisk_path, ie->path, - blob, 0, 1, 0, a->repo, + blob, 0, 1, 0, 0, a->repo, a->progress_cb, a->progress_arg); } else { if (rename(path_content, ondisk_path) == -1) { @@ -4796,7 +4800,7 @@ revert_file(void *arg, unsigned char status, unsigned if (te && S_ISLNK(te->mode)) { err = install_symlink(&is_bad_symlink, a->worktree, ondisk_path, ie->path, - blob, 0, 1, 0, a->repo, + blob, 0, 1, 0, 0, a->repo, a->progress_cb, a->progress_arg); } else { err = install_blob(a->worktree, ondisk_path, blob - fb48a56bcaee4c1b91848964e57ec9ebfcfef84d file + regress/cmdline/cherrypick.sh --- regress/cmdline/cherrypick.sh +++ regress/cmdline/cherrypick.sh @@ -448,6 +448,29 @@ test_cherrypick_modified_symlinks() { git_commit $testroot/repo -m "add symlinks" local commit_id1=`git_show_head $testroot/repo` + got tree -r $testroot/repo -R -c $commit_id1 \ + > $testroot/stdout + cat > $testroot/stdout.expected <<EOF +alpha +alpha.link@ -> alpha +beta +epsilon/ +epsilon/beta.link@ -> ../beta +epsilon/zeta +epsilon.link@ -> epsilon +gamma/ +gamma/delta +nonexistent.link@ -> nonexistent +passwd.link@ -> /etc/passwd +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 + got branch -r $testroot/repo foo got checkout -b foo $testroot/repo $testroot/wt > /dev/null @@ -455,7 +478,7 @@ test_cherrypick_modified_symlinks() { (cd $testroot/repo && ln -sf beta alpha.link) (cd $testroot/repo && ln -sfh gamma epsilon.link) (cd $testroot/repo && ln -sf ../gamma/delta epsilon/beta.link) - (cd $testroot/repo && ln -sf .got/bar $testroot/repo/dotgotfoo.link) + (cd $testroot/repo && ln -sf .got/foo $testroot/repo/dotgotfoo.link) (cd $testroot/repo && git rm -q nonexistent.link) (cd $testroot/repo && ln -sf epsilon/zeta zeta.link) (cd $testroot/repo && git add .) @@ -495,6 +518,22 @@ test_cherrypick_modified_symlinks() { return 1 fi + if ! [ -h $testroot/wt/dotgotfoo.link ]; then + echo "dotgotfoo.link is not a symlink" + 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 ! [ -h $testroot/wt/epsilon.link ]; then echo "epsilon.link is not a symlink" test_done "$testroot" "1" @@ -546,7 +585,58 @@ test_cherrypick_modified_symlinks() { return 1 fi - test_done "$testroot" "0" + (cd $testroot/wt && got commit -m 'commit cherrypick result' \ + > /dev/null 2>$testroot/stderr) + ret="$?" + if [ "$ret" == "0" ]; then + echo "got commit succeeded unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + echo -n "got: $testroot/wt/dotgotfoo.link: symbolic link points " \ + > $testroot/stderr.expected + echo "outside of paths under version control" \ + >> $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got commit -S -m 'commit cherrypick result' \ + > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got commit failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + local commit_id2=`git_show_head $testroot/repo` + + got tree -r $testroot/repo -R -c $commit_id2 \ + > $testroot/stdout + cat > $testroot/stdout.expected <<EOF +alpha +alpha.link@ -> beta +beta +dotgotfoo.link@ -> .got/foo +epsilon/ +epsilon/beta.link@ -> ../gamma/delta +epsilon/zeta +epsilon.link@ -> gamma +gamma/ +gamma/delta +passwd.link@ -> /etc/passwd +zeta.link@ -> epsilon/zeta +EOF + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" } test_cherrypick_symlink_conflicts() { @@ -585,7 +675,7 @@ test_cherrypick_symlink_conflicts() { # modeified symlink to file A vs modified symlink to dir B (cd $testroot/wt && ln -sfh ../gamma epsilon/beta.link) # added regular file A vs added bad symlink to file A - (cd $testroot/wt && ln -sf .got/bar dotgotfoo.link) + (cd $testroot/wt && ln -sf .got/foo dotgotfoo.link) (cd $testroot/wt && got add dotgotfoo.link > /dev/null) # added bad symlink to file A vs added regular file A echo 'this is regular file bar' > $testroot/wt/dotgotbar.link @@ -755,7 +845,7 @@ test_cherrypick_symlink_conflicts() { > $testroot/content.expected echo "this is regular file foo" >> $testroot/content.expected echo "=======" >> $testroot/content.expected - echo -n ".got/bar" >> $testroot/content.expected + echo -n ".got/foo" >> $testroot/content.expected echo '>>>>>>>' >> $testroot/content.expected cp $testroot/wt/dotgotfoo.link $testroot/content cmp -s $testroot/content.expected $testroot/content blob - 0e1a94a8ea7156a29ef18ee63b4b1d881a0b6d30 file + regress/cmdline/merge.sh --- regress/cmdline/merge.sh +++ regress/cmdline/merge.sh @@ -39,6 +39,10 @@ test_merge_basic() { (cd $testroot/repo && ln -s alpha symlink && git add symlink) git_commit $testroot/repo -m "adding symlink on newbranch" local branch_commit4=`git_show_branch_head $testroot/repo newbranch` + (cd $testroot/repo && ln -sf .got/bar dotgotbar.link) + (cd $testroot/repo && git add dotgotbar.link) + git_commit $testroot/repo -m "adding a bad symlink on newbranch" + local branch_commit5=`git_show_branch_head $testroot/repo newbranch` got checkout -b master $testroot/repo $testroot/wt > /dev/null ret="$?" @@ -216,6 +220,7 @@ test_merge_basic() { echo "G alpha" >> $testroot/stdout.expected echo "D beta" >> $testroot/stdout.expected + echo "A dotgotbar.link" >> $testroot/stdout.expected echo "A epsilon/new" >> $testroot/stdout.expected echo "G gamma/delta" >> $testroot/stdout.expected echo "A symlink" >> $testroot/stdout.expected @@ -267,6 +272,12 @@ test_merge_basic() { 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/symlink > $testroot/stdout echo "alpha" > $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout @@ -302,7 +313,10 @@ test_merge_basic() { (cd $testroot/wt && got update > $testroot/stdout) - echo 'Already up-to-date' > $testroot/stdout.expected + echo 'U dotgotbar.link' > $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + git_show_head $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" if [ "$ret" != "0" ]; then @@ -311,15 +325,49 @@ test_merge_basic() { return 1 fi + # update has changed the bad symlink into a regular file + if [ -h $testroot/wt/dotgotbar.link ]; then + echo "dotgotbar.link is a symlink" + test_done "$testroot" "1" + return 1 + fi + # We should have created a merge commit with two parents. (cd $testroot/wt && got log -l1 | grep ^parent > $testroot/stdout) echo "parent 1: $master_commit" > $testroot/stdout.expected - echo "parent 2: $branch_commit4" >> $testroot/stdout.expected + echo "parent 2: $branch_commit5" >> $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 $merge_commit -R > $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + echo "got tree failed unexpectedly" >&2 + test_done "$testroot" "$ret" + return 1 + fi + + # bad symlink dotgotbar.link appears as a symlink in the merge commit: + cat > $testroot/stdout.expected <<EOF +alpha +dotgotbar.link@ -> .got/bar +epsilon/ +epsilon/new +epsilon/zeta +gamma/ +gamma/delta +symlink@ -> alpha +EOF + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi test_done "$testroot" "$ret" }
allow bad symlinks to survive a merge