commit - 40dde666c0e7cae797b8652b1f4368e52c2c7b13
commit + af57b12ab516c7fa5ecc8bd00db5637240411ed7
blob - fe23bf04f80b2f9cb6cd0a084b66aa3656096f18
blob + 3adf5f3dbc4de118965bdc9066822242aff95942
--- include/got_object.h
+++ include/got_object.h
*/
const struct got_error *got_object_blob_dump_to_file(size_t *, int *,
off_t **, FILE *, struct got_blob_object *);
+
+/*
+ * Read the entire content of a blob into a newly allocated string buffer
+ * and terminate it with '\0'. This is intended for blobs which contain a
+ * symlink target path. It should not be used to process arbitrary blobs.
+ * Use got_object_blob_dump_to_file() or got_tree_entry_get_symlink_target()
+ * instead if possible. The caller must dispose of the string with free(3).
+ */
+const struct got_error *got_object_blob_read_to_str(char **,
+ struct got_blob_object *);
/*
* Attempt to open a tag object in a repository.
blob - eaff0579c403d60f90fdc01f50fde183091286a2
blob + 102953c89fd18f475a1f3d905c286a6f7fb4fa9b
--- lib/object.c
+++ lib/object.c
}
const struct got_error *
-got_tree_entry_get_symlink_target(char **link_target, struct got_tree_entry *te,
- struct got_repository *repo)
+got_object_blob_read_to_str(char **s, struct got_blob_object *blob)
{
const struct got_error *err = NULL;
- struct got_blob_object *blob = NULL;
size_t len, totlen, hdrlen, offset;
- *link_target = NULL;
-
- if (!got_object_tree_entry_is_symlink(te))
- return got_error(GOT_ERR_TREE_ENTRY_TYPE);
-
- err = got_object_open_as_blob(&blob, repo,
- got_tree_entry_get_id(te), PATH_MAX);
- if (err)
- return err;
hdrlen = got_object_blob_get_hdrlen(blob);
totlen = 0;
offset = 0;
err = got_object_blob_read_block(&len, blob);
if (err)
- goto done;
+ return err;
if (len == 0)
break;
totlen += len - hdrlen;
- p = realloc(*link_target, totlen + 1);
+ p = realloc(*s, totlen + 1);
if (p == NULL) {
err = got_error_from_errno("realloc");
- goto done;
+ free(*s);
+ *s = NULL;
+ return err;
}
- *link_target = p;
+ *s = p;
/* Skip blob object header first time around. */
- memcpy(*link_target + offset,
+ memcpy(*s + offset,
got_object_blob_get_read_buf(blob) + hdrlen, len - hdrlen);
hdrlen = 0;
offset = totlen;
} while (len > 0);
- (*link_target)[totlen] = '\0';
-done:
- if (blob)
- got_object_blob_close(blob);
+
+ (*s)[totlen] = '\0';
+ return NULL;
+}
+
+const struct got_error *
+got_tree_entry_get_symlink_target(char **link_target, struct got_tree_entry *te,
+ struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ struct got_blob_object *blob = NULL;
+
+ *link_target = NULL;
+
+ if (!got_object_tree_entry_is_symlink(te))
+ return got_error(GOT_ERR_TREE_ENTRY_TYPE);
+
+ err = got_object_open_as_blob(&blob, repo,
+ got_tree_entry_get_id(te), PATH_MAX);
+ if (err)
+ return err;
+
+ err = got_object_blob_read_to_str(link_target, blob);
+ got_object_blob_close(blob);
if (err) {
free(*link_target);
*link_target = NULL;
blob - 365bc046c6f9d63a9505071b9fcfa2ed056c4931
blob + 18b7915d9fcf0ce6358373186138b917bbf2f1ba
--- lib/worktree.c
+++ lib/worktree.c
if (blob_orig_path) {
unlink(blob_orig_path);
free(blob_orig_path);
+ }
+ return err;
+}
+
+static const struct got_error *
+update_symlink(const char *ondisk_path, const char *target_path,
+ size_t target_len)
+{
+ /* This is not atomic but matches what 'ln -sf' does. */
+ if (unlink(ondisk_path) == -1)
+ return got_error_from_errno2("unlink", ondisk_path);
+ if (symlink(target_path, ondisk_path) == -1)
+ return got_error_from_errno3("symlink", target_path,
+ ondisk_path);
+ return NULL;
+}
+
+/*
+ * 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.
+ * 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, uint16_t st_mode, const char *label_orig,
+ struct got_blob_object *blob_deriv,
+ 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;
+ struct stat sb;
+ ssize_t ondisk_len;
+ char ondisk_target[PATH_MAX];
+
+ if (lstat(ondisk_path, &sb) == -1)
+ return got_error_from_errno2("lstat", ondisk_path);
+
+ if (!S_ISLNK(sb.st_mode)) {
+ /* TODO symlink is obstructed; do something */
+ return got_error_path(ondisk_path, GOT_ERR_FILE_OBSTRUCTED);
+ }
+
+ ondisk_len = readlink(ondisk_path, ondisk_target,
+ sizeof(ondisk_target));
+ if (ondisk_len == -1) {
+ err = got_error_from_errno2("readlink",
+ ondisk_path);
+ goto done;
+ }
+
+ err = got_object_blob_read_to_str(&ancestor_target, blob_orig);
+ if (err)
+ goto done;
+
+ err = got_object_blob_read_to_str(&deriv_target, blob_deriv);
+ if (err)
+ goto done;
+
+ if (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 {
+ /* 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);
}
+done:
+ free(ancestor_target);
+ free(deriv_target);
return err;
}
err = NULL; /* nothing to do */
goto done;
} else {
- if (unlink(ondisk_path) == -1) {
- err = got_error_from_errno2("unlink",
- ondisk_path);
- goto done;
- }
- if (symlink(target_path, ondisk_path) == -1) {
- err = got_error_from_errno3("symlink",
- target_path, ondisk_path);
+ err = update_symlink(ondisk_path, target_path,
+ target_len);
+ if (err)
goto done;
- }
-
err = (*progress_cb)(progress_arg,
GOT_STATUS_UPDATE, path);
goto done;
goto done;
}
- err = merge_blob(&local_changes_subsumed, a->worktree, blob1,
- ondisk_path, path2, sb.st_mode, a->label_orig, blob2,
- a->commit_id2, repo, a->progress_cb, a->progress_arg);
+ if (S_ISLNK(mode1) && S_ISLNK(mode2)) {
+ err = merge_symlink(a->worktree, blob1,
+ ondisk_path, path2, sb.st_mode, a->label_orig,
+ blob2, a->commit_id2, repo, a->progress_cb,
+ a->progress_arg);
+ } else {
+ err = merge_blob(&local_changes_subsumed, a->worktree,
+ blob1, ondisk_path, path2, sb.st_mode,
+ a->label_orig, blob2, a->commit_id2, repo,
+ a->progress_cb, a->progress_arg);
+ }
} else if (blob1) {
ie = got_fileindex_entry_get(a->fileindex, path1,
strlen(path1));
blob - 15366064a879e3cb8137061a6408c3504af03578
blob + f5bdf9bfbbdf46e5e6221be22cfab48b7c15d9ff
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
diff -u $testroot/stdout.expected $testroot/stdout
fi
test_done "$testroot" "$ret"
+}
+
+function test_cherrypick_modified_symlinks {
+ local testroot=`test_init cherrypick_modified_symlinks`
+
+ (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 && git add .)
+ git_commit $testroot/repo -m "add symlinks"
+ local commit_id1=`git_show_head $testroot/repo`
+
+ got branch -r $testroot/repo foo
+
+ got checkout -b foo $testroot/repo $testroot/wt > /dev/null
+
+ (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 && git rm -q nonexistent.link)
+ (cd $testroot/repo && ln -sf epsilon/zeta zeta.link)
+ (cd $testroot/repo && git add .)
+ git_commit $testroot/repo -m "change symlinks"
+ local commit_id2=`git_show_head $testroot/repo`
+
+ (cd $testroot/wt && got cherrypick $commit_id2 > $testroot/stdout)
+
+ echo "G alpha.link" > $testroot/stdout.expected
+ echo "G epsilon/beta.link" >> $testroot/stdout.expected
+ echo "A dotgotfoo.link" >> $testroot/stdout.expected
+ echo "G epsilon.link" >> $testroot/stdout.expected
+ echo "D nonexistent.link" >> $testroot/stdout.expected
+ echo "A zeta.link" >> $testroot/stdout.expected
+ echo "Merged commit $commit_id2" >> $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 not a symlink"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ readlink $testroot/wt/alpha.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/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 "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
+
+ 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
+
+ readlink $testroot/wt/epsilon/beta.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/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
+
+ test_done "$testroot" "0"
}
run_test test_cherrypick_basic
run_test test_cherrypick_modified_submodule
run_test test_cherrypick_added_submodule
run_test test_cherrypick_conflict_wt_file_vs_repo_submodule
+run_test test_cherrypick_modified_symlinks