commit - 56d815a9328f873fd6818ac625999d06a8752a45
commit + 993e2a1b1ac61a9d56877df5325d519f7b737375
blob - f6e9f099eeee7a69e9bd4089987f1a6883cee164
blob + 7468ff6fc6fe2ee999848cdbe8cb3db5c139cf01
--- lib/worktree.c
+++ lib/worktree.c
/*
* Overwrite a symlink (or a regular file in case there was a "bad" symlink)
* in the work tree with a file that contains conflict markers and the
- * conflicting target paths of the original version and two derived versions
- * of a symlink.
+ * conflicting target paths of the original version, a "derived version"
+ * of a symlink from an incoming change, and a local version of the symlink.
+ *
+ * The original versions's target path can be NULL if it is not available,
+ * such as if both derived versions added a new symlink at the same path.
+ *
+ * The incoming derived symlink target is NULL in case the incoming change
+ * has deleted this symlink.
*/
static const struct got_error *
install_symlink_conflict(const char *deriv_target,
"contents may be filled in.\nThe following conflicting symlink "
"target paths were found:\n"
"%s %s\n%s\n%s%s%s%s%s\n%s\n%s\n", getprogname(),
- GOT_DIFF_CONFLICT_MARKER_BEGIN, label_deriv, deriv_target,
+ GOT_DIFF_CONFLICT_MARKER_BEGIN, label_deriv,
+ deriv_target ? deriv_target : "(symlink was deleted)",
orig_target ? label_orig : "",
orig_target ? "\n" : "",
orig_target ? orig_target : "",
goto done;
}
}
- err = merge_blob(&update_timestamps, worktree, blob2,
- ondisk_path, path, sb.st_mode, label_orig, blob,
- worktree->base_commit_id, repo,
- progress_cb, progress_arg);
+ if (S_ISLNK(te->mode)) {
+ err = merge_symlink(worktree, blob2,
+ ondisk_path, path, sb.st_mode, label_orig,
+ blob, worktree->base_commit_id, repo,
+ progress_cb, progress_arg);
+ } else {
+ err = merge_blob(&update_timestamps, worktree, blob2,
+ ondisk_path, path, sb.st_mode, label_orig, blob,
+ worktree->base_commit_id, repo,
+ progress_cb, progress_arg);
+ }
free(label_orig);
if (blob2)
got_object_blob_close(blob2);
if (err)
goto done;
+ if (S_ISLNK(sb.st_mode) && status != GOT_STATUS_NO_CHANGE) {
+ char ondisk_target[PATH_MAX];
+ ssize_t ondisk_len = readlink(ondisk_path, ondisk_target,
+ sizeof(ondisk_target));
+ if (ondisk_len == -1) {
+ err = got_error_from_errno2("readlink", ondisk_path);
+ goto done;
+ }
+ ondisk_target[ondisk_len] = '\0';
+ err = install_symlink_conflict(NULL, worktree->base_commit_id,
+ NULL, NULL, /* XXX pass common ancestor info? */
+ ondisk_target, ondisk_path);
+ if (err)
+ goto done;
+ err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT,
+ ie->path);
+ goto done;
+ }
+
if (status == GOT_STATUS_MODIFY || status == GOT_STATUS_CONFLICT ||
status == GOT_STATUS_ADD) {
err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, ie->path);
blob - b61d2a00448a591d108d2209b91d0e762aea07f2
blob + 2ee2f8f53e19a50823b280fff77f85ce353b0389
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
diff -u $testroot/stdout.expected $testroot/stdout
fi
test_done "$testroot" "$ret"
+}
+
+function test_update_deletes_symlink {
+ local testroot=`test_init update_deletes_symlink`
+
+ (cd $testroot/repo && ln -s alpha alpha.link)
+ (cd $testroot/repo && git add .)
+ git_commit $testroot/repo -m "add symlink"
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "checkout failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git rm -q alpha.link)
+ git_commit $testroot/repo -m "delete symlink"
+
+ echo "D alpha.link" > $testroot/stdout.expected
+ echo -n "Updated to commit " >> $testroot/stdout.expected
+ git_show_head $testroot/repo >> $testroot/stdout.expected
+ echo >> $testroot/stdout.expected
+
+ (cd $testroot/wt && got update > $testroot/stdout)
+
+ 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/alpha.link ]; then
+ echo "alpha.link still exists on disk"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ test_done "$testroot" "0"
+}
+
+function test_update_symlink_conflicts {
+ local testroot=`test_init update_symlink_conflicts`
+
+ (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 && 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
+ echo "checkout failed unexpectedly" >&2
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (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)
+ echo 'this is regular file foo' > $testroot/repo/dotgotfoo.link
+ (cd $testroot/repo && ln -sf .got/bar dotgotbar.link)
+ (cd $testroot/repo && git rm -q nonexistent.link)
+ (cd $testroot/repo && ln -sf gamma/delta zeta.link)
+ (cd $testroot/repo && ln -sf alpha new.link)
+ (cd $testroot/repo && git add .)
+ git_commit $testroot/repo -m "change symlinks"
+ local commit_id2=`git_show_head $testroot/repo`
+
+ # modified symlink to file A vs modified symlink to file B
+ (cd $testroot/wt && ln -sf gamma/delta alpha.link)
+ # modified symlink to dir A vs modified symlink to file B
+ (cd $testroot/wt && ln -sfh beta epsilon.link)
+ # 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)
+ # added bad symlink to file A vs added regular file A
+ echo 'this is regular file bar' > $testroot/wt/dotgotbar.link
+ # removed symlink to non-existent file A vs modified symlink
+ # to nonexistent file B
+ (cd $testroot/wt && ln -sf nonexistent2 nonexistent.link)
+ # modified symlink to file A vs removed symlink to file A
+ (cd $testroot/wt && got rm zeta.link > /dev/null)
+ # added symlink to file A vs added symlink to file B
+ (cd $testroot/wt && ln -sf beta new.link)
+ (cd $testroot/wt && got add new.link > /dev/null)
+
+ (cd $testroot/wt && got update > $testroot/stdout)
+
+ echo "C alpha.link" >> $testroot/stdout.expected
+ echo "U dotgotbar.link" >> $testroot/stdout.expected
+ echo "U dotgotfoo.link" >> $testroot/stdout.expected
+ echo "C epsilon/beta.link" >> $testroot/stdout.expected
+ echo "C epsilon.link" >> $testroot/stdout.expected
+ echo "C new.link" >> $testroot/stdout.expected
+ echo "C nonexistent.link" >> $testroot/stdout.expected
+ echo "G zeta.link" >> $testroot/stdout.expected
+ echo -n "Updated to commit " >> $testroot/stdout.expected
+ git_show_head $testroot/repo >> $testroot/stdout.expected
+ echo >> $testroot/stdout.expected
+ echo "Files with new merge conflicts: 5" >> $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/alpha.link ]; then
+ echo "alpha.link is a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ cat > $testroot/symlink-conflict-header <<EOF
+got: Could not install symbolic link because of merge conflict.
+ln(1) may be used to fix the situation. If this is intended to be a
+regular file instead then its expected contents may be filled in.
+The following conflicting symlink target paths were found:
+EOF
+ cp $testroot/symlink-conflict-header $testroot/content.expected
+ echo "<<<<<<< merged change: commit $commit_id2" \
+ >> $testroot/content.expected
+ echo "beta" >> $testroot/content.expected
+ echo "3-way merge base: commit $commit_id1" \
+ >> $testroot/content.expected
+ echo "alpha" >> $testroot/content.expected
+ echo "=======" >> $testroot/content.expected
+ echo "gamma/delta" >> $testroot/content.expected
+ echo '>>>>>>>' >> $testroot/content.expected
+ echo -n "" >> $testroot/content.expected
+
+ cp $testroot/wt/alpha.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 a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ cp $testroot/symlink-conflict-header $testroot/content.expected
+ echo "<<<<<<< merged change: commit $commit_id2" \
+ >> $testroot/content.expected
+ echo "gamma" >> $testroot/content.expected
+ echo "3-way merge base: commit $commit_id1" \
+ >> $testroot/content.expected
+ echo "epsilon" >> $testroot/content.expected
+ echo "=======" >> $testroot/content.expected
+ echo "beta" >> $testroot/content.expected
+ echo '>>>>>>>' >> $testroot/content.expected
+ echo -n "" >> $testroot/content.expected
+
+ cp $testroot/wt/epsilon.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/passwd.link ]; then
+ echo -n "passwd.link symlink points outside of work tree: " >&2
+ readlink $testroot/wt/passwd.link >&2
+ 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/epsilon/beta.link ]; then
+ echo "epsilon/beta.link is a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ cp $testroot/symlink-conflict-header $testroot/content.expected
+ echo "<<<<<<< merged change: commit $commit_id2" \
+ >> $testroot/content.expected
+ echo "../gamma/delta" >> $testroot/content.expected
+ echo "3-way merge base: commit $commit_id1" \
+ >> $testroot/content.expected
+ echo "../beta" >> $testroot/content.expected
+ echo "=======" >> $testroot/content.expected
+ echo "../gamma" >> $testroot/content.expected
+ echo '>>>>>>>' >> $testroot/content.expected
+ echo -n "" >> $testroot/content.expected
+
+ cp $testroot/wt/epsilon/beta.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/nonexistent.link ]; then
+ echo -n "nonexistent.link still exists on disk: " >&2
+ readlink $testroot/wt/nonexistent.link >&2
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ cp $testroot/symlink-conflict-header $testroot/content.expected
+ echo "<<<<<<< merged change: commit $commit_id2" \
+ >> $testroot/content.expected
+ echo "(symlink was deleted)" >> $testroot/content.expected
+ echo "=======" >> $testroot/content.expected
+ echo "nonexistent2" >> $testroot/content.expected
+ echo '>>>>>>>' >> $testroot/content.expected
+ echo -n "" >> $testroot/content.expected
+
+ cp $testroot/wt/nonexistent.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/dotgotbar.link ]; then
+ echo "dotgotbar.link is a symlink"
+ test_done "$testroot" "1"
+ return 1
+ 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/new.link ]; then
+ echo "new.link is a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ cp $testroot/symlink-conflict-header $testroot/content.expected
+ echo "<<<<<<< merged change: commit $commit_id2" \
+ >> $testroot/content.expected
+ echo "alpha" >> $testroot/content.expected
+ echo "=======" >> $testroot/content.expected
+ echo "beta" >> $testroot/content.expected
+ echo '>>>>>>>' >> $testroot/content.expected
+ echo -n "" >> $testroot/content.expected
+
+ cp $testroot/wt/new.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
+
+ echo "A dotgotfoo.link" > $testroot/stdout.expected
+ echo "M new.link" >> $testroot/stdout.expected
+ echo "D nonexistent.link" >> $testroot/stdout.expected
+ (cd $testroot/wt && got status > $testroot/stdout)
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ test_done "$testroot" "0"
+
}
run_test test_update_basic
run_test test_update_adds_submodule
run_test test_update_conflict_wt_file_vs_repo_submodule
run_test test_update_adds_symlink
+run_test test_update_deletes_symlink
+run_test test_update_symlink_conflicts