summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/copy.c')
-rw-r--r--subversion/libsvn_client/copy.c1060
1 files changed, 969 insertions, 91 deletions
diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c
index f204bbc..af6a75b 100644
--- a/subversion/libsvn_client/copy.c
+++ b/subversion/libsvn_client/copy.c
@@ -177,12 +177,513 @@ get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
return SVN_NO_ERROR;
}
+/* Quote a string if it would be handled as multiple or different tokens
+ during externals parsing */
+static const char *
+maybe_quote(const char *value,
+ apr_pool_t *result_pool)
+{
+ apr_status_t status;
+ char **argv;
+
+ status = apr_tokenize_to_argv(value, &argv, result_pool);
+
+ if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
+ return apr_pstrdup(result_pool, value);
+
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
+ const char *c;
+
+ svn_stringbuf_appendbyte(sb, '\"');
+
+ for (c = value; *c; c++)
+ {
+ if (*c == '\\' || *c == '\"' || *c == '\'')
+ svn_stringbuf_appendbyte(sb, '\\');
+
+ svn_stringbuf_appendbyte(sb, *c);
+ }
+
+ svn_stringbuf_appendbyte(sb, '\"');
+
+#ifdef SVN_DEBUG
+ status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
+
+ SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
+ && !strcmp(argv[0], value));
+#endif
+
+ return sb->data;
+ }
+}
+
+/* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for
+ * use as a line in an svn:externals property, based on the external item
+ * ITEM and the additional parser information in INFO. Pin the external
+ * to EXTERNAL_PEGREV. Use POOL for all allocations. */
+static svn_error_t *
+make_external_description(const char **new_external_description,
+ const char *local_abspath_or_url,
+ svn_wc_external_item2_t *item,
+ svn_wc__externals_parser_info_t *info,
+ svn_opt_revision_t external_pegrev,
+ apr_pool_t *pool)
+{
+ const char *rev_str;
+ const char *peg_rev_str;
+
+ switch (info->format)
+ {
+ case svn_wc__external_description_format_1:
+ if (external_pegrev.kind == svn_opt_revision_unspecified)
+ {
+ /* If info->rev_str is NULL, this yields an empty string. */
+ rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
+ }
+ else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
+ rev_str = apr_psprintf(pool, "%s ", info->rev_str);
+ else
+ {
+ /* ### can't handle svn_opt_revision_date without info->rev_str */
+ SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
+ rev_str = apr_psprintf(pool, "-r%ld ",
+ external_pegrev.value.number);
+ }
+
+ *new_external_description =
+ apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
+ rev_str,
+ maybe_quote(item->url, pool));
+ break;
+
+ case svn_wc__external_description_format_2:
+ if (external_pegrev.kind == svn_opt_revision_unspecified)
+ {
+ /* If info->rev_str is NULL, this yields an empty string. */
+ rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
+ }
+ else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
+ rev_str = apr_psprintf(pool, "%s ", info->rev_str);
+ else
+ rev_str = "";
+
+ if (external_pegrev.kind == svn_opt_revision_unspecified)
+ peg_rev_str = info->peg_rev_str ? info->peg_rev_str : "";
+ else if (info->peg_rev_str &&
+ item->peg_revision.kind != svn_opt_revision_head)
+ peg_rev_str = info->peg_rev_str;
+ else
+ {
+ /* ### can't handle svn_opt_revision_date without info->rev_str */
+ SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
+ peg_rev_str = apr_psprintf(pool, "@%ld",
+ external_pegrev.value.number);
+ }
+
+ *new_external_description =
+ apr_psprintf(pool, "%s%s %s\n", rev_str,
+ maybe_quote(apr_psprintf(pool, "%s%s", item->url,
+ peg_rev_str),
+ pool),
+ maybe_quote(item->target_dir, pool));
+ break;
+
+ default:
+ return svn_error_createf(
+ SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
+ _("%s property defined at '%s' is using an unsupported "
+ "syntax"), SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(local_abspath_or_url, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Pin all externals listed in EXTERNALS_PROP_VAL to their
+ * last-changed revision. Set *PINNED_EXTERNALS to a new property
+ * value allocated in RESULT_POOL, or to NULL if none of the externals
+ * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the
+ * path or URL defining the svn:externals property. Use SCRATCH_POOL
+ * for temporary allocations.
+ */
+static svn_error_t *
+pin_externals_prop(svn_string_t **pinned_externals,
+ svn_string_t *externals_prop_val,
+ const apr_hash_t *externals_to_pin,
+ const char *repos_root_url,
+ const char *local_abspath_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *buf;
+ apr_array_header_t *external_items;
+ apr_array_header_t *parser_infos;
+ apr_array_header_t *items_to_pin;
+ int pinned_items;
+ int i;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_wc__parse_externals_description(&external_items,
+ &parser_infos,
+ local_abspath_or_url,
+ externals_prop_val->data,
+ FALSE /* canonicalize_url */,
+ scratch_pool));
+
+ if (externals_to_pin)
+ {
+ items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
+ local_abspath_or_url);
+ if (!items_to_pin)
+ {
+ /* No pinning at all for this path. */
+ *pinned_externals = NULL;
+ return SVN_NO_ERROR;
+ }
+ }
+ else
+ items_to_pin = NULL;
+
+ buf = svn_stringbuf_create_empty(scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ pinned_items = 0;
+ for (i = 0; i < external_items->nelts; i++)
+ {
+ svn_wc_external_item2_t *item;
+ svn_wc__externals_parser_info_t *info;
+ svn_opt_revision_t external_pegrev;
+ const char *pinned_desc;
+
+ svn_pool_clear(iterpool);
+
+ item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
+ info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *);
+
+ if (items_to_pin)
+ {
+ int j;
+ svn_wc_external_item2_t *item_to_pin = NULL;
+
+ for (j = 0; j < items_to_pin->nelts; j++)
+ {
+ svn_wc_external_item2_t *const current =
+ APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
+
+
+ if (current
+ && 0 == strcmp(item->url, current->url)
+ && 0 == strcmp(item->target_dir, current->target_dir))
+ {
+ item_to_pin = current;
+ break;
+ }
+ }
+
+ /* If this item is not in our list of external items to pin then
+ * simply keep the external at its original value. */
+ if (!item_to_pin)
+ {
+ const char *desc;
+
+ external_pegrev.kind = svn_opt_revision_unspecified;
+ SVN_ERR(make_external_description(&desc, local_abspath_or_url,
+ item, info, external_pegrev,
+ iterpool));
+ svn_stringbuf_appendcstr(buf, desc);
+ continue;
+ }
+ }
+
+ if (item->peg_revision.kind == svn_opt_revision_date)
+ {
+ /* Already pinned ... copy the peg date. */
+ external_pegrev.kind = svn_opt_revision_date;
+ external_pegrev.value.date = item->peg_revision.value.date;
+ }
+ else if (item->peg_revision.kind == svn_opt_revision_number)
+ {
+ /* Already pinned ... copy the peg revision number. */
+ external_pegrev.kind = svn_opt_revision_number;
+ external_pegrev.value.number = item->peg_revision.value.number;
+ }
+ else
+ {
+ SVN_ERR_ASSERT(
+ item->peg_revision.kind == svn_opt_revision_head ||
+ item->peg_revision.kind == svn_opt_revision_unspecified);
+
+ /* We're actually going to change the peg revision. */
+ ++pinned_items;
+
+ if (svn_path_is_url(local_abspath_or_url))
+ {
+ const char *resolved_url;
+ svn_ra_session_t *external_ra_session;
+ svn_revnum_t latest_revnum;
+
+ SVN_ERR(svn_wc__resolve_relative_external_url(
+ &resolved_url, item, repos_root_url,
+ local_abspath_or_url, iterpool, iterpool));
+ SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
+ NULL, resolved_url,
+ NULL, NULL, FALSE,
+ FALSE, ctx,
+ iterpool,
+ iterpool));
+ SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
+ &latest_revnum,
+ iterpool));
+
+ external_pegrev.kind = svn_opt_revision_number;
+ external_pegrev.value.number = latest_revnum;
+ }
+ else
+ {
+ const char *external_abspath;
+ svn_node_kind_t external_kind;
+ svn_revnum_t external_checked_out_rev;
+
+ external_abspath = svn_dirent_join(local_abspath_or_url,
+ item->target_dir,
+ iterpool);
+ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL,
+ NULL, NULL, ctx->wc_ctx,
+ local_abspath_or_url,
+ external_abspath, TRUE,
+ iterpool,
+ iterpool));
+ if (external_kind == svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ NULL,
+ _("Cannot pin external '%s' defined "
+ "in %s at '%s' because it is not "
+ "checked out in the working copy "
+ "at '%s'"),
+ item->url, SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(
+ local_abspath_or_url, iterpool),
+ svn_dirent_local_style(
+ external_abspath, iterpool));
+ else if (external_kind == svn_node_dir)
+ {
+ svn_boolean_t is_switched;
+ svn_boolean_t is_modified;
+ svn_revnum_t min_rev;
+ svn_revnum_t max_rev;
+
+ /* Perform some sanity checks on the checked-out external. */
+
+ SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
+ ctx->wc_ctx,
+ external_abspath, NULL,
+ iterpool));
+ if (is_switched)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ NULL,
+ _("Cannot pin external '%s' defined "
+ "in %s at '%s' because '%s' has "
+ "switched subtrees (switches "
+ "cannot be represented in %s)"),
+ item->url, SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(
+ local_abspath_or_url, iterpool),
+ svn_dirent_local_style(
+ external_abspath, iterpool),
+ SVN_PROP_EXTERNALS);
+
+ SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
+ external_abspath, TRUE,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ iterpool));
+ if (is_modified)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ NULL,
+ _("Cannot pin external '%s' defined "
+ "in %s at '%s' because '%s' has "
+ "local modifications (local "
+ "modifications cannot be "
+ "represented in %s)"),
+ item->url, SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(
+ local_abspath_or_url, iterpool),
+ svn_dirent_local_style(
+ external_abspath, iterpool),
+ SVN_PROP_EXTERNALS);
+
+ SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
+ external_abspath, FALSE,
+ iterpool));
+ if (min_rev != max_rev)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+ NULL,
+ _("Cannot pin external '%s' defined "
+ "in %s at '%s' because '%s' is a "
+ "mixed-revision working copy "
+ "(mixed-revisions cannot be "
+ "represented in %s)"),
+ item->url, SVN_PROP_EXTERNALS,
+ svn_dirent_local_style(
+ local_abspath_or_url, iterpool),
+ svn_dirent_local_style(
+ external_abspath, iterpool),
+ SVN_PROP_EXTERNALS);
+ external_checked_out_rev = min_rev;
+ }
+ else
+ {
+ SVN_ERR_ASSERT(external_kind == svn_node_file);
+ SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
+ NULL, NULL, NULL,
+ ctx->wc_ctx, external_abspath,
+ iterpool, iterpool));
+ }
+
+ external_pegrev.kind = svn_opt_revision_number;
+ external_pegrev.value.number = external_checked_out_rev;
+ }
+ }
+
+ SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
+ external_pegrev.kind == svn_opt_revision_number);
+
+ SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
+ item, info, external_pegrev, iterpool));
+
+ svn_stringbuf_appendcstr(buf, pinned_desc);
+ }
+ svn_pool_destroy(iterpool);
+
+ if (pinned_items > 0)
+ *pinned_externals = svn_string_create_from_buf(buf, result_pool);
+ else
+ *pinned_externals = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths
+ * to svn:externals property values (as const char *), where some or all
+ * external references have been pinned.
+ * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals
+ * mentioned in EXTERNALS_TO_PIN.
+ * The pinning operation takes place as part of the copy operation for
+ * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL
+ * to contact the repository containing the externals definition, if neccesary.
+ * Use CX to fopen additional RA sessions to external repositories, if
+ * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+resolve_pinned_externals(apr_hash_t **pinned_externals,
+ const apr_hash_t *externals_to_pin,
+ svn_client__copy_pair_t *pair,
+ svn_ra_session_t *ra_session,
+ const char *repos_root_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_url = NULL;
+ apr_hash_t *externals_props;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ *pinned_externals = apr_hash_make(result_pool);
+
+ if (svn_path_is_url(pair->src_abspath_or_url))
+ {
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
+ pair->src_abspath_or_url,
+ scratch_pool));
+ externals_props = apr_hash_make(scratch_pool);
+ SVN_ERR(svn_client__remote_propget(externals_props, NULL,
+ SVN_PROP_EXTERNALS,
+ pair->src_abspath_or_url, "",
+ svn_node_dir,
+ pair->src_revnum,
+ ra_session,
+ svn_depth_infinity,
+ scratch_pool,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
+ ctx->wc_ctx,
+ pair->src_abspath_or_url,
+ svn_depth_infinity,
+ scratch_pool, scratch_pool));
+
+ /* ### gather_definitions returns propvals as const char * */
+ for (hi = apr_hash_first(scratch_pool, externals_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath_or_url = apr_hash_this_key(hi);
+ const char *propval = apr_hash_this_val(hi);
+ svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
+
+ svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
+ }
+ }
+
+ if (apr_hash_count(externals_props) == 0)
+ {
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, externals_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *local_abspath_or_url = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *relpath;
+ svn_string_t *new_propval;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
+ externals_to_pin,
+ repos_root_url, local_abspath_or_url, ctx,
+ result_pool, iterpool));
+ if (new_propval)
+ {
+ if (svn_path_is_url(pair->src_abspath_or_url))
+ relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
+ local_abspath_or_url,
+ result_pool);
+ else
+ relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
+ local_abspath_or_url);
+ SVN_ERR_ASSERT(relpath);
+
+ svn_hash_sets(*pinned_externals, relpath, new_propval);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (old_url)
+ SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
/* The guts of do_wc_to_wc_copies */
static svn_error_t *
do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
const char *dst_parent,
+ svn_boolean_t metadata_only,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
@@ -195,22 +696,63 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
const char *dst_abspath;
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
+ apr_hash_t *pinned_externals = NULL;
+
svn_pool_clear(iterpool);
/* Check for cancellation */
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+ if (pin_externals)
+ {
+ const char *repos_root_url;
+
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
+ NULL, NULL, NULL, ctx->wc_ctx,
+ pair->src_abspath_or_url, FALSE,
+ scratch_pool, iterpool));
+ SVN_ERR(resolve_pinned_externals(&pinned_externals,
+ externals_to_pin, pair, NULL,
+ repos_root_url, ctx,
+ iterpool, iterpool));
+ }
+
/* Perform the copy */
dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
iterpool);
*timestamp_sleep = TRUE;
err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
- FALSE /* metadata_only */,
+ metadata_only,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func2, ctx->notify_baton2, iterpool);
if (err)
break;
+
+ if (pinned_externals)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(iterpool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *local_abspath;
+
+ local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
+ dst_relpath, iterpool);
+ /* ### use a work queue? */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, externals_propval,
+ svn_depth_empty, TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* no extra notification */
+ iterpool));
+ }
+ }
}
svn_pool_destroy(iterpool);
@@ -223,6 +765,9 @@ do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
static svn_error_t *
do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
+ svn_boolean_t metadata_only,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -236,7 +781,8 @@ do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
- ctx, pool),
+ metadata_only, pin_externals,
+ externals_to_pin, ctx, pool),
ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
return SVN_NO_ERROR;
@@ -511,10 +1057,24 @@ verify_wc_dsts(const apr_array_header_t *copy_pairs,
ctx->wc_ctx, pair->dst_parent_abspath,
FALSE, TRUE,
iterpool));
- if (make_parents && dst_parent_kind == svn_node_none)
+ if (dst_parent_kind == svn_node_none)
{
- SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
- TRUE, ctx, iterpool));
+ if (make_parents)
+ SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
+ TRUE, ctx, iterpool));
+ else
+ {
+ SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
+ &dst_parent_kind, scratch_pool));
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ (dst_parent_kind == svn_node_dir)
+ ? _("Directory '%s' is not under "
+ "version control")
+ : _("Path '%s' is not a directory"),
+ svn_dirent_local_style(
+ pair->dst_parent_abspath,
+ scratch_pool));
+ }
}
else if (dst_parent_kind != svn_node_dir)
{
@@ -594,6 +1154,8 @@ typedef struct path_driver_info_t
svn_boolean_t resurrection;
svn_boolean_t dir_add;
svn_string_t *mergeinfo; /* the new mergeinfo for the target */
+ svn_string_t *externals; /* new externals definitions for the target */
+ svn_boolean_t only_pin_externals;
} path_driver_info_t;
@@ -631,7 +1193,7 @@ path_driver_cb_func(void **dir_baton,
with such, the code is just plain wrong. */
SVN_ERR_ASSERT(! svn_path_is_empty(path));
- /* Check to see if we need to add the path as a directory. */
+ /* Check to see if we need to add the path as a parent directory. */
if (path_info->dir_add)
{
return cb_baton->editor->add_directory(path, parent_baton, NULL,
@@ -662,7 +1224,7 @@ path_driver_cb_func(void **dir_baton,
/* Not a move? This must just be the copy addition. */
else
{
- do_add = TRUE;
+ do_add = !path_info->only_pin_externals;
}
}
@@ -702,6 +1264,18 @@ path_driver_cb_func(void **dir_baton,
pool));
}
}
+
+ if (path_info->externals)
+ {
+ if (*dir_baton == NULL)
+ SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
+ SVN_INVALID_REVNUM,
+ pool, dir_baton));
+
+ SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
+ path_info->externals, pool));
+ }
+
return SVN_NO_ERROR;
}
@@ -786,6 +1360,79 @@ find_absent_parents2(svn_ra_session_t *ra_session,
return SVN_NO_ERROR;
}
+/* Queue property changes for pinning svn:externals properties set on
+ * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
+ * is keyed by the relative path of each descendant which should have some
+ * or all of its externals pinned, with the corresponding pinned svn:externals
+ * properties as values. Property changes are queued in a new list of path
+ * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
+ * existing item is found for the descendant. Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
+ apr_array_header_t *path_infos,
+ apr_hash_t *pinned_externals,
+ path_driver_info_t *parent_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_prop = apr_hash_this_val(hi);
+ const char *src_url;
+ path_driver_info_t *info;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ src_url = svn_path_url_add_component2(parent_info->src_url,
+ dst_relpath, iterpool);
+
+ /* Try to find a path info the external change can be applied to. */
+ info = NULL;
+ for (i = 0; i < path_infos->nelts; i++)
+ {
+ path_driver_info_t *existing_info;
+
+ existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
+ if (strcmp(src_url, existing_info->src_url) == 0)
+ {
+ info = existing_info;
+ break;
+ }
+ }
+
+ if (info == NULL)
+ {
+ /* A copied-along child needs its externals pinned.
+ Create a new path info for this property change. */
+ info = apr_pcalloc(result_pool, sizeof(*info));
+ info->src_url = svn_path_url_add_component2(
+ parent_info->src_url, dst_relpath,
+ result_pool);
+ info->src_path = NULL; /* Only needed on copied dirs */
+ info->dst_path = svn_relpath_join(parent_info->dst_path,
+ dst_relpath,
+ result_pool);
+ info->src_kind = svn_node_dir;
+ info->only_pin_externals = TRUE;
+ APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
+ }
+
+ info->externals = externals_prop;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
repos_to_repos_copy(const apr_array_header_t *copy_pairs,
svn_boolean_t make_parents,
@@ -794,6 +1441,8 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
void *commit_baton,
svn_client_ctx_t *ctx,
svn_boolean_t is_move,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
apr_pool_t *pool)
{
svn_error_t *err;
@@ -809,6 +1458,7 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
struct path_driver_cb_baton cb_baton;
apr_array_header_t *new_dirs = NULL;
apr_hash_t *commit_revprops;
+ apr_array_header_t *pin_externals_only_infos = NULL;
int i;
svn_client__copy_pair_t *first_pair =
APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
@@ -1006,7 +1656,10 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
&& (relpath != NULL && *relpath != '\0'))
{
info->resurrection = TRUE;
- top_url = svn_uri_dirname(top_url, pool);
+ top_url = svn_uri_get_longest_ancestor(
+ top_url,
+ svn_uri_dirname(pair->dst_abspath_or_url, pool),
+ pool);
SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
}
}
@@ -1058,16 +1711,40 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
&dst_kind, pool));
if (dst_kind != svn_node_none)
- return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
- _("Path '%s' already exists"), dst_rel);
+ {
+ const char *path = svn_uri_skip_ancestor(repos_root,
+ pair->dst_abspath_or_url,
+ pool);
+ return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Path '/%s' already exists"), path);
+ }
/* More info for our INFO structure. */
- info->src_path = src_rel;
+ info->src_path = src_rel; /* May be NULL, if outside RA session scope */
info->dst_path = dst_rel;
svn_hash_sets(action_hash, info->dst_path, info);
if (is_move && (! info->resurrection))
svn_hash_sets(action_hash, info->src_path, info);
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+
+ SVN_ERR(resolve_pinned_externals(&pinned_externals,
+ externals_to_pin, pair,
+ ra_session, repos_root,
+ ctx, pool, pool));
+ if (pin_externals_only_infos == NULL)
+ {
+ pin_externals_only_infos =
+ apr_array_make(pool, 0, sizeof(path_driver_info_t *));
+ }
+ SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
+ path_infos,
+ pinned_externals,
+ info, pool, pool));
+ }
}
if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
@@ -1088,6 +1765,7 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
item = svn_client_commit_item3_create(pool);
item->url = svn_path_url_add_component2(top_url, relpath, pool);
+ item->kind = svn_node_dir;
item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
}
@@ -1101,14 +1779,19 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
item = svn_client_commit_item3_create(pool);
item->url = svn_path_url_add_component2(top_url, info->dst_path,
pool);
- item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ item->kind = info->src_kind;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
+ | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
+ item->copyfrom_url = info->src_url;
+ item->copyfrom_rev = info->src_revnum;
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
if (is_move && (! info->resurrection))
{
- item = apr_pcalloc(pool, sizeof(*item));
+ item = svn_client_commit_item3_create(pool);
item->url = svn_path_url_add_component2(top_url, info->src_path,
pool);
+ item->kind = info->src_kind;
item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
}
@@ -1150,6 +1833,19 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
APR_ARRAY_PUSH(paths, const char *) = info->src_path;
}
+ /* Add any items which only need their externals pinned. */
+ if (pin_externals_only_infos)
+ {
+ for (i = 0; i < pin_externals_only_infos->nelts; i++)
+ {
+ path_driver_info_t *info;
+
+ info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
+ APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
+ svn_hash_sets(action_hash, info->dst_path, info);
+ }
+ }
+
SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
message, ctx, pool));
@@ -1181,6 +1877,15 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
editor->abort_edit(edit_baton, pool));
}
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+ notify = svn_wc_create_notify_url(top_url,
+ svn_wc_notify_commit_finalizing,
+ pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, pool);
+ }
+
/* Close the edit. */
return svn_error_trace(editor->close_edit(edit_baton, pool));
}
@@ -1219,6 +1924,65 @@ check_url_kind(void *baton,
return SVN_NO_ERROR;
}
+/* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
+ * in the COMMIT_ITEMS list.
+ * If the list does not already have a commit item for COMMIT_URL
+ * add a new commit item for the property change.
+ * Allocate results in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+queue_prop_change_commit_items(const char *local_abspath,
+ const char *commit_url,
+ apr_array_header_t *commit_items,
+ const char *propname,
+ svn_string_t *propval,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_commit_item3_t *item = NULL;
+ svn_prop_t *prop;
+ int i;
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *existing_item;
+
+ existing_item = APR_ARRAY_IDX(commit_items, i,
+ svn_client_commit_item3_t *);
+ if (strcmp(existing_item->url, commit_url) == 0)
+ {
+ item = existing_item;
+ break;
+ }
+ }
+
+ if (item == NULL)
+ {
+ item = svn_client_commit_item3_create(result_pool);
+ item->path = local_abspath;
+ item->url = commit_url;
+ item->kind = svn_node_dir;
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+
+ item->incoming_prop_changes = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_t *));
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+ }
+ else
+ item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
+
+ if (item->outgoing_prop_changes == NULL)
+ item->outgoing_prop_changes = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_t *));
+
+ prop = apr_palloc(result_pool, sizeof(*prop));
+ prop->name = propname;
+ prop->value = propval;
+ APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
+
+ return SVN_NO_ERROR;
+}
+
/* ### Copy ...
* COMMIT_INFO_P is ...
* COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
@@ -1232,6 +1996,8 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
void *commit_baton,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
@@ -1241,7 +2007,9 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
const char *top_src_abspath;
svn_ra_session_t *ra_session;
const svn_delta_editor_t *editor;
+#ifdef ENABLE_EV2_SHIMS
apr_hash_t *relpath_map = NULL;
+#endif
void *edit_baton;
svn_client__committables_t *committables;
apr_array_header_t *commit_items;
@@ -1250,6 +2018,7 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
apr_hash_t *commit_revprops;
svn_client__copy_pair_t *first_pair;
apr_pool_t *session_pool = svn_pool_create(scratch_pool);
+ apr_array_header_t *commit_items_for_dav;
int i;
/* Find the common root of all the source paths */
@@ -1284,9 +2053,13 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
+ commit_items_for_dav = apr_array_make(session_pool, 0,
+ sizeof(svn_client_commit_item3_t*));
+
/* Open a session to help while determining the exact targets */
SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
- top_src_abspath, NULL,
+ top_src_abspath,
+ commit_items_for_dav,
FALSE /* write_dav_props */,
TRUE /* read_dav_props */,
ctx,
@@ -1323,52 +2096,6 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
}
}
- if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
- {
- /* Produce a list of new paths to add, and provide it to the
- mechanism used to acquire a log message. */
- svn_client_commit_item3_t *item;
- const char *tmp_file;
- commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
- sizeof(item));
-
- /* Add any intermediate directories to the message */
- if (make_parents)
- {
- for (i = 0; i < new_dirs->nelts; i++)
- {
- const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
-
- item = svn_client_commit_item3_create(scratch_pool);
- item->url = url;
- item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
- APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
- }
- }
-
- for (i = 0; i < copy_pairs->nelts; i++)
- {
- svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
- svn_client__copy_pair_t *);
-
- item = svn_client_commit_item3_create(scratch_pool);
- item->url = pair->dst_abspath_or_url;
- item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
- APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
- }
-
- SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
- ctx, scratch_pool));
- if (! message)
- {
- svn_pool_destroy(iterpool);
- svn_pool_destroy(session_pool);
- return SVN_NO_ERROR;
- }
- }
- else
- message = "";
-
cukb.session = ra_session;
SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
cukb.should_reparent = FALSE;
@@ -1399,6 +2126,7 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
item = svn_client_commit_item3_create(scratch_pool);
item->url = url;
+ item->kind = svn_node_dir;
item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
sizeof(svn_prop_t *));
@@ -1426,8 +2154,6 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
/* Set the mergeinfo for the destination to the combined merge
info known to the WC and the repository. */
- item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
- sizeof(svn_prop_t *));
/* Repository mergeinfo (or NULL if it's locally added)... */
if (src_origin)
SVN_ERR(svn_client__get_repos_mergeinfo(
@@ -1444,34 +2170,97 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
iterpool));
else if (! mergeinfo)
mergeinfo = wc_mergeinfo;
+
if (mergeinfo)
{
/* Push a mergeinfo prop representing MERGEINFO onto the
* OUTGOING_PROP_CHANGES array. */
svn_prop_t *mergeinfo_prop
- = apr_palloc(item->outgoing_prop_changes->pool,
- sizeof(svn_prop_t));
+ = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
svn_string_t *prop_value;
SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
- item->outgoing_prop_changes->pool));
+ scratch_pool));
+
+ if (!item->outgoing_prop_changes)
+ {
+ item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
+ sizeof(svn_prop_t *));
+ }
mergeinfo_prop->name = SVN_PROP_MERGEINFO;
mergeinfo_prop->value = prop_value;
APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
= mergeinfo_prop;
}
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(resolve_pinned_externals(&pinned_externals,
+ externals_to_pin, pair,
+ ra_session, cukb.repos_root_url,
+ ctx, scratch_pool, iterpool));
+ for (hi = apr_hash_first(scratch_pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *dst_url;
+ const char *commit_url;
+ const char *src_abspath;
+
+ if (svn_path_is_url(pair->dst_abspath_or_url))
+ dst_url = pair->dst_abspath_or_url;
+ else
+ SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
+ pair->dst_abspath_or_url,
+ scratch_pool, iterpool));
+ commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
+ scratch_pool);
+ src_abspath = svn_dirent_join(pair->src_abspath_or_url,
+ dst_relpath, iterpool);
+ SVN_ERR(queue_prop_change_commit_items(src_abspath,
+ commit_url, commit_items,
+ SVN_PROP_EXTERNALS,
+ externals_propval,
+ scratch_pool, iterpool));
+ }
+ }
+ }
+
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ const char *tmp_file;
+
+ SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
+ ctx, scratch_pool));
+ if (! message)
+ {
+ svn_pool_destroy(iterpool);
+ svn_pool_destroy(session_pool);
+ return SVN_NO_ERROR;
+ }
}
+ else
+ message = "";
/* Sort and condense our COMMIT_ITEMS. */
SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
commit_items, scratch_pool));
+ /* Add the commit items to the DAV commit item list to provide access
+ to dav properties (for pre http-v2 DAV) */
+ apr_array_cat(commit_items_for_dav, commit_items);
+
#ifdef ENABLE_EV2_SHIMS
if (commit_items)
{
- relpath_map = apr_hash_make(pool);
+ relpath_map = apr_hash_make(scratch_pool);
for (i = 0; i < commit_items->nelts; i++)
{
svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
@@ -1482,7 +2271,8 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
continue;
svn_pool_clear(iterpool);
- SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
+ NULL, NULL,
ctx->wc_ctx, item->path, FALSE,
scratch_pool, iterpool));
if (relpath)
@@ -1491,22 +2281,17 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
}
#endif
- /* Close the initial session, to reopen a new session with commit handling */
- svn_pool_clear(session_pool);
-
- /* Open a new RA session to DST_URL. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
- NULL, commit_items,
- FALSE, FALSE, ctx,
- session_pool, session_pool));
+ SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
message, ctx, session_pool));
/* Fetch RA commit editor. */
+#ifdef ENABLE_EV2_SHIMS
SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
session_pool)));
+#endif
SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
commit_revprops,
commit_callback,
@@ -1517,7 +2302,7 @@ wc_to_repos_copy(const apr_array_header_t *copy_pairs,
/* Perform the commit. */
SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
editor, edit_baton,
- 0, /* ### any notify_path_offset needed? */
+ NULL /* notify_path_prefix */,
NULL, ctx, session_pool, session_pool),
_("Commit failed (details follow):"));
@@ -1565,6 +2350,8 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
svn_client__copy_pair_t *pair,
svn_boolean_t same_repositories,
svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
@@ -1593,7 +2380,6 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
{
if (same_repositories)
{
- svn_boolean_t sleep_needed = FALSE;
const char *tmpdir_abspath, *tmp_abspath;
/* Find a temporary location in which to check out the copy source. */
@@ -1622,14 +2408,22 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
ctx->notify_func2 = notification_adjust_func;
ctx->notify_baton2 = &nb;
- err = svn_client__checkout_internal(&pair->src_revnum,
+ /* Avoid a chicken-and-egg problem:
+ * If pinning externals we'll need to adjust externals
+ * properties before checking out any externals.
+ * But copy needs to happen before pinning because else there
+ * are no svn:externals properties to pin. */
+ if (pin_externals)
+ ignore_externals = TRUE;
+
+ err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
pair->src_original,
tmp_abspath,
&pair->src_peg_revision,
&pair->src_op_revision,
svn_depth_infinity,
ignore_externals, FALSE,
- &sleep_needed, ctx, pool);
+ ra_session, ctx, pool);
ctx->notify_func2 = old_notify_func2;
ctx->notify_baton2 = old_notify_baton2;
@@ -1674,6 +2468,61 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
return SVN_NO_ERROR;
}
+
+ if (pin_externals)
+ {
+ apr_hash_t *pinned_externals;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ const char *repos_root_url;
+ apr_hash_t *new_externals;
+ apr_hash_t *new_depths;
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
+ SVN_ERR(resolve_pinned_externals(&pinned_externals,
+ externals_to_pin, pair,
+ ra_session, repos_root_url,
+ ctx, pool, pool));
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+
+ local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
+ dst_relpath, iterpool);
+ /* ### use a work queue? */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, externals_propval,
+ svn_depth_empty, TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* no extra notification */
+ iterpool));
+ }
+
+ /* Now update all externals in the newly created copy. */
+ SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
+ &new_depths,
+ ctx->wc_ctx,
+ dst_abspath,
+ svn_depth_infinity,
+ iterpool, iterpool));
+ SVN_ERR(svn_client__handle_externals(new_externals,
+ new_depths,
+ repos_root_url, dst_abspath,
+ svn_depth_infinity,
+ timestamp_sleep,
+ ra_session,
+ ctx, iterpool));
+ svn_pool_destroy(iterpool);
+ }
} /* end directory case */
else if (pair->src_kind == svn_node_file)
@@ -1722,7 +2571,7 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
svn_wc_notify_t *notify = svn_wc_create_notify(
dst_abspath, svn_wc_notify_add, pool);
notify->kind = pair->src_kind;
- (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, pool);
}
return SVN_NO_ERROR;
@@ -1733,6 +2582,8 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
const char *top_dst_path,
svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
@@ -1798,6 +2649,7 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
svn_client__copy_pair_t *),
same_repositories,
ignore_externals,
+ pin_externals, externals_to_pin,
ra_session, ctx, iterpool));
}
svn_pool_destroy(iterpool);
@@ -1810,6 +2662,8 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
svn_boolean_t make_parents,
svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -1918,6 +2772,7 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
SVN_WC__CALL_WITH_WRITE_LOCK(
repos_to_wc_copy_locked(timestamp_sleep,
copy_pairs, top_dst_path, ignore_externals,
+ pin_externals, externals_to_pin,
ra_session, ctx, pool),
ctx->wc_ctx, lock_abspath, FALSE, pool);
return SVN_NO_ERROR;
@@ -1944,6 +2799,8 @@ try_copy(svn_boolean_t *timestamp_sleep,
svn_boolean_t metadata_only,
svn_boolean_t make_parents,
svn_boolean_t ignore_externals,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
void *commit_baton,
@@ -1956,6 +2813,9 @@ try_copy(svn_boolean_t *timestamp_sleep,
svn_boolean_t srcs_are_urls, dst_is_url;
int i;
+ /* Assert instead of crashing if the sources list is empty. */
+ SVN_ERR_ASSERT(sources->nelts > 0);
+
/* Are either of our paths URLs? Just check the first src_path. If
there are more than one, we'll check for homogeneity among them
down below. */
@@ -1976,7 +2836,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
{
svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
svn_client_copy_source_t *);
- svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
+ svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
const char *src_basename;
svn_boolean_t src_is_url = svn_path_is_url(source->path);
@@ -1998,6 +2858,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
pair->src_op_revision = *source->revision;
pair->src_peg_revision = *source->peg_revision;
+ pair->src_kind = svn_node_unknown;
SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
&pair->src_op_revision,
@@ -2026,7 +2887,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
else
{
/* Only one source path. */
- svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
+ svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
svn_client_copy_source_t *source =
APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
svn_boolean_t src_is_url = svn_path_is_url(source->path);
@@ -2038,6 +2899,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
source->path, pool));
pair->src_op_revision = *source->revision;
pair->src_peg_revision = *source->peg_revision;
+ pair->src_kind = svn_node_unknown;
SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
&pair->src_op_revision,
@@ -2184,7 +3046,7 @@ try_copy(svn_boolean_t *timestamp_sleep,
SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
&copyfrom_repos_relpath,
&copyfrom_repos_root_url,
- NULL, NULL,
+ NULL, NULL, NULL,
ctx->wc_ctx,
pair->src_abspath_or_url,
TRUE, iterpool, iterpool));
@@ -2239,30 +3101,35 @@ try_copy(svn_boolean_t *timestamp_sleep,
else
{
/* We ignore these values, so assert the default value */
- SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
+ SVN_ERR_ASSERT(allow_mixed_revisions);
return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
- copy_pairs, ctx, pool));
+ copy_pairs,
+ metadata_only,
+ pin_externals,
+ externals_to_pin,
+ ctx, pool));
}
}
else if ((! srcs_are_urls) && (dst_is_url))
{
return svn_error_trace(
wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
- commit_callback, commit_baton, ctx, pool));
+ commit_callback, commit_baton,
+ pin_externals, externals_to_pin, ctx, pool));
}
else if ((srcs_are_urls) && (! dst_is_url))
{
return svn_error_trace(
repos_to_wc_copy(timestamp_sleep,
copy_pairs, make_parents, ignore_externals,
- ctx, pool));
+ pin_externals, externals_to_pin, ctx, pool));
}
else
{
return svn_error_trace(
repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
commit_callback, commit_baton, ctx, is_move,
- pool));
+ pin_externals, externals_to_pin, pool));
}
}
@@ -2270,11 +3137,14 @@ try_copy(svn_boolean_t *timestamp_sleep,
/* Public Interfaces */
svn_error_t *
-svn_client_copy6(const apr_array_header_t *sources,
+svn_client_copy7(const apr_array_header_t *sources,
const char *dst_path,
svn_boolean_t copy_as_child,
svn_boolean_t make_parents,
svn_boolean_t ignore_externals,
+ svn_boolean_t metadata_only,
+ svn_boolean_t pin_externals,
+ const apr_hash_t *externals_to_pin,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
void *commit_baton,
@@ -2293,9 +3163,11 @@ svn_client_copy6(const apr_array_header_t *sources,
sources, dst_path,
FALSE /* is_move */,
TRUE /* allow_mixed_revisions */,
- FALSE /* metadata_only */,
+ metadata_only,
make_parents,
ignore_externals,
+ pin_externals,
+ externals_to_pin,
revprop_table,
commit_callback, commit_baton,
ctx,
@@ -2327,9 +3199,11 @@ svn_client_copy6(const apr_array_header_t *sources,
sources, dst_path,
FALSE /* is_move */,
TRUE /* allow_mixed_revisions */,
- FALSE /* metadata_only */,
+ metadata_only,
make_parents,
ignore_externals,
+ pin_externals,
+ externals_to_pin,
revprop_table,
commit_callback, commit_baton,
ctx,
@@ -2391,6 +3265,8 @@ svn_client_move7(const apr_array_header_t *src_paths,
metadata_only,
make_parents,
FALSE /* ignore_externals */,
+ FALSE /* pin_externals */,
+ NULL /* externals_to_pin */,
revprop_table,
commit_callback, commit_baton,
ctx,
@@ -2424,6 +3300,8 @@ svn_client_move7(const apr_array_header_t *src_paths,
metadata_only,
make_parents,
FALSE /* ignore_externals */,
+ FALSE /* pin_externals */,
+ NULL /* externals_to_pin */,
revprop_table,
commit_callback, commit_baton,
ctx,