blob: 18bc2404982f8a523e6f9ee1db59d9ae354603f0 [file] [log] [blame]
/*
* copy.c: copy/move wrappers around wc 'copy' functionality.
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <string.h>
#include "svn_hash.h"
#include "svn_client.h"
#include "svn_error.h"
#include "svn_error_codes.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_opt.h"
#include "svn_time.h"
#include "svn_props.h"
#include "svn_mergeinfo.h"
#include "svn_pools.h"
#include "client.h"
#include "mergeinfo.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
#include "private/svn_ra_private.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_client_private.h"
/*
* OUR BASIC APPROACH TO COPIES
* ============================
*
* for each source/destination pair
* if (not exist src_path)
* return ERR_BAD_SRC error
*
* if (exist dst_path)
* return ERR_OBSTRUCTION error
* else
* copy src_path into parent_of_dst_path as basename (dst_path)
*
* if (this is a move)
* delete src_path
*/
/*** Code. ***/
/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
MERGEINFO to any mergeinfo pre-existing in the WC. */
static svn_error_t *
extend_wc_mergeinfo(const char *target_abspath,
apr_hash_t *mergeinfo,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
apr_hash_t *wc_mergeinfo;
/* Get a fresh copy of the pre-existing state of the WC's mergeinfo
updating it. */
SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
target_abspath, pool, pool));
/* Combine the provided mergeinfo with any mergeinfo from the WC. */
if (wc_mergeinfo && mergeinfo)
SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
else if (! wc_mergeinfo)
wc_mergeinfo = mergeinfo;
return svn_error_trace(
svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
FALSE, ctx, pool));
}
/* Find the longest common ancestor of paths in COPY_PAIRS. If
SRC_ANCESTOR is NULL, ignore source paths in this calculation. If
DST_ANCESTOR is NULL, ignore destination paths in this calculation.
COMMON_ANCESTOR will be the common ancestor of both the
SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
NULL.
*/
static svn_error_t *
get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
const char **src_ancestor,
const char **dst_ancestor,
const char **common_ancestor,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
svn_client__copy_pair_t *first;
const char *first_dst;
const char *first_src;
const char *top_dst;
svn_boolean_t src_is_url;
svn_boolean_t dst_is_url;
char *top_src;
int i;
first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
/* Because all the destinations are in the same directory, we can easily
determine their common ancestor. */
first_dst = first->dst_abspath_or_url;
dst_is_url = svn_path_is_url(first_dst);
if (copy_pairs->nelts == 1)
top_dst = apr_pstrdup(subpool, first_dst);
else
top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
: svn_dirent_dirname(first_dst, subpool);
/* Sources can came from anywhere, so we have to actually do some
work for them. */
first_src = first->src_abspath_or_url;
src_is_url = svn_path_is_url(first_src);
top_src = apr_pstrdup(subpool, first_src);
for (i = 1; i < copy_pairs->nelts; i++)
{
/* We don't need to clear the subpool here for several reasons:
1) If we do, we can't use it to allocate the initial versions of
top_src and top_dst (above).
2) We don't return any errors in the following loop, so we
are guanteed to destroy the subpool at the end of this function.
3) The number of iterations is likely to be few, and the loop will
be through quickly, so memory leakage will not be significant,
in time or space.
*/
const svn_client__copy_pair_t *pair =
APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
top_src = src_is_url
? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
subpool)
: svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
subpool);
}
if (src_ancestor)
*src_ancestor = apr_pstrdup(pool, top_src);
if (dst_ancestor)
*dst_ancestor = apr_pstrdup(pool, top_dst);
if (common_ancestor)
*common_ancestor =
src_is_url
? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
: svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
svn_pool_destroy(subpool);
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 necessary.
* Use CX to fopen additional RA sessions to external repositories, if
* necessary. 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,
const 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)
{
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
svn_error_t *err = SVN_NO_ERROR;
for (i = 0; i < copy_pairs->nelts; i++)
{
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,
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);
SVN_ERR(err);
return SVN_NO_ERROR;
}
/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
allocations. */
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)
{
const char *dst_parent, *dst_parent_abspath;
SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
if (copy_pairs->nelts == 1)
dst_parent = svn_dirent_dirname(dst_parent, pool);
SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
metadata_only, pin_externals,
externals_to_pin, ctx, pool),
ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
return SVN_NO_ERROR;
}
/* The locked bit of do_wc_to_wc_moves. */
static svn_error_t *
do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
const char *dst_parent_abspath,
svn_boolean_t lock_src,
svn_boolean_t lock_dst,
svn_boolean_t allow_mixed_revisions,
svn_boolean_t metadata_only,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *dst_abspath;
dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
scratch_pool);
SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
dst_abspath, metadata_only,
allow_mixed_revisions,
ctx->cancel_func, ctx->cancel_baton,
ctx->notify_func2, ctx->notify_baton2,
scratch_pool));
return SVN_NO_ERROR;
}
/* Wrapper to add an optional second lock */
static svn_error_t *
do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
const char *dst_parent_abspath,
svn_boolean_t lock_src,
svn_boolean_t lock_dst,
svn_boolean_t allow_mixed_revisions,
svn_boolean_t metadata_only,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
if (lock_dst)
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
lock_dst, allow_mixed_revisions,
metadata_only,
ctx, scratch_pool),
ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
else
SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
lock_dst, allow_mixed_revisions,
metadata_only,
ctx, scratch_pool));
return SVN_NO_ERROR;
}
/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
afterwards. Use POOL for temporary allocations. */
static svn_error_t *
do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
const char *dst_path,
svn_boolean_t allow_mixed_revisions,
svn_boolean_t metadata_only,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
int i;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_error_t *err = SVN_NO_ERROR;
for (i = 0; i < copy_pairs->nelts; i++)
{
const char *src_parent_abspath;
svn_boolean_t lock_src, lock_dst;
const char *src_wcroot_abspath;
const char *dst_wcroot_abspath;
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
svn_pool_clear(iterpool);
/* Check for cancellation */
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
iterpool);
SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
ctx->wc_ctx, src_parent_abspath,
iterpool, iterpool));
SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
ctx->wc_ctx, pair->dst_parent_abspath,
iterpool, iterpool));
/* We now need to lock the right combination of batons.
Four cases:
1) src_parent == dst_parent
2) src_parent is parent of dst_parent
3) dst_parent is parent of src_parent
4) src_parent and dst_parent are disjoint
We can handle 1) as either 2) or 3) */
if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
|| (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
NULL)
&& !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
NULL)))
{
lock_src = TRUE;
lock_dst = FALSE;
}
else if (svn_dirent_is_child(pair->dst_parent_abspath,
src_parent_abspath, NULL)
&& !svn_dirent_is_child(pair->dst_parent_abspath,
src_wcroot_abspath, NULL))
{
lock_src = FALSE;
lock_dst = TRUE;
}
else
{
lock_src = TRUE;
lock_dst = TRUE;
}
*timestamp_sleep = TRUE;
/* Perform the copy and then the delete. */
if (lock_src)
SVN_WC__CALL_WITH_WRITE_LOCK(
do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
lock_src, lock_dst,
allow_mixed_revisions,
metadata_only,
ctx, iterpool),
ctx->wc_ctx, src_parent_abspath,
FALSE, iterpool);
else
SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
lock_src, lock_dst,
allow_mixed_revisions,
metadata_only,
ctx, iterpool));
}
svn_pool_destroy(iterpool);
return svn_error_trace(err);
}
/* Verify that the destinations stored in COPY_PAIRS are valid working copy
destinations and set pair->dst_parent_abspath and pair->base_name for each
item to the resulting location if they do */
static svn_error_t *
verify_wc_dsts(const apr_array_header_t *copy_pairs,
svn_boolean_t make_parents,
svn_boolean_t is_move,
svn_boolean_t metadata_only,
svn_client_ctx_t *ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* Check that DST does not exist, but its parent does */
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 *);
svn_node_kind_t dst_kind, dst_parent_kind;
svn_pool_clear(iterpool);
/* If DST_PATH does not exist, then its basename will become a new
file or dir added to its parent (possibly an implicit '.').
Else, just error out. */
SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
pair->dst_abspath_or_url,
FALSE /* show_deleted */,
TRUE /* show_hidden */,
iterpool));
if (dst_kind != svn_node_none)
{
svn_boolean_t is_excluded;
svn_boolean_t is_server_excluded;
SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
&is_server_excluded, ctx->wc_ctx,
pair->dst_abspath_or_url, FALSE,
iterpool));
if (is_excluded || is_server_excluded)
{
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE,
NULL, _("Path '%s' exists, but is excluded"),
svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
}
else
return svn_error_createf(
SVN_ERR_ENTRY_EXISTS, NULL,
_("Path '%s' already exists"),
svn_dirent_local_style(pair->dst_abspath_or_url,
scratch_pool));
}
/* Check that there is no unversioned obstruction */
if (metadata_only)
dst_kind = svn_node_none;
else
SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
iterpool));
if (dst_kind != svn_node_none)
{
if (is_move
&& copy_pairs->nelts == 1
&& strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
svn_dirent_dirname(pair->dst_abspath_or_url,
iterpool)) == 0)
{
const char *dst;
char *dst_apr;
apr_status_t apr_err;
/* We have a rename inside a directory, which might collide
just because the case insensivity of the filesystem makes
the source match the destination. */
SVN_ERR(svn_path_cstring_from_utf8(&dst,
pair->dst_abspath_or_url,
scratch_pool));
apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
APR_FILEPATH_TRUENAME, iterpool);
if (!apr_err)
{
/* And now bring it back to our canonical format */
SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
dst = svn_dirent_canonicalize(dst, iterpool);
}
/* else: Don't report this error; just report the normal error */
if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
{
/* Ok, we have a single case only rename. Get out of here */
svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
pair->dst_abspath_or_url, result_pool);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
}
return svn_error_createf(
SVN_ERR_ENTRY_EXISTS, NULL,
_("Path '%s' already exists as unversioned node"),
svn_dirent_local_style(pair->dst_abspath_or_url,
scratch_pool));
}
svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
pair->dst_abspath_or_url, result_pool);
/* Make sure the destination parent is a directory and produce a clear
error message if it is not. */
SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
ctx->wc_ctx, pair->dst_parent_abspath,
FALSE, TRUE,
iterpool));
if (dst_parent_kind == svn_node_none)
{
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)
{
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("Path '%s' is not a directory"),
svn_dirent_local_style(
pair->dst_parent_abspath, scratch_pool));
}
SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
&dst_parent_kind, scratch_pool));
if (dst_parent_kind != svn_node_dir)
return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
_("Path '%s' is not a directory"),
svn_dirent_local_style(
pair->dst_parent_abspath, scratch_pool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Verify that the WC sources in COPY_PAIRS exist, and set pair->src_kind
for each.
*/
static svn_error_t *
verify_wc_srcs(const apr_array_header_t *copy_pairs,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* Check that all of our SRCs exist. */
for (i = 0; i < copy_pairs->nelts; i++)
{
svn_boolean_t deleted_ok;
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
svn_pool_clear(iterpool);
deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
|| pair->src_op_revision.kind == svn_opt_revision_base);
/* Verify that SRC_PATH exists. */
SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
pair->src_abspath_or_url,
deleted_ok, FALSE, iterpool));
if (pair->src_kind == svn_node_none)
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("Path '%s' does not exist"),
svn_dirent_local_style(
pair->src_abspath_or_url,
scratch_pool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Path-specific state used as part of path_driver_cb_baton. */
typedef struct path_driver_info_t
{
const char *src_url;
const char *src_path;
const char *dst_path;
svn_node_kind_t src_kind;
svn_revnum_t src_revnum;
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;
/* The baton used with the path_driver_cb_func() callback for a copy
or move operation. */
struct path_driver_cb_baton
{
/* A hash of path -> path_driver_info_t *'s. */
apr_hash_t *action_hash;
/* Whether the operation is a move or copy. */
svn_boolean_t is_move;
};
static svn_error_t *
path_driver_cb_func(void **dir_baton,
const svn_delta_editor_t *editor,
void *edit_baton,
void *parent_baton,
void *callback_baton,
const char *path,
apr_pool_t *pool)
{
struct path_driver_cb_baton *cb_baton = callback_baton;
svn_boolean_t do_delete = FALSE, do_add = FALSE;
path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
/* Initialize return value. */
*dir_baton = NULL;
/* This function should never get an empty PATH. We can neither
create nor delete the empty PATH, so if someone is calling us
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 parent directory. */
if (path_info->dir_add)
{
return editor->add_directory(path, parent_baton, NULL,
SVN_INVALID_REVNUM, pool,
dir_baton);
}
/* If this is a resurrection, we know the source and dest paths are
the same, and that our driver will only be calling us once. */
if (path_info->resurrection)
{
/* If this is a move, we do nothing. Otherwise, we do the copy. */
if (! cb_baton->is_move)
do_add = TRUE;
}
/* Not a resurrection. */
else
{
/* If this is a move, we check PATH to see if it is the source
or the destination of the move. */
if (cb_baton->is_move)
{
if (strcmp(path_info->src_path, path) == 0)
do_delete = TRUE;
else
do_add = TRUE;
}
/* Not a move? This must just be the copy addition. */
else
{
do_add = !path_info->only_pin_externals;
}
}
if (do_delete)
{
SVN_ERR(editor->delete_entry(path, SVN_INVALID_REVNUM,
parent_baton, pool));
}
if (do_add)
{
SVN_ERR(svn_path_check_valid(path, pool));
if (path_info->src_kind == svn_node_file)
{
void *file_baton;
SVN_ERR(editor->add_file(path, parent_baton,
path_info->src_url,
path_info->src_revnum,
pool, &file_baton));
if (path_info->mergeinfo)
SVN_ERR(editor->change_file_prop(file_baton,
SVN_PROP_MERGEINFO,
path_info->mergeinfo,
pool));
SVN_ERR(editor->close_file(file_baton, NULL, pool));
}
else
{
SVN_ERR(editor->add_directory(path, parent_baton,
path_info->src_url,
path_info->src_revnum,
pool, dir_baton));
if (path_info->mergeinfo)
SVN_ERR(editor->change_dir_prop(*dir_baton,
SVN_PROP_MERGEINFO,
path_info->mergeinfo,
pool));
}
}
if (path_info->externals)
{
if (*dir_baton == NULL)
SVN_ERR(editor->open_directory(path, parent_baton,
SVN_INVALID_REVNUM,
pool, dir_baton));
SVN_ERR(editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
path_info->externals, pool));
}
return SVN_NO_ERROR;
}
/* Starting with the path DIR relative to the RA_SESSION's session
URL, work up through DIR's parents until an existing node is found.
Push each nonexistent path onto the array NEW_DIRS, allocating in
POOL. Raise an error if the existing node is not a directory.
### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
### implementation susceptible to race conditions. */
static svn_error_t *
find_absent_parents1(svn_ra_session_t *ra_session,
const char *dir,
apr_array_header_t *new_dirs,
apr_pool_t *pool)
{
svn_node_kind_t kind;
apr_pool_t *iterpool = svn_pool_create(pool);
SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
iterpool));
while (kind == svn_node_none)
{
svn_pool_clear(iterpool);
APR_ARRAY_PUSH(new_dirs, const char *) = dir;
dir = svn_dirent_dirname(dir, pool);
SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
&kind, iterpool));
}
if (kind != svn_node_dir)
return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
_("Path '%s' already exists, but is not a "
"directory"), dir);
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Starting with the URL *TOP_DST_URL which is also the root of
RA_SESSION, work up through its parents until an existing node is
found. Push each nonexistent URL onto the array NEW_DIRS,
allocating in POOL. Raise an error if the existing node is not a
directory.
Set *TOP_DST_URL and the RA session's root to the existing node's URL.
### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
### implementation susceptible to race conditions. */
static svn_error_t *
find_absent_parents2(svn_ra_session_t *ra_session,
const char **top_dst_url,
apr_array_header_t *new_dirs,
apr_pool_t *pool)
{
const char *root_url = *top_dst_url;
svn_node_kind_t kind;
SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
pool));
while (kind == svn_node_none)
{
APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
root_url = svn_uri_dirname(root_url, pool);
SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
pool));
}
if (kind != svn_node_dir)
return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
_("Path '%s' already exists, but is not a directory"),
root_url);
*top_dst_url = root_url;
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,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
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;
apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
sizeof(const char *));
apr_hash_t *action_hash = apr_hash_make(pool);
apr_array_header_t *path_infos;
const char *top_url, *top_url_all, *top_url_dst;
const char *message, *repos_root;
svn_ra_session_t *ra_session = NULL;
const svn_delta_editor_t *editor;
void *edit_baton;
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 *);
/* Open an RA session to the first copy pair's destination. We'll
be verifying that every one of our copy source and destination
URLs is or is beneath this sucker's repository root URL as a form
of a cheap(ish) sanity check. */
SVN_ERR(svn_client_open_ra_session2(&ra_session,
first_pair->src_abspath_or_url, NULL,
ctx, pool, pool));
SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
/* Verify that sources and destinations are all at or under
REPOS_ROOT. While here, create a path_info struct for each
src/dst pair and initialize portions of it with normalized source
location information. */
path_infos = apr_array_make(pool, copy_pairs->nelts,
sizeof(path_driver_info_t *));
for (i = 0; i < copy_pairs->nelts; i++)
{
path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
apr_hash_t *mergeinfo;
/* Are the source and destination URLs at or under REPOS_ROOT? */
if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
&& svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Source and destination URLs appear not to point to the "
"same repository."));
/* Run the history function to get the source's URL and revnum in the
operational revision. */
SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
&pair->src_revnum,
NULL, NULL,
ra_session,
pair->src_abspath_or_url,
&pair->src_peg_revision,
&pair->src_op_revision, NULL,
ctx, pool));
/* Go ahead and grab mergeinfo from the source, too. */
SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
SVN_ERR(svn_client__get_repos_mergeinfo(
&mergeinfo, ra_session,
pair->src_abspath_or_url, pair->src_revnum,
svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
if (mergeinfo)
SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
/* Plop an INFO structure onto our array thereof. */
info->src_url = pair->src_abspath_or_url;
info->src_revnum = pair->src_revnum;
info->resurrection = FALSE;
APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
}
/* If this is a move, we have to open our session to the longest
path common to all SRC_URLS and DST_URLS in the repository so we
can do existence checks on all paths, and so we can operate on
all paths in the case of a move. But if this is *not* a move,
then opening our session at the longest path common to sources
*and* destinations might be an optimization when the user is
authorized to access all that stuff, but could cause the
operation to fail altogether otherwise. See issue #3242. */
SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
pool));
top_url = is_move ? top_url_all : top_url_dst;
/* Check each src/dst pair for resurrection, and verify that TOP_URL
is anchored high enough to cover all the editor_t activities
required for this operation. */
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 *);
path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
path_driver_info_t *);
/* Source and destination are the same? It's a resurrection. */
if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
info->resurrection = TRUE;
/* We need to add each dst_URL, and (in a move) we'll need to
delete each src_URL. Our selection of TOP_URL so far ensures
that all our destination URLs (and source URLs, for moves)
are at least as deep as TOP_URL, but we need to make sure
that TOP_URL is an *ancestor* of all our to-be-edited paths.
Issue #683 is demonstrates this scenario. If you're
resurrecting a deleted item like this: 'svn cp -rN src_URL
dst_URL', then src_URL == dst_URL == top_url. In this
situation, we want to open an RA session to be at least the
*parent* of all three. */
if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
&& (strcmp(top_url, repos_root) != 0))
{
top_url = svn_uri_dirname(top_url, pool);
}
if (is_move
&& (strcmp(top_url, pair->src_abspath_or_url) == 0)
&& (strcmp(top_url, repos_root) != 0))
{
top_url = svn_uri_dirname(top_url, pool);
}
}
/* Point the RA session to our current TOP_URL. */
SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
/* If we're allowed to create nonexistent parent directories of our
destinations, then make a list in NEW_DIRS of the parent
directories of the destination that don't yet exist. */
if (make_parents)
{
new_dirs = apr_array_make(pool, 0, sizeof(const char *));
/* If this is a move, TOP_URL is at least the common ancestor of
all the paths (sources and destinations) involved. Assuming
the sources exist (which is fair, because if they don't, this
whole operation will fail anyway), TOP_URL must also exist.
So it's the paths between TOP_URL and the destinations which
we have to check for existence. But here, we take advantage
of the knowledge of our caller. We know that if there are
multiple copy/move operations being requested, then the
destinations of the copies/moves will all be siblings of one
another. Therefore, we need only to check for the
nonexistent paths between TOP_URL and *one* of our
destinations to find nonexistent parents of all of them. */
if (is_move)
{
/* Imagine a situation where the user tries to copy an
existing source directory to nonexistent directory with
--parents options specified:
svn copy --parents URL/src URL/dst
where src exists and dst does not. If the dirname of the
destination path is equal to TOP_URL,
do not try to add dst to the NEW_DIRS list since it
will be added to the commit items array later in this
function. */
const char *dir = svn_uri_skip_ancestor(
top_url,
svn_uri_dirname(first_pair->dst_abspath_or_url,
pool),
pool);
if (dir && *dir)
SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
}
/* If, however, this is *not* a move, TOP_URL only points to the
common ancestor of our destination path(s), or possibly one
level higher. We'll need to do an existence crawl toward the
root of the repository, starting with one of our destinations
(see "... take advantage of the knowledge of our caller ..."
above), and possibly adjusting TOP_URL as we go. */
else
{
apr_array_header_t *new_urls =
apr_array_make(pool, 0, sizeof(const char *));
SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
/* Convert absolute URLs into relpaths relative to TOP_URL. */
for (i = 0; i < new_urls->nelts; i++)
{
const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
APR_ARRAY_PUSH(new_dirs, const char *) = dir;
}
}
}
/* For each src/dst pair, check to see if that SRC_URL is a child of
the DST_URL (excepting the case where DST_URL is the repo root).
If it is, and the parent of DST_URL is the current TOP_URL, then we
need to reparent the session one directory higher, the parent of
the DST_URL. */
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 *);
path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
path_driver_info_t *);
const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
pair->src_abspath_or_url,
pool);
if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
&& (relpath != NULL && *relpath != '\0'))
{
info->resurrection = TRUE;
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));
}
}
/* Get the portions of the SRC and DST URLs that are relative to
TOP_URL (URI-decoding them while we're at it), verify that the
source exists and the proposed destination does not, and toss
what we've learned into the INFO array. (For copies -- that is,
non-moves -- the relative source URL NULL because it isn't a
child of the TOP_URL at all. That's okay, we'll deal with
it.) */
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 *);
path_driver_info_t *info =
APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
svn_node_kind_t dst_kind;
const char *src_rel, *dst_rel;
src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
if (src_rel)
{
SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
&info->src_kind, pool));
}
else
{
const char *old_url;
src_rel = NULL;
SVN_ERR_ASSERT(! is_move);
SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
pair->src_abspath_or_url,
pool));
SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
&info->src_kind, pool));
SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
}
if (info->src_kind == svn_node_none)
return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' does not exist in revision %ld"),
pair->src_abspath_or_url, pair->src_revnum);
/* Figure out the basename that will result from this operation,
and ensure that we aren't trying to overwrite existing paths. */
dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
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"),
pair->dst_abspath_or_url);
/* More info for our INFO structure. */
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))
{
/* 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;
apr_array_header_t *commit_items
= apr_array_make(pool, 2 * 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 *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
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;
}
}
for (i = 0; i < path_infos->nelts; i++)
{
path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
path_driver_info_t *);
item = svn_client_commit_item3_create(pool);
item->url = svn_path_url_add_component2(top_url, info->dst_path,
pool);
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 = 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;
}
}
SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
ctx, pool));
if (! message)
return SVN_NO_ERROR;
}
else
message = "";
/* Setup our PATHS for the path-based editor drive. */
/* First any intermediate directories. */
if (make_parents)
{
for (i = 0; i < new_dirs->nelts; i++)
{
const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
info->dst_path = relpath;
info->dir_add = TRUE;
APR_ARRAY_PUSH(paths, const char *) = relpath;
svn_hash_sets(action_hash, relpath, info);
}
}
/* Then our copy destinations and move sources (if any). */
for (i = 0; i < path_infos->nelts; i++)
{
path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
path_driver_info_t *);
APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
if (is_move && (! info->resurrection))
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));
/* Fetch RA commit editor. */
SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
svn_client__get_shim_callbacks(ctx->wc_ctx,
NULL, pool)));
SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
commit_revprops,
commit_callback,
commit_baton,
NULL, TRUE, /* No lock tokens */
pool));
/* Setup the callback baton. */
cb_baton.action_hash = action_hash;
cb_baton.is_move = is_move;
/* Call the path-based editor driver. */
err = svn_delta_path_driver3(editor, edit_baton, paths, TRUE,
path_driver_cb_func, &cb_baton, pool);
if (err)
{
/* At least try to abort the edit (and fs txn) before throwing err. */
return svn_error_compose_create(
err,
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));
}
/* Baton for check_url_kind */
struct check_url_kind_baton
{
svn_ra_session_t *session;
const char *repos_root_url;
svn_boolean_t should_reparent;
};
/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
static svn_error_t *
check_url_kind(void *baton,
svn_node_kind_t *kind,
const char *url,
svn_revnum_t revision,
apr_pool_t *scratch_pool)
{
struct check_url_kind_baton *cukb = baton;
/* If we don't have a session or can't use the session, get one */
if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
*kind = svn_node_none;
else
{
cukb->should_reparent = TRUE;
SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
kind, scratch_pool));
}
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
* and each 'dst_abspath_or_url' is a URL.
* MAKE_PARENTS is ...
* REVPROP_TABLE is ...
* CTX is ... */
static svn_error_t *
wc_to_repos_copy(const apr_array_header_t *copy_pairs,
svn_boolean_t make_parents,
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)
{
const char *message;
const char *top_src_path, *top_dst_url;
struct check_url_kind_baton cukb;
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;
apr_pool_t *iterpool;
apr_array_header_t *new_dirs = NULL;
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 */
SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
scratch_pool));
/* Do we need to lock the working copy? 1.6 didn't take a write
lock, but what happens if the working copy changes during the copy
operation? */
iterpool = svn_pool_create(scratch_pool);
/* Determine the longest common ancestor for the destinations, and open an RA
session to that location. */
/* ### But why start by getting the _parent_ of the first one? */
/* --- That works because multiple destinations always point to the same
* directory. I'm rather wondering why we need to find a common
* destination parent here at all, instead of simply getting
* top_dst_url from get_copy_pair_ancestors() above?
* It looks like the entire block of code hanging off this comment
* is redundant. */
first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
for (i = 1; i < copy_pairs->nelts; i++)
{
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
pair->dst_abspath_or_url,
scratch_pool);
}
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,
commit_items_for_dav,
FALSE /* write_dav_props */,
TRUE /* read_dav_props */,
ctx,
session_pool, session_pool));
/* If requested, determine the nearest existing parent of the destination,
and reparent the ra session there. */
if (make_parents)
{
new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
scratch_pool));
}
/* Figure out the basename that will result from each copy and check to make
sure it doesn't exist already. */
for (i = 0; i < copy_pairs->nelts; i++)
{
svn_node_kind_t dst_kind;
const char *dst_rel;
svn_client__copy_pair_t *pair =
APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
svn_pool_clear(iterpool);
dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
iterpool);
SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
&dst_kind, iterpool));
if (dst_kind != svn_node_none)
{
return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
_("Path '%s' already exists"),
pair->dst_abspath_or_url);
}
}
cukb.session = ra_session;
SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
cukb.should_reparent = FALSE;
/* Crawl the working copy for commit items. */
/* ### TODO: Pass check_url_func for issue #3314 handling */
SVN_ERR(svn_client__get_copy_committables(&committables,
copy_pairs,
check_url_kind, &cukb,
ctx, scratch_pool, iterpool));
/* The committables are keyed by the repository root */
commit_items = svn_hash_gets(committables->by_repository,
cukb.repos_root_url);
SVN_ERR_ASSERT(commit_items != NULL);
if (cukb.should_reparent)
SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
/* If we are creating intermediate directories, tack them onto the list
of committables. */
if (make_parents)
{
for (i = 0; i < new_dirs->nelts; i++)
{
const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
svn_client_commit_item3_t *item;
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 *));
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
}
}
/* ### TODO: This extra loop would be unnecessary if this code lived
### in svn_client__get_copy_committables(), which is incidentally
### only used above (so should really be in this source file). */
for (i = 0; i < copy_pairs->nelts; i++)
{
apr_hash_t *mergeinfo, *wc_mergeinfo;
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
svn_client_commit_item3_t *item =
APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
svn_client__pathrev_t *src_origin;
svn_pool_clear(iterpool);
SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
pair->src_abspath_or_url,
ctx, iterpool, iterpool));
/* Set the mergeinfo for the destination to the combined merge
info known to the WC and the repository. */
/* Repository mergeinfo (or NULL if it's locally added)... */
if (src_origin)
SVN_ERR(svn_client__get_repos_mergeinfo(
&mergeinfo, ra_session, src_origin->url, src_origin->rev,
svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
else
mergeinfo = NULL;
/* ... and WC mergeinfo. */
SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
pair->src_abspath_or_url,
iterpool, iterpool));
if (wc_mergeinfo && mergeinfo)
SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
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(scratch_pool, sizeof(*mergeinfo_prop));
svn_string_t *prop_value;
SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
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(scratch_pool);
for (i = 0; i < commit_items->nelts; i++)
{
svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
svn_client_commit_item3_t *);
const char *relpath;
if (!item->path)
continue;
svn_pool_clear(iterpool);
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)
svn_hash_sets(relpath_map, relpath, item->path);
}
}
#endif
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,
commit_baton, NULL,
TRUE, /* No lock tokens */
session_pool));
/* Perform the commit. */
SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
editor, edit_baton,
NULL /* notify_path_prefix */,
NULL, ctx, session_pool, session_pool),
_("Commit failed (details follow):"));
svn_pool_destroy(iterpool);
svn_pool_destroy(session_pool);
return SVN_NO_ERROR;
}
/* A baton for notification_adjust_func(). */
struct notification_adjust_baton
{
svn_wc_notify_func2_t inner_func;
void *inner_baton;
const char *checkout_abspath;
const char *final_abspath;
};
/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
* baton is BATON->inner_baton) to turn the result of a 'checkout' into
* what we want to see for a 'copy to WC' operation.
*
* - Adjust the notification paths that start with BATON->checkout_abspath
* to start instead with BATON->final_abspath.
* - Change start-of-update notification into a plain WC 'add' for the root.
* - Change checkout 'add' notifications into a plain WC 'add'.
* - Discard 'update_completed' notifications.
*/
static void
notification_adjust_func(void *baton,
const svn_wc_notify_t *notify,
apr_pool_t *pool)
{
struct notification_adjust_baton *nb = baton;
svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
const char *relpath;
relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
/* Convert 'update' notifications to plain 'add' notifications; discard
notifications about checkout/update starting/finishing. */
if (notify->action == svn_wc_notify_update_started /* root */
|| notify->action == svn_wc_notify_update_add) /* non-root */
{
inner_notify->action = svn_wc_notify_add;
}
else if (notify->action == svn_wc_notify_update_update
|| notify->action == svn_wc_notify_update_completed)
{
/* update_update happens only for a prop mod on root; the root was
already notified so discard this */
return;
}
if (nb->inner_func)
nb->inner_func(nb->inner_baton, inner_notify, pool);
}
/** Copy a directory tree from a remote repository.
*
* Copy from RA_SESSION:LOCATION to WC_CTX:DST_ABSPATH.
*
* Create the directory DST_ABSPATH, if not present. Its parent should be
* already under version control in the WC and in a suitable state for
* scheduling the addition of a child.
*
* Ignore any incoming non-regular properties (entry-props, DAV/WC-props).
* Remove any incoming 'svn:mergeinfo' properties.
*/
static svn_error_t *
copy_foreign_dir(svn_ra_session_t *ra_session,
const svn_client__pathrev_t *location,
const char *dst_abspath,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const svn_delta_editor_t *editor;
void *eb;
const svn_delta_editor_t *wrapped_editor;
void *wrapped_baton;
const svn_ra_reporter3_t *reporter;
void *reporter_baton;
/* Get a WC editor. It does not need an RA session because we will not
be sending it any 'copy from' requests, only 'add' requests. */
SVN_ERR(svn_client__wc_editor_internal(&editor, &eb,
dst_abspath,
TRUE /*root_dir_add*/,
TRUE /*ignore_mergeinfo_changes*/,
FALSE /*manage_wc_write_lock*/,
notify_func, notify_baton,
NULL /*ra_session*/,
ctx, scratch_pool));
SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
editor, eb,
&wrapped_editor, &wrapped_baton,
scratch_pool));
SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
location->rev, "", svn_depth_infinity,
FALSE, FALSE, wrapped_editor, wrapped_baton,
scratch_pool, scratch_pool));
SVN_ERR(reporter->set_path(reporter_baton, "", location->rev,
svn_depth_infinity /* irrelevant */,
TRUE /*start_empty*/,
NULL, scratch_pool));
SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
return SVN_NO_ERROR;
}
/* Implementation of svn_client__repos_to_wc_copy() for a dir.
*/
static svn_error_t *
svn_client__repos_to_wc_copy_dir(svn_boolean_t *timestamp_sleep,
const char *src_url,
svn_revnum_t src_revnum,
const char *dst_abspath,
svn_boolean_t same_repositories,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *tmpdir_abspath, *tmp_abspath;
SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
if (!same_repositories)
{
svn_client__pathrev_t *location;
*timestamp_sleep = TRUE;
/* ### Reparenting "ra_session" can't be right, can it? As this is
a foreign repo, surely we need a new RA session? */
SVN_ERR(svn_client__pathrev_create_with_session(&location, ra_session,
src_revnum, src_url,
scratch_pool));
SVN_ERR(svn_ra_reparent(ra_session, src_url, scratch_pool));
SVN_ERR(copy_foreign_dir(ra_session, location,
dst_abspath,
ctx->notify_func2, ctx->notify_baton2,
ctx->cancel_func, ctx->cancel_baton,
ctx, scratch_pool));
return SVN_NO_ERROR;
}
/* Find a temporary location in which to check out the copy source. */
SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
scratch_pool, scratch_pool));
/* Get a temporary path. The crude way we do this is to create a
temporary file, remember its name, and let it be deleted immediately. */
SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
svn_io_file_del_on_close,
scratch_pool, scratch_pool));
/* Make a new checkout of the requested source. While doing so,
* resolve copy_src_revnum to an actual revision number in case it
* was until now 'invalid' meaning 'head'. Ask this function not to
* sleep for timestamps, by passing a sleep_needed output param.
* Send notifications for all nodes except the root node, and adjust
* them to refer to the destination rather than this temporary path. */
{
svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
void *old_notify_baton2 = ctx->notify_baton2;
struct notification_adjust_baton nb;
svn_error_t *err;
svn_opt_revision_t copy_src_revision;
copy_src_revision.kind = svn_opt_revision_number;
copy_src_revision.value.number = src_revnum;
nb.inner_func = ctx->notify_func2;
nb.inner_baton = ctx->notify_baton2;
nb.checkout_abspath = tmp_abspath;
nb.final_abspath = dst_abspath;
ctx->notify_func2 = notification_adjust_func;
ctx->notify_baton2 = &nb;
err = svn_client__checkout_internal(NULL /*result_rev*/, timestamp_sleep,
src_url,
tmp_abspath,
&copy_src_revision,
&copy_src_revision,
svn_depth_infinity,
TRUE /*ignore_externals*/,
FALSE, /* we don't allow obstructions */
TRUE, /*settings_from_context*/
NULL, svn_tristate_unknown,
ra_session, ctx, scratch_pool);
ctx->notify_func2 = old_notify_func2;
ctx->notify_baton2 = old_notify_baton2;
SVN_ERR(err);
}
/* Schedule dst_path for addition in parent, with copy history.
Don't send any notification here.
Then remove the temporary checkout's .svn dir in preparation for
moving the rest of it into the final destination. */
SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
TRUE /* metadata_only */,
NULL, NULL, /* don't allow user to cancel here */
NULL, NULL, scratch_pool));
SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
FALSE, scratch_pool, scratch_pool));
SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
tmp_abspath,
FALSE, FALSE,
NULL, NULL, /* don't cancel */
scratch_pool));
/* Move the temporary disk tree into place. */
SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, scratch_pool));
return SVN_NO_ERROR;
}
/* Implementation of svn_client__repos_to_wc_copy() for a file.
*
* This has no 'ignore_externals' parameter because we don't support the
* 'svn:externals' property being set on a file.
*/
static svn_error_t *
svn_client__repos_to_wc_copy_file(svn_boolean_t *timestamp_sleep,
const char *src_url,
svn_revnum_t src_rev,
const char *dst_abspath,
svn_boolean_t same_repositories,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *src_rel;
apr_hash_t *new_props;
svn_stream_t *new_base_contents = svn_stream_buffered(scratch_pool);
SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, src_url,
scratch_pool));
/* Fetch the file content. */
SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_rev,
new_base_contents, NULL, &new_props,
scratch_pool));
if (!same_repositories)
svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
*timestamp_sleep = TRUE;
SVN_ERR(svn_wc_add_repos_file4(
ctx->wc_ctx, dst_abspath,
new_base_contents, NULL, new_props, NULL,
same_repositories ? src_url : NULL,
same_repositories ? src_rev : SVN_INVALID_REVNUM,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool));
/* Do our own notification for the root node, even if we could possibly
have delegated it. See also issue #2198. */
if (ctx->notify_func2)
{
svn_wc_notify_t *notify
= svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool);
notify->kind = svn_node_file;
ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
}
return SVN_NO_ERROR;
}
/* Are RA_SESSION and the versioned *parent* dir of WC_TARGET_ABSPATH in
* the same repository?
*/
static svn_error_t *
is_same_repository(svn_boolean_t *same_repository,
svn_ra_session_t *ra_session,
const char *wc_target_abspath,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *src_uuid, *dst_uuid;
/* Get the repository UUIDs of copy source URL and WC parent path */
SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, scratch_pool));
SVN_ERR(svn_client_get_repos_root(NULL /*root_url*/, &dst_uuid,
svn_dirent_dirname(wc_target_abspath,
scratch_pool),
ctx, scratch_pool, scratch_pool));
*same_repository = (strcmp(src_uuid, dst_uuid) == 0);
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__repos_to_wc_copy_internal(svn_boolean_t *timestamp_sleep,
svn_node_kind_t kind,
const char *src_url,
svn_revnum_t src_rev,
const char *dst_abspath,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *old_session_url;
svn_boolean_t timestamp_sleep_ignored;
svn_boolean_t same_repositories;
SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
src_url, scratch_pool));
SVN_ERR(is_same_repository(&same_repositories,
ra_session, dst_abspath, ctx, scratch_pool));
if (!timestamp_sleep)
timestamp_sleep = &timestamp_sleep_ignored;
if (kind == svn_node_dir)
{
SVN_ERR(svn_client__repos_to_wc_copy_dir(timestamp_sleep,
src_url, src_rev,
dst_abspath,
same_repositories,
ra_session,
ctx, scratch_pool));
}
else if (kind == svn_node_file)
{
SVN_ERR(svn_client__repos_to_wc_copy_file(timestamp_sleep,
src_url, src_rev,
dst_abspath,
same_repositories,
ra_session,
ctx, scratch_pool));
}
/* Reparent the session back to the original URL. */
SVN_ERR(svn_ra_reparent(ra_session, old_session_url, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__repos_to_wc_copy_by_editor(svn_boolean_t *timestamp_sleep,
svn_node_kind_t kind,
const char *src_url,
svn_revnum_t src_rev,
const char *dst_abspath,
svn_ra_session_t *ra_session,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const svn_delta_editor_t *editor;
void *eb;
const char *src_anchor = svn_uri_dirname(src_url, scratch_pool);
const char *dst_target = svn_dirent_basename(dst_abspath, scratch_pool);
void *rb, *db;
SVN_ERR(svn_ra_reparent(ra_session, src_anchor, scratch_pool));
SVN_ERR(svn_client__wc_editor_internal(
&editor, &eb,
svn_dirent_dirname(dst_abspath, scratch_pool),
FALSE /*root_dir_add*/,
FALSE /*ignore_mergeinfo_changes*/,
FALSE /*manage_wc_write_lock*/,
ctx->notify_func2, ctx->notify_baton2,
ra_session,
ctx, scratch_pool));
SVN_ERR(editor->open_root(eb, SVN_INVALID_REVNUM, scratch_pool, &rb));
if (kind == svn_node_dir)
{
SVN_ERR(editor->add_directory(dst_target, rb,
src_url, src_rev,
scratch_pool,
&db));
SVN_ERR(editor->close_directory(db, scratch_pool));
}
else
{
SVN_ERR(editor->add_file(dst_target, rb,
src_url, src_rev,
scratch_pool,
&db));
SVN_ERR(editor->close_file(db, NULL, scratch_pool));
}
SVN_ERR(editor->close_edit(eb, scratch_pool));
if (timestamp_sleep)
*timestamp_sleep = TRUE;
return SVN_NO_ERROR;
}
/* Perform each individual copy operation for a repos -> wc copy. A
helper for repos_to_wc_copy().
PAIR->src_revnum PAIR->src_abspath_or_url should already have been
resolved to the operative revision number and operative URL.
*/
static svn_error_t *
repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
const svn_client__copy_pair_t *pair,
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)
{
apr_hash_t *src_mergeinfo;
const char *dst_abspath = pair->dst_abspath_or_url;
svn_boolean_t same_repositories;
SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(pair->src_revnum));
SVN_ERR_ASSERT(svn_path_is_url(pair->src_abspath_or_url));
SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
SVN_ERR(is_same_repository(&same_repositories,
ra_session, dst_abspath, ctx, pool));
if (!same_repositories && ctx->notify_func2)
{
svn_wc_notify_t *notify;
notify = svn_wc_create_notify_url(
pair->src_abspath_or_url,
svn_wc_notify_foreign_copy_begin,
pool);
notify->kind = pair->src_kind;
ctx->notify_func2(ctx->notify_baton2, notify, pool);
/* Allow a theoretical cancel to get through. */
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
}
SVN_ERR(svn_client__repos_to_wc_copy_by_editor(
timestamp_sleep,
pair->src_kind,
pair->src_abspath_or_url,
pair->src_revnum,
dst_abspath,
ra_session, ctx, pool));
/* Fetch externals, pinning them if requested */
if (!ignore_externals && pair->src_kind == svn_node_dir)
{
if (same_repositories)
{
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));
if (pin_externals)
{
apr_hash_t *pinned_externals;
apr_hash_index_t *hi;
apr_pool_t *iterpool;
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));
}
svn_pool_destroy(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,
pool, pool));
SVN_ERR(svn_client__handle_externals(new_externals,
new_depths,
repos_root_url, dst_abspath,
svn_depth_infinity,
timestamp_sleep,
ra_session,
ctx, pool));
}
}
if (same_repositories)
{
/* Record the implied mergeinfo. */
SVN_ERR(svn_client__get_repos_mergeinfo(&src_mergeinfo, ra_session,
pair->src_abspath_or_url,
pair->src_revnum,
svn_mergeinfo_inherited,
TRUE /*squelch_incapable*/,
pool));
SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
/* ### Maybe the notification should mention this mergeinfo change. */
/* ### Maybe we should do this during rather than after the copy. */
}
return SVN_NO_ERROR;
}
static svn_error_t *
repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
const char *top_dst_abspath,
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)
{
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* Perform the move for each of the copy_pairs. */
for (i = 0; i < copy_pairs->nelts; i++)
{
/* Check for cancellation */
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
svn_pool_clear(iterpool);
SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *),
ignore_externals,
pin_externals, externals_to_pin,
ra_session, ctx, iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
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)
{
svn_ra_session_t *ra_session;
const char *top_src_url, *top_dst_abspath;
apr_pool_t *iterpool = svn_pool_create(pool);
const char *lock_abspath;
int i;
/* Get the real path for the source, based upon its peg revision. */
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 *);
const char *src;
svn_pool_clear(iterpool);
SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
NULL,
pair->src_abspath_or_url,
&pair->src_peg_revision,
&pair->src_op_revision, NULL,
ctx, iterpool));
pair->src_original = pair->src_abspath_or_url;
pair->src_abspath_or_url = apr_pstrdup(pool, src);
}
SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_abspath,
NULL, pool));
lock_abspath = top_dst_abspath;
if (copy_pairs->nelts == 1)
{
top_src_url = svn_uri_dirname(top_src_url, pool);
lock_abspath = svn_dirent_dirname(top_dst_abspath, pool);
}
/* Open a repository session to the longest common src ancestor. We do not
(yet) have a working copy, so we don't have a corresponding path and
tempfiles cannot go into the admin area. */
SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
ctx, pool, pool));
/* Get the correct src path for the peg revision used, and verify that we
aren't overwriting an existing path. */
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 *);
const char *src_rel;
svn_pool_clear(iterpool);
/* Next, make sure that the path exists in the repository. */
SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
pair->src_abspath_or_url,
iterpool));
SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
&pair->src_kind, pool));
if (pair->src_kind == svn_node_none)
{
if (SVN_IS_VALID_REVNUM(pair->src_revnum))
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' not found in revision %ld"),
pair->src_abspath_or_url, pair->src_revnum);
else
return svn_error_createf
(SVN_ERR_FS_NOT_FOUND, NULL,
_("Path '%s' not found in head revision"),
pair->src_abspath_or_url);
}
}
svn_pool_destroy(iterpool);
SVN_WC__CALL_WITH_WRITE_LOCK(
repos_to_wc_copy_locked(timestamp_sleep,
copy_pairs, top_dst_abspath, ignore_externals,
pin_externals, externals_to_pin,
ra_session, ctx, pool),
ctx->wc_ctx, lock_abspath, FALSE, pool);
return SVN_NO_ERROR;
}
#define NEED_REPOS_REVNUM(revision) \
((revision.kind != svn_opt_revision_unspecified) \
&& (revision.kind != svn_opt_revision_working))
/* ...
*
* Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
* change *TIMESTAMP_SLEEP. This output will be valid even if the
* function returns an error.
*
* Perform all allocations in POOL.
*/
static svn_error_t *
try_copy(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *sources,
const char *dst_path_in,
svn_boolean_t is_move,
svn_boolean_t allow_mixed_revisions,
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,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
apr_array_header_t *copy_pairs =
apr_array_make(pool, sources->nelts,
sizeof(svn_client__copy_pair_t *));
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. */
srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
svn_client_copy_source_t *)->path);
dst_is_url = svn_path_is_url(dst_path_in);
if (!dst_is_url)
SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
/* If we have multiple source paths, it implies the dst_path is a
directory we are moving or copying into. Populate the COPY_PAIRS
array to contain a destination path for each of the source paths. */
if (sources->nelts > 1)
{
apr_pool_t *iterpool = svn_pool_create(pool);
for (i = 0; i < sources->nelts; i++)
{
svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
svn_client_copy_source_t *);
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);
svn_pool_clear(iterpool);
if (src_is_url)
{
pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
src_basename = svn_uri_basename(pair->src_abspath_or_url,
iterpool);
}
else
{
SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
source->path, pool));
src_basename = svn_dirent_basename(pair->src_abspath_or_url,
iterpool);
}
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,
src_is_url,
TRUE,
iterpool));
/* Check to see if all the sources are urls or all working copy
* paths. */
if (src_is_url != srcs_are_urls)
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Cannot mix repository and working copy sources"));
if (dst_is_url)
pair->dst_abspath_or_url =
svn_path_url_add_component2(dst_path_in, src_basename, pool);
else
pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
src_basename, pool);
APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
}
svn_pool_destroy(iterpool);
}
else
{
/* Only one source path. */
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);
if (src_is_url)
pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
else
SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
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,
src_is_url, TRUE, pool));
pair->dst_abspath_or_url = dst_path_in;
APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
}
if (is_move || (!srcs_are_urls && !dst_is_url))
{
apr_pool_t *iterpool = svn_pool_create(pool);
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 *);
svn_pool_clear(iterpool);
if (svn_dirent_is_child(pair->src_abspath_or_url,
pair->dst_abspath_or_url, iterpool))
return svn_error_createf
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
is_move ?
_("Cannot move path '%s' into its own child '%s'") :
_("Cannot copy path '%s' into its own child '%s'"),
svn_dirent_local_style(pair->src_abspath_or_url, pool),
svn_dirent_local_style(pair->dst_abspath_or_url, pool));
}
svn_pool_destroy(iterpool);
}
/* A file external should not be moved since the file external is
implemented as a switched file and it would delete the file the
file external is switched to, which is not the behavior the user
would probably want. */
if (is_move && !srcs_are_urls)
{
apr_pool_t *iterpool = svn_pool_create(pool);
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 *);
svn_node_kind_t external_kind;
const char *defining_abspath;
svn_pool_clear(iterpool);
SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
NULL, NULL, NULL, ctx->wc_ctx,
pair->src_abspath_or_url,
pair->src_abspath_or_url, TRUE,
iterpool, iterpool));
if (external_kind != svn_node_none)
return svn_error_createf(
SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
NULL,
_("Cannot move the external at '%s'; please "
"edit the svn:externals property on '%s'."),
svn_dirent_local_style(pair->src_abspath_or_url, pool),
svn_dirent_local_style(defining_abspath, pool));
}
svn_pool_destroy(iterpool);
}
if (is_move)
{
/* Disallow moves between the working copy and the repository. */
if (srcs_are_urls != dst_is_url)
{
return svn_error_create
(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Moves between the working copy and the repository are not "
"supported"));
}
/* Disallow moving any path/URL onto or into itself. */
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 *);
if (strcmp(pair->src_abspath_or_url,
pair->dst_abspath_or_url) == 0)
return svn_error_createf(
SVN_ERR_UNSUPPORTED_FEATURE, NULL,
srcs_are_urls ?
_("Cannot move URL '%s' into itself") :
_("Cannot move path '%s' into itself"),
srcs_are_urls ?
pair->src_abspath_or_url :
svn_dirent_local_style(pair->src_abspath_or_url, pool));
}
}
else
{
if (!srcs_are_urls)
{
/* If we are doing a wc->* copy, but with an operational revision
other than the working copy revision, we are really doing a
repo->* copy, because we're going to need to get the rev from the
repo. */
svn_boolean_t need_repos_op_rev = FALSE;
svn_boolean_t need_repos_peg_rev = FALSE;
/* Check to see if any revision is something other than
svn_opt_revision_unspecified or svn_opt_revision_working. */
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 *);
if (NEED_REPOS_REVNUM(pair->src_op_revision))
need_repos_op_rev = TRUE;
if (NEED_REPOS_REVNUM(pair->src_peg_revision))
need_repos_peg_rev = TRUE;
if (need_repos_op_rev || need_repos_peg_rev)
break;
}
if (need_repos_op_rev || need_repos_peg_rev)
{
apr_pool_t *iterpool = svn_pool_create(pool);
for (i = 0; i < copy_pairs->nelts; i++)
{
const char *copyfrom_repos_root_url;
const char *copyfrom_repos_relpath;
const char *url;
svn_revnum_t copyfrom_rev;
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
svn_pool_clear(iterpool);
SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
&copyfrom_repos_relpath,
&copyfrom_repos_root_url,
NULL, NULL, NULL,
ctx->wc_ctx,
pair->src_abspath_or_url,
TRUE, iterpool, iterpool));
if (copyfrom_repos_relpath)
url = svn_path_url_add_component2(copyfrom_repos_root_url,
copyfrom_repos_relpath,
pool);
else
return svn_error_createf
(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("'%s' does not have a URL associated with it"),
svn_dirent_local_style(pair->src_abspath_or_url, pool));
pair->src_abspath_or_url = url;
if (!need_repos_peg_rev
|| pair->src_peg_revision.kind == svn_opt_revision_base)
{
/* Default the peg revision to that of the WC entry. */
pair->src_peg_revision.kind = svn_opt_revision_number;
pair->src_peg_revision.value.number = copyfrom_rev;
}
if (pair->src_op_revision.kind == svn_opt_revision_base)
{
/* Use the entry's revision as the operational rev. */
pair->src_op_revision.kind = svn_opt_revision_number;
pair->src_op_revision.value.number = copyfrom_rev;
}
}
svn_pool_destroy(iterpool);
srcs_are_urls = TRUE;
}
}
}
/* Now, call the right handler for the operation. */
if ((! srcs_are_urls) && (! dst_is_url))
{
SVN_ERR(verify_wc_srcs(copy_pairs, ctx, pool));
SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only,
ctx, pool, pool));
/* Copy or move all targets. */
if (is_move)
return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
copy_pairs, dst_path_in,
allow_mixed_revisions,
metadata_only,
ctx, pool));
else
{
/* We ignore these values, so assert the default value */
SVN_ERR_ASSERT(allow_mixed_revisions);
return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
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,
pin_externals, externals_to_pin, ctx, pool));
}
else if ((srcs_are_urls) && (! dst_is_url))
{
SVN_ERR(verify_wc_dsts(copy_pairs, make_parents,
FALSE, FALSE /* metadata_only */,
ctx, pool, pool));
return svn_error_trace(
repos_to_wc_copy(timestamp_sleep,
copy_pairs, ignore_externals,
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,
pin_externals, externals_to_pin, pool));
}
}
/* Public Interfaces */
svn_error_t *
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,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_error_t *err;
svn_boolean_t timestamp_sleep = FALSE;
apr_pool_t *subpool = svn_pool_create(pool);
if (sources->nelts > 1 && !copy_as_child)
return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
NULL, NULL);
err = try_copy(&timestamp_sleep,
sources, dst_path,
FALSE /* is_move */,
TRUE /* allow_mixed_revisions */,
metadata_only,
make_parents,
ignore_externals,
pin_externals,
externals_to_pin,
revprop_table,
commit_callback, commit_baton,
ctx,
subpool);
/* If the destination exists, try to copy the sources as children of the
destination. */
if (copy_as_child && err && (sources->nelts == 1)
&& (err->apr_err == SVN_ERR_ENTRY_EXISTS
|| err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
{
const char *src_path = APR_ARRAY_IDX(sources, 0,
svn_client_copy_source_t *)->path;
const char *src_basename;
svn_boolean_t src_is_url = svn_path_is_url(src_path);
svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
svn_error_clear(err);
svn_pool_clear(subpool);
src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
: svn_dirent_basename(src_path, subpool);
dst_path
= dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
subpool)
: svn_dirent_join(dst_path, src_basename, subpool);
err = try_copy(&timestamp_sleep,
sources, dst_path,
FALSE /* is_move */,
TRUE /* allow_mixed_revisions */,
metadata_only,
make_parents,
ignore_externals,
pin_externals,
externals_to_pin,
revprop_table,
commit_callback, commit_baton,
ctx,
subpool);
}
/* Sleep if required. DST_PATH is not a URL in these cases. */
if (timestamp_sleep)
svn_io_sleep_for_timestamps(dst_path, subpool);
svn_pool_destroy(subpool);
return svn_error_trace(err);
}
svn_error_t *
svn_client_move7(const apr_array_header_t *src_paths,
const char *dst_path,
svn_boolean_t move_as_child,
svn_boolean_t make_parents,
svn_boolean_t allow_mixed_revisions,
svn_boolean_t metadata_only,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
void *commit_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
const svn_opt_revision_t head_revision
= { svn_opt_revision_head, { 0 } };
svn_error_t *err;
svn_boolean_t timestamp_sleep = FALSE;
int i;
apr_pool_t *subpool = svn_pool_create(pool);
apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
sizeof(const svn_client_copy_source_t *));
if (src_paths->nelts > 1 && !move_as_child)
return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
NULL, NULL);
for (i = 0; i < src_paths->nelts; i++)
{
const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
svn_client_copy_source_t *copy_source = apr_palloc(pool,
sizeof(*copy_source));
copy_source->path = src_path;
copy_source->revision = &head_revision;
copy_source->peg_revision = &head_revision;
APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
}
err = try_copy(&timestamp_sleep,
sources, dst_path,
TRUE /* is_move */,
allow_mixed_revisions,
metadata_only,
make_parents,
FALSE /* ignore_externals */,
FALSE /* pin_externals */,
NULL /* externals_to_pin */,
revprop_table,
commit_callback, commit_baton,
ctx,
subpool);
/* If the destination exists, try to move the sources as children of the
destination. */
if (move_as_child && err && (src_paths->nelts == 1)
&& (err->apr_err == SVN_ERR_ENTRY_EXISTS
|| err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
{
const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
const char *src_basename;
svn_boolean_t src_is_url = svn_path_is_url(src_path);
svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
svn_error_clear(err);
svn_pool_clear(subpool);
src_basename = src_is_url ? svn_uri_basename(src_path, pool)
: svn_dirent_basename(src_path, pool);
dst_path
= dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
subpool)
: svn_dirent_join(dst_path, src_basename, subpool);
err = try_copy(&timestamp_sleep,
sources, dst_path,
TRUE /* is_move */,
allow_mixed_revisions,
metadata_only,
make_parents,
FALSE /* ignore_externals */,
FALSE /* pin_externals */,
NULL /* externals_to_pin */,
revprop_table,
commit_callback, commit_baton,
ctx,
subpool);
}
/* Sleep if required. DST_PATH is not a URL in these cases. */
if (timestamp_sleep)
svn_io_sleep_for_timestamps(dst_path, subpool);
svn_pool_destroy(subpool);
return svn_error_trace(err);
}