commit - e26bafba995edab19824ed3ed6d81535259b39f1
commit + 11cc08c1dfab6c56e9e4bd98ba204b5a7d56ea9e
blob - 3b8ae9a28141f69e42518bafe9e4ad73f3227cc8
blob + 18f9d880dc8bece0503a96fcd827fbaddd5c5125
--- 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.
+ */
+static const struct got_error *
+install_symlink_conflict(const char *deriv_target,
+ struct got_object_id *deriv_base_commit_id, const char *orig_target,
+ const char *label_orig, const char *local_target, const char *ondisk_path)
+{
+ const struct got_error *err;
+ char *id_str = NULL, *label_deriv = NULL, *path = NULL;
+ FILE *f = NULL;
+
+ err = got_object_id_str(&id_str, deriv_base_commit_id);
+ if (err)
+ return got_error_from_errno("asprintf");
+
+ if (asprintf(&label_deriv, "%s: commit %s",
+ GOT_MERGE_LABEL_MERGED, id_str) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = got_opentemp_named(&path, &f, "got-symlink-conflict");
+ if (err)
+ goto done;
+
+ if (fprintf(f, "%s: Could not install symbolic link because of merge "
+ "conflict.\nln(1) may be used to fix the situation. If this is "
+ "intended to be a\nregular file instead then its expected "
+ "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,
+ orig_target ? label_orig : "",
+ orig_target ? "\n" : "",
+ orig_target ? orig_target : "",
+ orig_target ? "\n" : "",
+ GOT_DIFF_CONFLICT_MARKER_SEP,
+ local_target, GOT_DIFF_CONFLICT_MARKER_END) < 0) {
+ err = got_error_from_errno2("fprintf", path);
+ goto done;
+ }
+
+ if (unlink(ondisk_path) == -1) {
+ err = got_error_from_errno2("unlink", ondisk_path);
+ goto done;
+ }
+ if (rename(path, ondisk_path) == -1) {
+ err = got_error_from_errno3("rename", path, ondisk_path);
+ goto done;
+ }
+ if (chmod(ondisk_path, GOT_DEFAULT_FILE_MODE) == -1) {
+ err = got_error_from_errno2("chmod", ondisk_path);
+ goto done;
+ }
+done:
+ if (f != NULL && fclose(f) == EOF && err == NULL)
+ err = got_error_from_errno2("fclose", path);
+ free(path);
+ free(id_str);
+ free(label_deriv);
+ return err;
+}
+
+/*
* 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.
const struct got_error *err = NULL;
char *ancestor_target = NULL, *deriv_target = NULL;
struct stat sb;
- ssize_t ondisk_len;
+ ssize_t ondisk_len, deriv_len;
char ondisk_target[PATH_MAX];
+ int have_local_change = 0;
+ int have_incoming_change = 0;
if (lstat(ondisk_path, &sb) == -1)
return got_error_from_errno2("lstat", ondisk_path);
if (err)
goto done;
- if (ancestor_target && (ondisk_len != strlen(ancestor_target) ||
- memcmp(ondisk_target, ancestor_target, ondisk_len) != 0)) {
- /*
- * The symlink has changed on-disk (second derived version).
- * Keep that change and discard the incoming change (first
- * derived version).
- * TODO: Need tree-conflict resolution to handle this.
- */
- err = (*progress_cb)(progress_arg, GOT_STATUS_OBSTRUCTED,
- path);
- } else if (ondisk_len == strlen(deriv_target) &&
- memcmp(ondisk_target, deriv_target, ondisk_len) == 0) {
- /* Both versions made the same change. */
- err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path);
- } else {
+ if (ancestor_target == NULL ||
+ (ondisk_len != strlen(ancestor_target) ||
+ memcmp(ondisk_target, ancestor_target, ondisk_len) != 0))
+ have_local_change = 1;
+
+ deriv_len = strlen(deriv_target);
+ if (ancestor_target == NULL ||
+ (deriv_len != strlen(ancestor_target) ||
+ memcmp(deriv_target, ancestor_target, deriv_len) != 0))
+ have_incoming_change = 1;
+
+ if (!have_local_change && !have_incoming_change) {
+ if (ancestor_target) {
+ /* Both sides made the same change. */
+ err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE,
+ path);
+ } else if (deriv_len == ondisk_len &&
+ memcmp(ondisk_target, deriv_target, deriv_len) == 0) {
+ /* Both sides added the same symlink. */
+ err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE,
+ path);
+ } else {
+ /* Both sides added symlinks which don't match. */
+ err = install_symlink_conflict(deriv_target,
+ deriv_base_commit_id, ancestor_target,
+ label_orig, ondisk_target, ondisk_path);
+ if (err)
+ goto done;
+ err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT,
+ path);
+ }
+ } else if (!have_local_change && have_incoming_change) {
/* Apply the incoming change. */
err = update_symlink(ondisk_path, deriv_target,
strlen(deriv_target));
if (err)
goto done;
err = (*progress_cb)(progress_arg, GOT_STATUS_MERGE, path);
+ } else if (have_local_change && have_incoming_change) {
+ err = install_symlink_conflict(deriv_target,
+ deriv_base_commit_id, ancestor_target, label_orig,
+ ondisk_target, ondisk_path);
+ if (err)
+ goto done;
+ err = (*progress_cb)(progress_arg, GOT_STATUS_CONFLICT,
+ path);
}
+
done:
free(ancestor_target);
free(deriv_target);
blob - d6bd1a3b13dd12f24ee44b0e0b915d66d1912d1e
blob + 28666959922bc27bcad3ced40c4aed3ca140f2c0
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
(cd $testroot/wt && got cherrypick $commit_id2 > $testroot/stdout)
echo -n > $testroot/stdout.expected
- echo "~ alpha.link" >> $testroot/stdout.expected
- echo "~ epsilon/beta.link" >> $testroot/stdout.expected
+ echo "C alpha.link" >> $testroot/stdout.expected
+ echo "C epsilon/beta.link" >> $testroot/stdout.expected
echo "U dotgotbar.link" >> $testroot/stdout.expected
- echo "~ epsilon.link" >> $testroot/stdout.expected
+ echo "C epsilon.link" >> $testroot/stdout.expected
echo "U dotgotfoo.link" >> $testroot/stdout.expected
echo "D nonexistent.link" >> $testroot/stdout.expected
echo "! zeta.link" >> $testroot/stdout.expected
- echo "G new.link" >> $testroot/stdout.expected
+ echo "C new.link" >> $testroot/stdout.expected
echo "Merged commit $commit_id2" >> $testroot/stdout.expected
- echo "File paths obstructed by a non-regular file: 3" \
- >> $testroot/stdout.expected
+ echo "Files with new merge conflicts: 4" >> $testroot/stdout.expected
cmp -s $testroot/stdout.expected $testroot/stdout
ret="$?"
if [ "$ret" != "0" ]; then
return 1
fi
- if ! [ -h $testroot/wt/alpha.link ]; then
- echo "alpha.link is not a symlink"
+ if [ -h $testroot/wt/alpha.link ]; then
+ echo "alpha.link is 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
+ 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/stdout.expected $testroot/stdout
+ 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 not a symlink"
+ if [ -h $testroot/wt/epsilon.link ]; then
+ echo "epsilon.link is 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
+ 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/stdout.expected $testroot/stdout
+ diff -u $testroot/content.expected $testroot/content
test_done "$testroot" "$ret"
return 1
fi
return 1
fi
- readlink $testroot/wt/epsilon/beta.link > $testroot/stdout
- echo "../gamma" > $testroot/stdout.expected
- cmp -s $testroot/stdout.expected $testroot/stdout
+ 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/stdout.expected $testroot/stdout
+ diff -u $testroot/content.expected $testroot/content
test_done "$testroot" "$ret"
return 1
fi
return 1
fi
- if ! [ -h $testroot/wt/new.link ]; then
- echo "new.link is not a symlink"
+ if [ -h $testroot/wt/new.link ]; then
+ echo "new.link is a symlink"
test_done "$testroot" "1"
return 1
fi
- readlink $testroot/wt/new.link > $testroot/stdout
- echo "alpha" > $testroot/stdout.expected
- cmp -s $testroot/stdout.expected $testroot/stdout
+ 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/stdout.expected $testroot/stdout
+ diff -u $testroot/content.expected $testroot/content
test_done "$testroot" "$ret"
return 1
fi