diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_client/copy.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_client/copy.c')
-rw-r--r-- | subversion/libsvn_client/copy.c | 1060 |
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, ©from_rev, ©from_repos_relpath, ©from_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, |