1251881Speter/* 2251881Speter * copy.c: copy/move wrappers around wc 'copy' functionality. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <string.h> 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_client.h" 33251881Speter#include "svn_error.h" 34251881Speter#include "svn_error_codes.h" 35251881Speter#include "svn_dirent_uri.h" 36251881Speter#include "svn_path.h" 37251881Speter#include "svn_opt.h" 38251881Speter#include "svn_time.h" 39251881Speter#include "svn_props.h" 40251881Speter#include "svn_mergeinfo.h" 41251881Speter#include "svn_pools.h" 42251881Speter 43251881Speter#include "client.h" 44251881Speter#include "mergeinfo.h" 45251881Speter 46251881Speter#include "svn_private_config.h" 47251881Speter#include "private/svn_wc_private.h" 48251881Speter#include "private/svn_ra_private.h" 49251881Speter#include "private/svn_mergeinfo_private.h" 50251881Speter#include "private/svn_client_private.h" 51251881Speter 52251881Speter 53251881Speter/* 54251881Speter * OUR BASIC APPROACH TO COPIES 55251881Speter * ============================ 56251881Speter * 57251881Speter * for each source/destination pair 58251881Speter * if (not exist src_path) 59251881Speter * return ERR_BAD_SRC error 60251881Speter * 61251881Speter * if (exist dst_path) 62251881Speter * return ERR_OBSTRUCTION error 63251881Speter * else 64251881Speter * copy src_path into parent_of_dst_path as basename (dst_path) 65251881Speter * 66251881Speter * if (this is a move) 67251881Speter * delete src_path 68251881Speter */ 69251881Speter 70251881Speter 71251881Speter 72251881Speter/*** Code. ***/ 73251881Speter 74251881Speter/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding 75251881Speter MERGEINFO to any mergeinfo pre-existing in the WC. */ 76251881Speterstatic svn_error_t * 77251881Speterextend_wc_mergeinfo(const char *target_abspath, 78251881Speter apr_hash_t *mergeinfo, 79251881Speter svn_client_ctx_t *ctx, 80251881Speter apr_pool_t *pool) 81251881Speter{ 82251881Speter apr_hash_t *wc_mergeinfo; 83251881Speter 84251881Speter /* Get a fresh copy of the pre-existing state of the WC's mergeinfo 85251881Speter updating it. */ 86251881Speter SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 87251881Speter target_abspath, pool, pool)); 88251881Speter 89251881Speter /* Combine the provided mergeinfo with any mergeinfo from the WC. */ 90251881Speter if (wc_mergeinfo && mergeinfo) 91251881Speter SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool)); 92251881Speter else if (! wc_mergeinfo) 93251881Speter wc_mergeinfo = mergeinfo; 94251881Speter 95251881Speter return svn_error_trace( 96251881Speter svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo, 97251881Speter FALSE, ctx, pool)); 98251881Speter} 99251881Speter 100251881Speter/* Find the longest common ancestor of paths in COPY_PAIRS. If 101251881Speter SRC_ANCESTOR is NULL, ignore source paths in this calculation. If 102251881Speter DST_ANCESTOR is NULL, ignore destination paths in this calculation. 103251881Speter COMMON_ANCESTOR will be the common ancestor of both the 104251881Speter SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not 105251881Speter NULL. 106251881Speter */ 107251881Speterstatic svn_error_t * 108251881Speterget_copy_pair_ancestors(const apr_array_header_t *copy_pairs, 109251881Speter const char **src_ancestor, 110251881Speter const char **dst_ancestor, 111251881Speter const char **common_ancestor, 112251881Speter apr_pool_t *pool) 113251881Speter{ 114251881Speter apr_pool_t *subpool = svn_pool_create(pool); 115251881Speter svn_client__copy_pair_t *first; 116251881Speter const char *first_dst; 117251881Speter const char *first_src; 118251881Speter const char *top_dst; 119251881Speter svn_boolean_t src_is_url; 120251881Speter svn_boolean_t dst_is_url; 121251881Speter char *top_src; 122251881Speter int i; 123251881Speter 124251881Speter first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 125251881Speter 126251881Speter /* Because all the destinations are in the same directory, we can easily 127251881Speter determine their common ancestor. */ 128251881Speter first_dst = first->dst_abspath_or_url; 129251881Speter dst_is_url = svn_path_is_url(first_dst); 130251881Speter 131251881Speter if (copy_pairs->nelts == 1) 132251881Speter top_dst = apr_pstrdup(subpool, first_dst); 133251881Speter else 134251881Speter top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool) 135251881Speter : svn_dirent_dirname(first_dst, subpool); 136251881Speter 137251881Speter /* Sources can came from anywhere, so we have to actually do some 138251881Speter work for them. */ 139251881Speter first_src = first->src_abspath_or_url; 140251881Speter src_is_url = svn_path_is_url(first_src); 141251881Speter top_src = apr_pstrdup(subpool, first_src); 142251881Speter for (i = 1; i < copy_pairs->nelts; i++) 143251881Speter { 144251881Speter /* We don't need to clear the subpool here for several reasons: 145251881Speter 1) If we do, we can't use it to allocate the initial versions of 146251881Speter top_src and top_dst (above). 147251881Speter 2) We don't return any errors in the following loop, so we 148251881Speter are guanteed to destroy the subpool at the end of this function. 149251881Speter 3) The number of iterations is likely to be few, and the loop will 150251881Speter be through quickly, so memory leakage will not be significant, 151251881Speter in time or space. 152251881Speter */ 153251881Speter const svn_client__copy_pair_t *pair = 154251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 155251881Speter 156251881Speter top_src = src_is_url 157251881Speter ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url, 158251881Speter subpool) 159251881Speter : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url, 160251881Speter subpool); 161251881Speter } 162251881Speter 163251881Speter if (src_ancestor) 164251881Speter *src_ancestor = apr_pstrdup(pool, top_src); 165251881Speter 166251881Speter if (dst_ancestor) 167251881Speter *dst_ancestor = apr_pstrdup(pool, top_dst); 168251881Speter 169251881Speter if (common_ancestor) 170251881Speter *common_ancestor = 171251881Speter src_is_url 172251881Speter ? svn_uri_get_longest_ancestor(top_src, top_dst, pool) 173251881Speter : svn_dirent_get_longest_ancestor(top_src, top_dst, pool); 174251881Speter 175251881Speter svn_pool_destroy(subpool); 176251881Speter 177251881Speter return SVN_NO_ERROR; 178251881Speter} 179251881Speter 180299742Sdim/* Quote a string if it would be handled as multiple or different tokens 181299742Sdim during externals parsing */ 182299742Sdimstatic const char * 183299742Sdimmaybe_quote(const char *value, 184299742Sdim apr_pool_t *result_pool) 185299742Sdim{ 186299742Sdim apr_status_t status; 187299742Sdim char **argv; 188251881Speter 189299742Sdim status = apr_tokenize_to_argv(value, &argv, result_pool); 190299742Sdim 191299742Sdim if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0) 192299742Sdim return apr_pstrdup(result_pool, value); 193299742Sdim 194299742Sdim { 195299742Sdim svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool); 196299742Sdim const char *c; 197299742Sdim 198299742Sdim svn_stringbuf_appendbyte(sb, '\"'); 199299742Sdim 200299742Sdim for (c = value; *c; c++) 201299742Sdim { 202299742Sdim if (*c == '\\' || *c == '\"' || *c == '\'') 203299742Sdim svn_stringbuf_appendbyte(sb, '\\'); 204299742Sdim 205299742Sdim svn_stringbuf_appendbyte(sb, *c); 206299742Sdim } 207299742Sdim 208299742Sdim svn_stringbuf_appendbyte(sb, '\"'); 209299742Sdim 210299742Sdim#ifdef SVN_DEBUG 211299742Sdim status = apr_tokenize_to_argv(sb->data, &argv, result_pool); 212299742Sdim 213299742Sdim SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1] 214299742Sdim && !strcmp(argv[0], value)); 215299742Sdim#endif 216299742Sdim 217299742Sdim return sb->data; 218299742Sdim } 219299742Sdim} 220299742Sdim 221299742Sdim/* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for 222299742Sdim * use as a line in an svn:externals property, based on the external item 223299742Sdim * ITEM and the additional parser information in INFO. Pin the external 224299742Sdim * to EXTERNAL_PEGREV. Use POOL for all allocations. */ 225299742Sdimstatic svn_error_t * 226299742Sdimmake_external_description(const char **new_external_description, 227299742Sdim const char *local_abspath_or_url, 228299742Sdim svn_wc_external_item2_t *item, 229299742Sdim svn_wc__externals_parser_info_t *info, 230299742Sdim svn_opt_revision_t external_pegrev, 231299742Sdim apr_pool_t *pool) 232299742Sdim{ 233299742Sdim const char *rev_str; 234299742Sdim const char *peg_rev_str; 235299742Sdim 236299742Sdim switch (info->format) 237299742Sdim { 238299742Sdim case svn_wc__external_description_format_1: 239299742Sdim if (external_pegrev.kind == svn_opt_revision_unspecified) 240299742Sdim { 241299742Sdim /* If info->rev_str is NULL, this yields an empty string. */ 242299742Sdim rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); 243299742Sdim } 244299742Sdim else if (info->rev_str && item->revision.kind != svn_opt_revision_head) 245299742Sdim rev_str = apr_psprintf(pool, "%s ", info->rev_str); 246299742Sdim else 247299742Sdim { 248299742Sdim /* ### can't handle svn_opt_revision_date without info->rev_str */ 249299742Sdim SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); 250299742Sdim rev_str = apr_psprintf(pool, "-r%ld ", 251299742Sdim external_pegrev.value.number); 252299742Sdim } 253299742Sdim 254299742Sdim *new_external_description = 255299742Sdim apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool), 256299742Sdim rev_str, 257299742Sdim maybe_quote(item->url, pool)); 258299742Sdim break; 259299742Sdim 260299742Sdim case svn_wc__external_description_format_2: 261299742Sdim if (external_pegrev.kind == svn_opt_revision_unspecified) 262299742Sdim { 263299742Sdim /* If info->rev_str is NULL, this yields an empty string. */ 264299742Sdim rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); 265299742Sdim } 266299742Sdim else if (info->rev_str && item->revision.kind != svn_opt_revision_head) 267299742Sdim rev_str = apr_psprintf(pool, "%s ", info->rev_str); 268299742Sdim else 269299742Sdim rev_str = ""; 270299742Sdim 271299742Sdim if (external_pegrev.kind == svn_opt_revision_unspecified) 272299742Sdim peg_rev_str = info->peg_rev_str ? info->peg_rev_str : ""; 273299742Sdim else if (info->peg_rev_str && 274299742Sdim item->peg_revision.kind != svn_opt_revision_head) 275299742Sdim peg_rev_str = info->peg_rev_str; 276299742Sdim else 277299742Sdim { 278299742Sdim /* ### can't handle svn_opt_revision_date without info->rev_str */ 279299742Sdim SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); 280299742Sdim peg_rev_str = apr_psprintf(pool, "@%ld", 281299742Sdim external_pegrev.value.number); 282299742Sdim } 283299742Sdim 284299742Sdim *new_external_description = 285299742Sdim apr_psprintf(pool, "%s%s %s\n", rev_str, 286299742Sdim maybe_quote(apr_psprintf(pool, "%s%s", item->url, 287299742Sdim peg_rev_str), 288299742Sdim pool), 289299742Sdim maybe_quote(item->target_dir, pool)); 290299742Sdim break; 291299742Sdim 292299742Sdim default: 293299742Sdim return svn_error_createf( 294299742Sdim SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 295299742Sdim _("%s property defined at '%s' is using an unsupported " 296299742Sdim "syntax"), SVN_PROP_EXTERNALS, 297299742Sdim svn_dirent_local_style(local_abspath_or_url, pool)); 298299742Sdim } 299299742Sdim 300299742Sdim return SVN_NO_ERROR; 301299742Sdim} 302299742Sdim 303299742Sdim/* Pin all externals listed in EXTERNALS_PROP_VAL to their 304299742Sdim * last-changed revision. Set *PINNED_EXTERNALS to a new property 305299742Sdim * value allocated in RESULT_POOL, or to NULL if none of the externals 306299742Sdim * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the 307299742Sdim * path or URL defining the svn:externals property. Use SCRATCH_POOL 308299742Sdim * for temporary allocations. 309299742Sdim */ 310299742Sdimstatic svn_error_t * 311299742Sdimpin_externals_prop(svn_string_t **pinned_externals, 312299742Sdim svn_string_t *externals_prop_val, 313299742Sdim const apr_hash_t *externals_to_pin, 314299742Sdim const char *repos_root_url, 315299742Sdim const char *local_abspath_or_url, 316299742Sdim svn_client_ctx_t *ctx, 317299742Sdim apr_pool_t *result_pool, 318299742Sdim apr_pool_t *scratch_pool) 319299742Sdim{ 320299742Sdim svn_stringbuf_t *buf; 321299742Sdim apr_array_header_t *external_items; 322299742Sdim apr_array_header_t *parser_infos; 323299742Sdim apr_array_header_t *items_to_pin; 324299742Sdim int pinned_items; 325299742Sdim int i; 326299742Sdim apr_pool_t *iterpool; 327299742Sdim 328299742Sdim SVN_ERR(svn_wc__parse_externals_description(&external_items, 329299742Sdim &parser_infos, 330299742Sdim local_abspath_or_url, 331299742Sdim externals_prop_val->data, 332299742Sdim FALSE /* canonicalize_url */, 333299742Sdim scratch_pool)); 334299742Sdim 335299742Sdim if (externals_to_pin) 336299742Sdim { 337299742Sdim items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin, 338299742Sdim local_abspath_or_url); 339299742Sdim if (!items_to_pin) 340299742Sdim { 341299742Sdim /* No pinning at all for this path. */ 342299742Sdim *pinned_externals = NULL; 343299742Sdim return SVN_NO_ERROR; 344299742Sdim } 345299742Sdim } 346299742Sdim else 347299742Sdim items_to_pin = NULL; 348299742Sdim 349299742Sdim buf = svn_stringbuf_create_empty(scratch_pool); 350299742Sdim iterpool = svn_pool_create(scratch_pool); 351299742Sdim pinned_items = 0; 352299742Sdim for (i = 0; i < external_items->nelts; i++) 353299742Sdim { 354299742Sdim svn_wc_external_item2_t *item; 355299742Sdim svn_wc__externals_parser_info_t *info; 356299742Sdim svn_opt_revision_t external_pegrev; 357299742Sdim const char *pinned_desc; 358299742Sdim 359299742Sdim svn_pool_clear(iterpool); 360299742Sdim 361299742Sdim item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *); 362299742Sdim info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *); 363299742Sdim 364299742Sdim if (items_to_pin) 365299742Sdim { 366299742Sdim int j; 367299742Sdim svn_wc_external_item2_t *item_to_pin = NULL; 368299742Sdim 369299742Sdim for (j = 0; j < items_to_pin->nelts; j++) 370299742Sdim { 371299742Sdim svn_wc_external_item2_t *const current = 372299742Sdim APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *); 373299742Sdim 374299742Sdim 375299742Sdim if (current 376299742Sdim && 0 == strcmp(item->url, current->url) 377299742Sdim && 0 == strcmp(item->target_dir, current->target_dir)) 378299742Sdim { 379299742Sdim item_to_pin = current; 380299742Sdim break; 381299742Sdim } 382299742Sdim } 383299742Sdim 384299742Sdim /* If this item is not in our list of external items to pin then 385299742Sdim * simply keep the external at its original value. */ 386299742Sdim if (!item_to_pin) 387299742Sdim { 388299742Sdim const char *desc; 389299742Sdim 390299742Sdim external_pegrev.kind = svn_opt_revision_unspecified; 391299742Sdim SVN_ERR(make_external_description(&desc, local_abspath_or_url, 392299742Sdim item, info, external_pegrev, 393299742Sdim iterpool)); 394299742Sdim svn_stringbuf_appendcstr(buf, desc); 395299742Sdim continue; 396299742Sdim } 397299742Sdim } 398299742Sdim 399299742Sdim if (item->peg_revision.kind == svn_opt_revision_date) 400299742Sdim { 401299742Sdim /* Already pinned ... copy the peg date. */ 402299742Sdim external_pegrev.kind = svn_opt_revision_date; 403299742Sdim external_pegrev.value.date = item->peg_revision.value.date; 404299742Sdim } 405299742Sdim else if (item->peg_revision.kind == svn_opt_revision_number) 406299742Sdim { 407299742Sdim /* Already pinned ... copy the peg revision number. */ 408299742Sdim external_pegrev.kind = svn_opt_revision_number; 409299742Sdim external_pegrev.value.number = item->peg_revision.value.number; 410299742Sdim } 411299742Sdim else 412299742Sdim { 413299742Sdim SVN_ERR_ASSERT( 414299742Sdim item->peg_revision.kind == svn_opt_revision_head || 415299742Sdim item->peg_revision.kind == svn_opt_revision_unspecified); 416299742Sdim 417299742Sdim /* We're actually going to change the peg revision. */ 418299742Sdim ++pinned_items; 419299742Sdim 420299742Sdim if (svn_path_is_url(local_abspath_or_url)) 421299742Sdim { 422299742Sdim const char *resolved_url; 423299742Sdim svn_ra_session_t *external_ra_session; 424299742Sdim svn_revnum_t latest_revnum; 425299742Sdim 426299742Sdim SVN_ERR(svn_wc__resolve_relative_external_url( 427299742Sdim &resolved_url, item, repos_root_url, 428299742Sdim local_abspath_or_url, iterpool, iterpool)); 429299742Sdim SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session, 430299742Sdim NULL, resolved_url, 431299742Sdim NULL, NULL, FALSE, 432299742Sdim FALSE, ctx, 433299742Sdim iterpool, 434299742Sdim iterpool)); 435299742Sdim SVN_ERR(svn_ra_get_latest_revnum(external_ra_session, 436299742Sdim &latest_revnum, 437299742Sdim iterpool)); 438299742Sdim 439299742Sdim external_pegrev.kind = svn_opt_revision_number; 440299742Sdim external_pegrev.value.number = latest_revnum; 441299742Sdim } 442299742Sdim else 443299742Sdim { 444299742Sdim const char *external_abspath; 445299742Sdim svn_node_kind_t external_kind; 446299742Sdim svn_revnum_t external_checked_out_rev; 447299742Sdim 448299742Sdim external_abspath = svn_dirent_join(local_abspath_or_url, 449299742Sdim item->target_dir, 450299742Sdim iterpool); 451299742Sdim SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, 452299742Sdim NULL, NULL, ctx->wc_ctx, 453299742Sdim local_abspath_or_url, 454299742Sdim external_abspath, TRUE, 455299742Sdim iterpool, 456299742Sdim iterpool)); 457299742Sdim if (external_kind == svn_node_none) 458299742Sdim return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 459299742Sdim NULL, 460299742Sdim _("Cannot pin external '%s' defined " 461299742Sdim "in %s at '%s' because it is not " 462299742Sdim "checked out in the working copy " 463299742Sdim "at '%s'"), 464299742Sdim item->url, SVN_PROP_EXTERNALS, 465299742Sdim svn_dirent_local_style( 466299742Sdim local_abspath_or_url, iterpool), 467299742Sdim svn_dirent_local_style( 468299742Sdim external_abspath, iterpool)); 469299742Sdim else if (external_kind == svn_node_dir) 470299742Sdim { 471299742Sdim svn_boolean_t is_switched; 472299742Sdim svn_boolean_t is_modified; 473299742Sdim svn_revnum_t min_rev; 474299742Sdim svn_revnum_t max_rev; 475299742Sdim 476299742Sdim /* Perform some sanity checks on the checked-out external. */ 477299742Sdim 478299742Sdim SVN_ERR(svn_wc__has_switched_subtrees(&is_switched, 479299742Sdim ctx->wc_ctx, 480299742Sdim external_abspath, NULL, 481299742Sdim iterpool)); 482299742Sdim if (is_switched) 483299742Sdim return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 484299742Sdim NULL, 485299742Sdim _("Cannot pin external '%s' defined " 486299742Sdim "in %s at '%s' because '%s' has " 487299742Sdim "switched subtrees (switches " 488299742Sdim "cannot be represented in %s)"), 489299742Sdim item->url, SVN_PROP_EXTERNALS, 490299742Sdim svn_dirent_local_style( 491299742Sdim local_abspath_or_url, iterpool), 492299742Sdim svn_dirent_local_style( 493299742Sdim external_abspath, iterpool), 494299742Sdim SVN_PROP_EXTERNALS); 495299742Sdim 496299742Sdim SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, 497299742Sdim external_abspath, TRUE, 498299742Sdim ctx->cancel_func, 499299742Sdim ctx->cancel_baton, 500299742Sdim iterpool)); 501299742Sdim if (is_modified) 502299742Sdim return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 503299742Sdim NULL, 504299742Sdim _("Cannot pin external '%s' defined " 505299742Sdim "in %s at '%s' because '%s' has " 506299742Sdim "local modifications (local " 507299742Sdim "modifications cannot be " 508299742Sdim "represented in %s)"), 509299742Sdim item->url, SVN_PROP_EXTERNALS, 510299742Sdim svn_dirent_local_style( 511299742Sdim local_abspath_or_url, iterpool), 512299742Sdim svn_dirent_local_style( 513299742Sdim external_abspath, iterpool), 514299742Sdim SVN_PROP_EXTERNALS); 515299742Sdim 516299742Sdim SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx, 517299742Sdim external_abspath, FALSE, 518299742Sdim iterpool)); 519299742Sdim if (min_rev != max_rev) 520299742Sdim return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 521299742Sdim NULL, 522299742Sdim _("Cannot pin external '%s' defined " 523299742Sdim "in %s at '%s' because '%s' is a " 524299742Sdim "mixed-revision working copy " 525299742Sdim "(mixed-revisions cannot be " 526299742Sdim "represented in %s)"), 527299742Sdim item->url, SVN_PROP_EXTERNALS, 528299742Sdim svn_dirent_local_style( 529299742Sdim local_abspath_or_url, iterpool), 530299742Sdim svn_dirent_local_style( 531299742Sdim external_abspath, iterpool), 532299742Sdim SVN_PROP_EXTERNALS); 533299742Sdim external_checked_out_rev = min_rev; 534299742Sdim } 535299742Sdim else 536299742Sdim { 537299742Sdim SVN_ERR_ASSERT(external_kind == svn_node_file); 538299742Sdim SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev, 539299742Sdim NULL, NULL, NULL, 540299742Sdim ctx->wc_ctx, external_abspath, 541299742Sdim iterpool, iterpool)); 542299742Sdim } 543299742Sdim 544299742Sdim external_pegrev.kind = svn_opt_revision_number; 545299742Sdim external_pegrev.value.number = external_checked_out_rev; 546299742Sdim } 547299742Sdim } 548299742Sdim 549299742Sdim SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date || 550299742Sdim external_pegrev.kind == svn_opt_revision_number); 551299742Sdim 552299742Sdim SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url, 553299742Sdim item, info, external_pegrev, iterpool)); 554299742Sdim 555299742Sdim svn_stringbuf_appendcstr(buf, pinned_desc); 556299742Sdim } 557299742Sdim svn_pool_destroy(iterpool); 558299742Sdim 559299742Sdim if (pinned_items > 0) 560299742Sdim *pinned_externals = svn_string_create_from_buf(buf, result_pool); 561299742Sdim else 562299742Sdim *pinned_externals = NULL; 563299742Sdim 564299742Sdim return SVN_NO_ERROR; 565299742Sdim} 566299742Sdim 567299742Sdim/* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths 568299742Sdim * to svn:externals property values (as const char *), where some or all 569299742Sdim * external references have been pinned. 570299742Sdim * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals 571299742Sdim * mentioned in EXTERNALS_TO_PIN. 572299742Sdim * The pinning operation takes place as part of the copy operation for 573299742Sdim * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL 574299742Sdim * to contact the repository containing the externals definition, if neccesary. 575299742Sdim * Use CX to fopen additional RA sessions to external repositories, if 576299742Sdim * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL. 577299742Sdim * Use SCRATCH_POOL for temporary allocations. */ 578299742Sdimstatic svn_error_t * 579299742Sdimresolve_pinned_externals(apr_hash_t **pinned_externals, 580299742Sdim const apr_hash_t *externals_to_pin, 581299742Sdim svn_client__copy_pair_t *pair, 582299742Sdim svn_ra_session_t *ra_session, 583299742Sdim const char *repos_root_url, 584299742Sdim svn_client_ctx_t *ctx, 585299742Sdim apr_pool_t *result_pool, 586299742Sdim apr_pool_t *scratch_pool) 587299742Sdim{ 588299742Sdim const char *old_url = NULL; 589299742Sdim apr_hash_t *externals_props; 590299742Sdim apr_hash_index_t *hi; 591299742Sdim apr_pool_t *iterpool; 592299742Sdim 593299742Sdim *pinned_externals = apr_hash_make(result_pool); 594299742Sdim 595299742Sdim if (svn_path_is_url(pair->src_abspath_or_url)) 596299742Sdim { 597299742Sdim SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 598299742Sdim pair->src_abspath_or_url, 599299742Sdim scratch_pool)); 600299742Sdim externals_props = apr_hash_make(scratch_pool); 601299742Sdim SVN_ERR(svn_client__remote_propget(externals_props, NULL, 602299742Sdim SVN_PROP_EXTERNALS, 603299742Sdim pair->src_abspath_or_url, "", 604299742Sdim svn_node_dir, 605299742Sdim pair->src_revnum, 606299742Sdim ra_session, 607299742Sdim svn_depth_infinity, 608299742Sdim scratch_pool, 609299742Sdim scratch_pool)); 610299742Sdim } 611299742Sdim else 612299742Sdim { 613299742Sdim SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL, 614299742Sdim ctx->wc_ctx, 615299742Sdim pair->src_abspath_or_url, 616299742Sdim svn_depth_infinity, 617299742Sdim scratch_pool, scratch_pool)); 618299742Sdim 619299742Sdim /* ### gather_definitions returns propvals as const char * */ 620299742Sdim for (hi = apr_hash_first(scratch_pool, externals_props); 621299742Sdim hi; 622299742Sdim hi = apr_hash_next(hi)) 623299742Sdim { 624299742Sdim const char *local_abspath_or_url = apr_hash_this_key(hi); 625299742Sdim const char *propval = apr_hash_this_val(hi); 626299742Sdim svn_string_t *new_propval = svn_string_create(propval, scratch_pool); 627299742Sdim 628299742Sdim svn_hash_sets(externals_props, local_abspath_or_url, new_propval); 629299742Sdim } 630299742Sdim } 631299742Sdim 632299742Sdim if (apr_hash_count(externals_props) == 0) 633299742Sdim { 634299742Sdim if (old_url) 635299742Sdim SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); 636299742Sdim return SVN_NO_ERROR; 637299742Sdim } 638299742Sdim 639299742Sdim iterpool = svn_pool_create(scratch_pool); 640299742Sdim for (hi = apr_hash_first(scratch_pool, externals_props); 641299742Sdim hi; 642299742Sdim hi = apr_hash_next(hi)) 643299742Sdim { 644299742Sdim const char *local_abspath_or_url = apr_hash_this_key(hi); 645299742Sdim svn_string_t *externals_propval = apr_hash_this_val(hi); 646299742Sdim const char *relpath; 647299742Sdim svn_string_t *new_propval; 648299742Sdim 649299742Sdim svn_pool_clear(iterpool); 650299742Sdim 651299742Sdim SVN_ERR(pin_externals_prop(&new_propval, externals_propval, 652299742Sdim externals_to_pin, 653299742Sdim repos_root_url, local_abspath_or_url, ctx, 654299742Sdim result_pool, iterpool)); 655299742Sdim if (new_propval) 656299742Sdim { 657299742Sdim if (svn_path_is_url(pair->src_abspath_or_url)) 658299742Sdim relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url, 659299742Sdim local_abspath_or_url, 660299742Sdim result_pool); 661299742Sdim else 662299742Sdim relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url, 663299742Sdim local_abspath_or_url); 664299742Sdim SVN_ERR_ASSERT(relpath); 665299742Sdim 666299742Sdim svn_hash_sets(*pinned_externals, relpath, new_propval); 667299742Sdim } 668299742Sdim } 669299742Sdim svn_pool_destroy(iterpool); 670299742Sdim 671299742Sdim if (old_url) 672299742Sdim SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); 673299742Sdim 674299742Sdim return SVN_NO_ERROR; 675299742Sdim} 676299742Sdim 677299742Sdim 678299742Sdim 679251881Speter/* The guts of do_wc_to_wc_copies */ 680251881Speterstatic svn_error_t * 681251881Speterdo_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep, 682251881Speter const apr_array_header_t *copy_pairs, 683251881Speter const char *dst_parent, 684299742Sdim svn_boolean_t metadata_only, 685299742Sdim svn_boolean_t pin_externals, 686299742Sdim const apr_hash_t *externals_to_pin, 687251881Speter svn_client_ctx_t *ctx, 688251881Speter apr_pool_t *scratch_pool) 689251881Speter{ 690251881Speter int i; 691251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 692251881Speter svn_error_t *err = SVN_NO_ERROR; 693251881Speter 694251881Speter for (i = 0; i < copy_pairs->nelts; i++) 695251881Speter { 696251881Speter const char *dst_abspath; 697251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 698251881Speter svn_client__copy_pair_t *); 699299742Sdim apr_hash_t *pinned_externals = NULL; 700299742Sdim 701251881Speter svn_pool_clear(iterpool); 702251881Speter 703251881Speter /* Check for cancellation */ 704251881Speter if (ctx->cancel_func) 705251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 706251881Speter 707299742Sdim if (pin_externals) 708299742Sdim { 709299742Sdim const char *repos_root_url; 710299742Sdim 711299742Sdim SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url, 712299742Sdim NULL, NULL, NULL, ctx->wc_ctx, 713299742Sdim pair->src_abspath_or_url, FALSE, 714299742Sdim scratch_pool, iterpool)); 715299742Sdim SVN_ERR(resolve_pinned_externals(&pinned_externals, 716299742Sdim externals_to_pin, pair, NULL, 717299742Sdim repos_root_url, ctx, 718299742Sdim iterpool, iterpool)); 719299742Sdim } 720299742Sdim 721251881Speter /* Perform the copy */ 722251881Speter dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name, 723251881Speter iterpool); 724251881Speter *timestamp_sleep = TRUE; 725251881Speter err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath, 726299742Sdim metadata_only, 727251881Speter ctx->cancel_func, ctx->cancel_baton, 728251881Speter ctx->notify_func2, ctx->notify_baton2, iterpool); 729251881Speter if (err) 730251881Speter break; 731299742Sdim 732299742Sdim if (pinned_externals) 733299742Sdim { 734299742Sdim apr_hash_index_t *hi; 735299742Sdim 736299742Sdim for (hi = apr_hash_first(iterpool, pinned_externals); 737299742Sdim hi; 738299742Sdim hi = apr_hash_next(hi)) 739299742Sdim { 740299742Sdim const char *dst_relpath = apr_hash_this_key(hi); 741299742Sdim svn_string_t *externals_propval = apr_hash_this_val(hi); 742299742Sdim const char *local_abspath; 743299742Sdim 744299742Sdim local_abspath = svn_dirent_join(pair->dst_abspath_or_url, 745299742Sdim dst_relpath, iterpool); 746299742Sdim /* ### use a work queue? */ 747299742Sdim SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 748299742Sdim SVN_PROP_EXTERNALS, externals_propval, 749299742Sdim svn_depth_empty, TRUE /* skip_checks */, 750299742Sdim NULL /* changelist_filter */, 751299742Sdim ctx->cancel_func, ctx->cancel_baton, 752299742Sdim NULL, NULL, /* no extra notification */ 753299742Sdim iterpool)); 754299742Sdim } 755299742Sdim } 756251881Speter } 757251881Speter svn_pool_destroy(iterpool); 758251881Speter 759251881Speter SVN_ERR(err); 760251881Speter return SVN_NO_ERROR; 761251881Speter} 762251881Speter 763251881Speter/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary 764251881Speter allocations. */ 765251881Speterstatic svn_error_t * 766251881Speterdo_wc_to_wc_copies(svn_boolean_t *timestamp_sleep, 767251881Speter const apr_array_header_t *copy_pairs, 768299742Sdim svn_boolean_t metadata_only, 769299742Sdim svn_boolean_t pin_externals, 770299742Sdim const apr_hash_t *externals_to_pin, 771251881Speter svn_client_ctx_t *ctx, 772251881Speter apr_pool_t *pool) 773251881Speter{ 774251881Speter const char *dst_parent, *dst_parent_abspath; 775251881Speter 776251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool)); 777251881Speter if (copy_pairs->nelts == 1) 778251881Speter dst_parent = svn_dirent_dirname(dst_parent, pool); 779251881Speter 780251881Speter SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool)); 781251881Speter 782251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 783251881Speter do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent, 784299742Sdim metadata_only, pin_externals, 785299742Sdim externals_to_pin, ctx, pool), 786251881Speter ctx->wc_ctx, dst_parent_abspath, FALSE, pool); 787251881Speter 788251881Speter return SVN_NO_ERROR; 789251881Speter} 790251881Speter 791251881Speter/* The locked bit of do_wc_to_wc_moves. */ 792251881Speterstatic svn_error_t * 793251881Speterdo_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair, 794251881Speter const char *dst_parent_abspath, 795251881Speter svn_boolean_t lock_src, 796251881Speter svn_boolean_t lock_dst, 797251881Speter svn_boolean_t allow_mixed_revisions, 798251881Speter svn_boolean_t metadata_only, 799251881Speter svn_client_ctx_t *ctx, 800251881Speter apr_pool_t *scratch_pool) 801251881Speter{ 802251881Speter const char *dst_abspath; 803251881Speter 804251881Speter dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name, 805251881Speter scratch_pool); 806251881Speter 807251881Speter SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url, 808251881Speter dst_abspath, metadata_only, 809251881Speter allow_mixed_revisions, 810251881Speter ctx->cancel_func, ctx->cancel_baton, 811251881Speter ctx->notify_func2, ctx->notify_baton2, 812251881Speter scratch_pool)); 813251881Speter 814251881Speter return SVN_NO_ERROR; 815251881Speter} 816251881Speter 817251881Speter/* Wrapper to add an optional second lock */ 818251881Speterstatic svn_error_t * 819251881Speterdo_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair, 820251881Speter const char *dst_parent_abspath, 821251881Speter svn_boolean_t lock_src, 822251881Speter svn_boolean_t lock_dst, 823251881Speter svn_boolean_t allow_mixed_revisions, 824251881Speter svn_boolean_t metadata_only, 825251881Speter svn_client_ctx_t *ctx, 826251881Speter apr_pool_t *scratch_pool) 827251881Speter{ 828251881Speter if (lock_dst) 829251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 830251881Speter do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 831251881Speter lock_dst, allow_mixed_revisions, 832251881Speter metadata_only, 833251881Speter ctx, scratch_pool), 834251881Speter ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool); 835251881Speter else 836251881Speter SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 837251881Speter lock_dst, allow_mixed_revisions, 838251881Speter metadata_only, 839251881Speter ctx, scratch_pool)); 840251881Speter 841251881Speter return SVN_NO_ERROR; 842251881Speter} 843251881Speter 844251881Speter/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC 845251881Speter afterwards. Use POOL for temporary allocations. */ 846251881Speterstatic svn_error_t * 847251881Speterdo_wc_to_wc_moves(svn_boolean_t *timestamp_sleep, 848251881Speter const apr_array_header_t *copy_pairs, 849251881Speter const char *dst_path, 850251881Speter svn_boolean_t allow_mixed_revisions, 851251881Speter svn_boolean_t metadata_only, 852251881Speter svn_client_ctx_t *ctx, 853251881Speter apr_pool_t *pool) 854251881Speter{ 855251881Speter int i; 856251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 857251881Speter svn_error_t *err = SVN_NO_ERROR; 858251881Speter 859251881Speter for (i = 0; i < copy_pairs->nelts; i++) 860251881Speter { 861251881Speter const char *src_parent_abspath; 862251881Speter svn_boolean_t lock_src, lock_dst; 863262253Speter const char *src_wcroot_abspath; 864262253Speter const char *dst_wcroot_abspath; 865251881Speter 866251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 867251881Speter svn_client__copy_pair_t *); 868251881Speter svn_pool_clear(iterpool); 869251881Speter 870251881Speter /* Check for cancellation */ 871251881Speter if (ctx->cancel_func) 872251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 873251881Speter 874251881Speter src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url, 875251881Speter iterpool); 876251881Speter 877262253Speter SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath, 878262253Speter ctx->wc_ctx, src_parent_abspath, 879262253Speter iterpool, iterpool)); 880262253Speter SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath, 881262253Speter ctx->wc_ctx, pair->dst_parent_abspath, 882262253Speter iterpool, iterpool)); 883262253Speter 884251881Speter /* We now need to lock the right combination of batons. 885251881Speter Four cases: 886251881Speter 1) src_parent == dst_parent 887251881Speter 2) src_parent is parent of dst_parent 888251881Speter 3) dst_parent is parent of src_parent 889251881Speter 4) src_parent and dst_parent are disjoint 890251881Speter We can handle 1) as either 2) or 3) */ 891251881Speter if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0 892262253Speter || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath, 893262253Speter NULL) 894262253Speter && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath, 895262253Speter NULL))) 896251881Speter { 897251881Speter lock_src = TRUE; 898251881Speter lock_dst = FALSE; 899251881Speter } 900251881Speter else if (svn_dirent_is_child(pair->dst_parent_abspath, 901262253Speter src_parent_abspath, NULL) 902262253Speter && !svn_dirent_is_child(pair->dst_parent_abspath, 903262253Speter src_wcroot_abspath, NULL)) 904251881Speter { 905251881Speter lock_src = FALSE; 906251881Speter lock_dst = TRUE; 907251881Speter } 908251881Speter else 909251881Speter { 910251881Speter lock_src = TRUE; 911251881Speter lock_dst = TRUE; 912251881Speter } 913251881Speter 914251881Speter *timestamp_sleep = TRUE; 915251881Speter 916251881Speter /* Perform the copy and then the delete. */ 917251881Speter if (lock_src) 918251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 919251881Speter do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 920251881Speter lock_src, lock_dst, 921251881Speter allow_mixed_revisions, 922251881Speter metadata_only, 923251881Speter ctx, iterpool), 924251881Speter ctx->wc_ctx, src_parent_abspath, 925251881Speter FALSE, iterpool); 926251881Speter else 927251881Speter SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 928251881Speter lock_src, lock_dst, 929251881Speter allow_mixed_revisions, 930251881Speter metadata_only, 931251881Speter ctx, iterpool)); 932251881Speter 933251881Speter } 934251881Speter svn_pool_destroy(iterpool); 935251881Speter 936251881Speter return svn_error_trace(err); 937251881Speter} 938251881Speter 939251881Speter/* Verify that the destinations stored in COPY_PAIRS are valid working copy 940251881Speter destinations and set pair->dst_parent_abspath and pair->base_name for each 941251881Speter item to the resulting location if they do */ 942251881Speterstatic svn_error_t * 943251881Speterverify_wc_dsts(const apr_array_header_t *copy_pairs, 944251881Speter svn_boolean_t make_parents, 945251881Speter svn_boolean_t is_move, 946253734Speter svn_boolean_t metadata_only, 947251881Speter svn_client_ctx_t *ctx, 948251881Speter apr_pool_t *result_pool, 949251881Speter apr_pool_t *scratch_pool) 950251881Speter{ 951251881Speter int i; 952251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 953251881Speter 954251881Speter /* Check that DST does not exist, but its parent does */ 955251881Speter for (i = 0; i < copy_pairs->nelts; i++) 956251881Speter { 957251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 958251881Speter svn_client__copy_pair_t *); 959251881Speter svn_node_kind_t dst_kind, dst_parent_kind; 960251881Speter 961251881Speter svn_pool_clear(iterpool); 962251881Speter 963251881Speter /* If DST_PATH does not exist, then its basename will become a new 964251881Speter file or dir added to its parent (possibly an implicit '.'). 965251881Speter Else, just error out. */ 966251881Speter SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx, 967251881Speter pair->dst_abspath_or_url, 968251881Speter FALSE /* show_deleted */, 969251881Speter TRUE /* show_hidden */, 970251881Speter iterpool)); 971251881Speter if (dst_kind != svn_node_none) 972251881Speter { 973251881Speter svn_boolean_t is_excluded; 974251881Speter svn_boolean_t is_server_excluded; 975251881Speter 976251881Speter SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded, 977251881Speter &is_server_excluded, ctx->wc_ctx, 978251881Speter pair->dst_abspath_or_url, FALSE, 979251881Speter iterpool)); 980251881Speter 981251881Speter if (is_excluded || is_server_excluded) 982251881Speter { 983251881Speter return svn_error_createf( 984251881Speter SVN_ERR_WC_OBSTRUCTED_UPDATE, 985251881Speter NULL, _("Path '%s' exists, but is excluded"), 986251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, iterpool)); 987251881Speter } 988251881Speter else 989251881Speter return svn_error_createf( 990251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 991251881Speter _("Path '%s' already exists"), 992251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, 993251881Speter scratch_pool)); 994251881Speter } 995251881Speter 996251881Speter /* Check that there is no unversioned obstruction */ 997253734Speter if (metadata_only) 998253734Speter dst_kind = svn_node_none; 999253734Speter else 1000253734Speter SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 1001253734Speter iterpool)); 1002251881Speter 1003251881Speter if (dst_kind != svn_node_none) 1004251881Speter { 1005251881Speter if (is_move 1006251881Speter && copy_pairs->nelts == 1 1007251881Speter && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool), 1008251881Speter svn_dirent_dirname(pair->dst_abspath_or_url, 1009251881Speter iterpool)) == 0) 1010251881Speter { 1011251881Speter const char *dst; 1012251881Speter char *dst_apr; 1013251881Speter apr_status_t apr_err; 1014251881Speter /* We have a rename inside a directory, which might collide 1015251881Speter just because the case insensivity of the filesystem makes 1016251881Speter the source match the destination. */ 1017251881Speter 1018251881Speter SVN_ERR(svn_path_cstring_from_utf8(&dst, 1019251881Speter pair->dst_abspath_or_url, 1020251881Speter scratch_pool)); 1021251881Speter 1022251881Speter apr_err = apr_filepath_merge(&dst_apr, NULL, dst, 1023251881Speter APR_FILEPATH_TRUENAME, iterpool); 1024251881Speter 1025251881Speter if (!apr_err) 1026251881Speter { 1027251881Speter /* And now bring it back to our canonical format */ 1028251881Speter SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool)); 1029251881Speter dst = svn_dirent_canonicalize(dst, iterpool); 1030251881Speter } 1031251881Speter /* else: Don't report this error; just report the normal error */ 1032251881Speter 1033251881Speter if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0) 1034251881Speter { 1035251881Speter /* Ok, we have a single case only rename. Get out of here */ 1036251881Speter svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 1037251881Speter pair->dst_abspath_or_url, result_pool); 1038251881Speter 1039251881Speter svn_pool_destroy(iterpool); 1040251881Speter return SVN_NO_ERROR; 1041251881Speter } 1042251881Speter } 1043251881Speter 1044251881Speter return svn_error_createf( 1045251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 1046251881Speter _("Path '%s' already exists as unversioned node"), 1047251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, 1048251881Speter scratch_pool)); 1049251881Speter } 1050251881Speter 1051251881Speter svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 1052251881Speter pair->dst_abspath_or_url, result_pool); 1053251881Speter 1054251881Speter /* Make sure the destination parent is a directory and produce a clear 1055251881Speter error message if it is not. */ 1056251881Speter SVN_ERR(svn_wc_read_kind2(&dst_parent_kind, 1057251881Speter ctx->wc_ctx, pair->dst_parent_abspath, 1058251881Speter FALSE, TRUE, 1059251881Speter iterpool)); 1060251881Speter if (make_parents && dst_parent_kind == svn_node_none) 1061251881Speter { 1062251881Speter SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath, 1063251881Speter TRUE, ctx, iterpool)); 1064251881Speter } 1065251881Speter else if (dst_parent_kind != svn_node_dir) 1066251881Speter { 1067251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1068251881Speter _("Path '%s' is not a directory"), 1069251881Speter svn_dirent_local_style( 1070251881Speter pair->dst_parent_abspath, scratch_pool)); 1071251881Speter } 1072251881Speter 1073251881Speter SVN_ERR(svn_io_check_path(pair->dst_parent_abspath, 1074251881Speter &dst_parent_kind, scratch_pool)); 1075251881Speter 1076251881Speter if (dst_parent_kind != svn_node_dir) 1077251881Speter return svn_error_createf(SVN_ERR_WC_MISSING, NULL, 1078251881Speter _("Path '%s' is not a directory"), 1079251881Speter svn_dirent_local_style( 1080251881Speter pair->dst_parent_abspath, scratch_pool)); 1081251881Speter } 1082251881Speter 1083251881Speter svn_pool_destroy(iterpool); 1084251881Speter 1085251881Speter return SVN_NO_ERROR; 1086251881Speter} 1087251881Speter 1088251881Speterstatic svn_error_t * 1089251881Speterverify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs, 1090251881Speter svn_boolean_t make_parents, 1091251881Speter svn_boolean_t is_move, 1092253734Speter svn_boolean_t metadata_only, 1093251881Speter svn_client_ctx_t *ctx, 1094251881Speter apr_pool_t *result_pool, 1095251881Speter apr_pool_t *scratch_pool) 1096251881Speter{ 1097251881Speter int i; 1098251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1099251881Speter 1100251881Speter /* Check that all of our SRCs exist. */ 1101251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1102251881Speter { 1103251881Speter svn_boolean_t deleted_ok; 1104251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1105251881Speter svn_client__copy_pair_t *); 1106251881Speter svn_pool_clear(iterpool); 1107251881Speter 1108251881Speter deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base 1109251881Speter || pair->src_op_revision.kind == svn_opt_revision_base); 1110251881Speter 1111251881Speter /* Verify that SRC_PATH exists. */ 1112251881Speter SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx, 1113251881Speter pair->src_abspath_or_url, 1114251881Speter deleted_ok, FALSE, iterpool)); 1115251881Speter if (pair->src_kind == svn_node_none) 1116251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1117251881Speter _("Path '%s' does not exist"), 1118251881Speter svn_dirent_local_style( 1119251881Speter pair->src_abspath_or_url, 1120251881Speter scratch_pool)); 1121251881Speter } 1122251881Speter 1123253734Speter SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx, 1124251881Speter result_pool, iterpool)); 1125251881Speter 1126251881Speter svn_pool_destroy(iterpool); 1127251881Speter 1128251881Speter return SVN_NO_ERROR; 1129251881Speter} 1130251881Speter 1131251881Speter 1132251881Speter/* Path-specific state used as part of path_driver_cb_baton. */ 1133251881Spetertypedef struct path_driver_info_t 1134251881Speter{ 1135251881Speter const char *src_url; 1136251881Speter const char *src_path; 1137251881Speter const char *dst_path; 1138251881Speter svn_node_kind_t src_kind; 1139251881Speter svn_revnum_t src_revnum; 1140251881Speter svn_boolean_t resurrection; 1141251881Speter svn_boolean_t dir_add; 1142251881Speter svn_string_t *mergeinfo; /* the new mergeinfo for the target */ 1143299742Sdim svn_string_t *externals; /* new externals definitions for the target */ 1144299742Sdim svn_boolean_t only_pin_externals; 1145251881Speter} path_driver_info_t; 1146251881Speter 1147251881Speter 1148251881Speter/* The baton used with the path_driver_cb_func() callback for a copy 1149251881Speter or move operation. */ 1150251881Speterstruct path_driver_cb_baton 1151251881Speter{ 1152251881Speter /* The editor (and its state) used to perform the operation. */ 1153251881Speter const svn_delta_editor_t *editor; 1154251881Speter void *edit_baton; 1155251881Speter 1156251881Speter /* A hash of path -> path_driver_info_t *'s. */ 1157251881Speter apr_hash_t *action_hash; 1158251881Speter 1159251881Speter /* Whether the operation is a move or copy. */ 1160251881Speter svn_boolean_t is_move; 1161251881Speter}; 1162251881Speter 1163251881Speterstatic svn_error_t * 1164251881Speterpath_driver_cb_func(void **dir_baton, 1165251881Speter void *parent_baton, 1166251881Speter void *callback_baton, 1167251881Speter const char *path, 1168251881Speter apr_pool_t *pool) 1169251881Speter{ 1170251881Speter struct path_driver_cb_baton *cb_baton = callback_baton; 1171251881Speter svn_boolean_t do_delete = FALSE, do_add = FALSE; 1172251881Speter path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path); 1173251881Speter 1174251881Speter /* Initialize return value. */ 1175251881Speter *dir_baton = NULL; 1176251881Speter 1177251881Speter /* This function should never get an empty PATH. We can neither 1178251881Speter create nor delete the empty PATH, so if someone is calling us 1179251881Speter with such, the code is just plain wrong. */ 1180251881Speter SVN_ERR_ASSERT(! svn_path_is_empty(path)); 1181251881Speter 1182299742Sdim /* Check to see if we need to add the path as a parent directory. */ 1183251881Speter if (path_info->dir_add) 1184251881Speter { 1185251881Speter return cb_baton->editor->add_directory(path, parent_baton, NULL, 1186251881Speter SVN_INVALID_REVNUM, pool, 1187251881Speter dir_baton); 1188251881Speter } 1189251881Speter 1190251881Speter /* If this is a resurrection, we know the source and dest paths are 1191251881Speter the same, and that our driver will only be calling us once. */ 1192251881Speter if (path_info->resurrection) 1193251881Speter { 1194251881Speter /* If this is a move, we do nothing. Otherwise, we do the copy. */ 1195251881Speter if (! cb_baton->is_move) 1196251881Speter do_add = TRUE; 1197251881Speter } 1198251881Speter /* Not a resurrection. */ 1199251881Speter else 1200251881Speter { 1201251881Speter /* If this is a move, we check PATH to see if it is the source 1202251881Speter or the destination of the move. */ 1203251881Speter if (cb_baton->is_move) 1204251881Speter { 1205251881Speter if (strcmp(path_info->src_path, path) == 0) 1206251881Speter do_delete = TRUE; 1207251881Speter else 1208251881Speter do_add = TRUE; 1209251881Speter } 1210251881Speter /* Not a move? This must just be the copy addition. */ 1211251881Speter else 1212251881Speter { 1213299742Sdim do_add = !path_info->only_pin_externals; 1214251881Speter } 1215251881Speter } 1216251881Speter 1217251881Speter if (do_delete) 1218251881Speter { 1219251881Speter SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM, 1220251881Speter parent_baton, pool)); 1221251881Speter } 1222251881Speter if (do_add) 1223251881Speter { 1224251881Speter SVN_ERR(svn_path_check_valid(path, pool)); 1225251881Speter 1226251881Speter if (path_info->src_kind == svn_node_file) 1227251881Speter { 1228251881Speter void *file_baton; 1229251881Speter SVN_ERR(cb_baton->editor->add_file(path, parent_baton, 1230251881Speter path_info->src_url, 1231251881Speter path_info->src_revnum, 1232251881Speter pool, &file_baton)); 1233251881Speter if (path_info->mergeinfo) 1234251881Speter SVN_ERR(cb_baton->editor->change_file_prop(file_baton, 1235251881Speter SVN_PROP_MERGEINFO, 1236251881Speter path_info->mergeinfo, 1237251881Speter pool)); 1238251881Speter SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool)); 1239251881Speter } 1240251881Speter else 1241251881Speter { 1242251881Speter SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, 1243251881Speter path_info->src_url, 1244251881Speter path_info->src_revnum, 1245251881Speter pool, dir_baton)); 1246251881Speter if (path_info->mergeinfo) 1247251881Speter SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, 1248251881Speter SVN_PROP_MERGEINFO, 1249251881Speter path_info->mergeinfo, 1250251881Speter pool)); 1251251881Speter } 1252251881Speter } 1253299742Sdim 1254299742Sdim if (path_info->externals) 1255299742Sdim { 1256299742Sdim if (*dir_baton == NULL) 1257299742Sdim SVN_ERR(cb_baton->editor->open_directory(path, parent_baton, 1258299742Sdim SVN_INVALID_REVNUM, 1259299742Sdim pool, dir_baton)); 1260299742Sdim 1261299742Sdim SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS, 1262299742Sdim path_info->externals, pool)); 1263299742Sdim } 1264299742Sdim 1265251881Speter return SVN_NO_ERROR; 1266251881Speter} 1267251881Speter 1268251881Speter 1269251881Speter/* Starting with the path DIR relative to the RA_SESSION's session 1270251881Speter URL, work up through DIR's parents until an existing node is found. 1271251881Speter Push each nonexistent path onto the array NEW_DIRS, allocating in 1272251881Speter POOL. Raise an error if the existing node is not a directory. 1273251881Speter 1274251881Speter ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 1275251881Speter ### implementation susceptible to race conditions. */ 1276251881Speterstatic svn_error_t * 1277251881Speterfind_absent_parents1(svn_ra_session_t *ra_session, 1278251881Speter const char *dir, 1279251881Speter apr_array_header_t *new_dirs, 1280251881Speter apr_pool_t *pool) 1281251881Speter{ 1282251881Speter svn_node_kind_t kind; 1283251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1284251881Speter 1285251881Speter SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind, 1286251881Speter iterpool)); 1287251881Speter 1288251881Speter while (kind == svn_node_none) 1289251881Speter { 1290251881Speter svn_pool_clear(iterpool); 1291251881Speter 1292251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = dir; 1293251881Speter dir = svn_dirent_dirname(dir, pool); 1294251881Speter 1295251881Speter SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, 1296251881Speter &kind, iterpool)); 1297251881Speter } 1298251881Speter 1299251881Speter if (kind != svn_node_dir) 1300251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1301251881Speter _("Path '%s' already exists, but is not a " 1302251881Speter "directory"), dir); 1303251881Speter 1304251881Speter svn_pool_destroy(iterpool); 1305251881Speter return SVN_NO_ERROR; 1306251881Speter} 1307251881Speter 1308251881Speter/* Starting with the URL *TOP_DST_URL which is also the root of 1309251881Speter RA_SESSION, work up through its parents until an existing node is 1310251881Speter found. Push each nonexistent URL onto the array NEW_DIRS, 1311251881Speter allocating in POOL. Raise an error if the existing node is not a 1312251881Speter directory. 1313251881Speter 1314251881Speter Set *TOP_DST_URL and the RA session's root to the existing node's URL. 1315251881Speter 1316251881Speter ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 1317251881Speter ### implementation susceptible to race conditions. */ 1318251881Speterstatic svn_error_t * 1319251881Speterfind_absent_parents2(svn_ra_session_t *ra_session, 1320251881Speter const char **top_dst_url, 1321251881Speter apr_array_header_t *new_dirs, 1322251881Speter apr_pool_t *pool) 1323251881Speter{ 1324251881Speter const char *root_url = *top_dst_url; 1325251881Speter svn_node_kind_t kind; 1326251881Speter 1327251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 1328251881Speter pool)); 1329251881Speter 1330251881Speter while (kind == svn_node_none) 1331251881Speter { 1332251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = root_url; 1333251881Speter root_url = svn_uri_dirname(root_url, pool); 1334251881Speter 1335251881Speter SVN_ERR(svn_ra_reparent(ra_session, root_url, pool)); 1336251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 1337251881Speter pool)); 1338251881Speter } 1339251881Speter 1340251881Speter if (kind != svn_node_dir) 1341251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1342251881Speter _("Path '%s' already exists, but is not a directory"), 1343251881Speter root_url); 1344251881Speter 1345251881Speter *top_dst_url = root_url; 1346251881Speter return SVN_NO_ERROR; 1347251881Speter} 1348251881Speter 1349299742Sdim/* Queue property changes for pinning svn:externals properties set on 1350299742Sdim * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS 1351299742Sdim * is keyed by the relative path of each descendant which should have some 1352299742Sdim * or all of its externals pinned, with the corresponding pinned svn:externals 1353299742Sdim * properties as values. Property changes are queued in a new list of path 1354299742Sdim * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an 1355299742Sdim * existing item is found for the descendant. Allocate results in RESULT_POOL. 1356299742Sdim * Use SCRATCH_POOL for temporary allocations. */ 1357251881Speterstatic svn_error_t * 1358299742Sdimqueue_externals_change_path_infos(apr_array_header_t *new_path_infos, 1359299742Sdim apr_array_header_t *path_infos, 1360299742Sdim apr_hash_t *pinned_externals, 1361299742Sdim path_driver_info_t *parent_info, 1362299742Sdim apr_pool_t *result_pool, 1363299742Sdim apr_pool_t *scratch_pool) 1364299742Sdim{ 1365299742Sdim apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1366299742Sdim apr_hash_index_t *hi; 1367299742Sdim 1368299742Sdim for (hi = apr_hash_first(scratch_pool, pinned_externals); 1369299742Sdim hi; 1370299742Sdim hi = apr_hash_next(hi)) 1371299742Sdim { 1372299742Sdim const char *dst_relpath = apr_hash_this_key(hi); 1373299742Sdim svn_string_t *externals_prop = apr_hash_this_val(hi); 1374299742Sdim const char *src_url; 1375299742Sdim path_driver_info_t *info; 1376299742Sdim int i; 1377299742Sdim 1378299742Sdim svn_pool_clear(iterpool); 1379299742Sdim 1380299742Sdim src_url = svn_path_url_add_component2(parent_info->src_url, 1381299742Sdim dst_relpath, iterpool); 1382299742Sdim 1383299742Sdim /* Try to find a path info the external change can be applied to. */ 1384299742Sdim info = NULL; 1385299742Sdim for (i = 0; i < path_infos->nelts; i++) 1386299742Sdim { 1387299742Sdim path_driver_info_t *existing_info; 1388299742Sdim 1389299742Sdim existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1390299742Sdim if (strcmp(src_url, existing_info->src_url) == 0) 1391299742Sdim { 1392299742Sdim info = existing_info; 1393299742Sdim break; 1394299742Sdim } 1395299742Sdim } 1396299742Sdim 1397299742Sdim if (info == NULL) 1398299742Sdim { 1399299742Sdim /* A copied-along child needs its externals pinned. 1400299742Sdim Create a new path info for this property change. */ 1401299742Sdim info = apr_pcalloc(result_pool, sizeof(*info)); 1402299742Sdim info->src_url = svn_path_url_add_component2( 1403299742Sdim parent_info->src_url, dst_relpath, 1404299742Sdim result_pool); 1405299742Sdim info->src_path = NULL; /* Only needed on copied dirs */ 1406299742Sdim info->dst_path = svn_relpath_join(parent_info->dst_path, 1407299742Sdim dst_relpath, 1408299742Sdim result_pool); 1409299742Sdim info->src_kind = svn_node_dir; 1410299742Sdim info->only_pin_externals = TRUE; 1411299742Sdim APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info; 1412299742Sdim } 1413299742Sdim 1414299742Sdim info->externals = externals_prop; 1415299742Sdim } 1416299742Sdim 1417299742Sdim svn_pool_destroy(iterpool); 1418299742Sdim 1419299742Sdim return SVN_NO_ERROR; 1420299742Sdim} 1421299742Sdim 1422299742Sdimstatic svn_error_t * 1423251881Speterrepos_to_repos_copy(const apr_array_header_t *copy_pairs, 1424251881Speter svn_boolean_t make_parents, 1425251881Speter const apr_hash_t *revprop_table, 1426251881Speter svn_commit_callback2_t commit_callback, 1427251881Speter void *commit_baton, 1428251881Speter svn_client_ctx_t *ctx, 1429251881Speter svn_boolean_t is_move, 1430299742Sdim svn_boolean_t pin_externals, 1431299742Sdim const apr_hash_t *externals_to_pin, 1432251881Speter apr_pool_t *pool) 1433251881Speter{ 1434251881Speter svn_error_t *err; 1435251881Speter apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts, 1436251881Speter sizeof(const char *)); 1437251881Speter apr_hash_t *action_hash = apr_hash_make(pool); 1438251881Speter apr_array_header_t *path_infos; 1439251881Speter const char *top_url, *top_url_all, *top_url_dst; 1440251881Speter const char *message, *repos_root; 1441251881Speter svn_ra_session_t *ra_session = NULL; 1442251881Speter const svn_delta_editor_t *editor; 1443251881Speter void *edit_baton; 1444251881Speter struct path_driver_cb_baton cb_baton; 1445251881Speter apr_array_header_t *new_dirs = NULL; 1446251881Speter apr_hash_t *commit_revprops; 1447299742Sdim apr_array_header_t *pin_externals_only_infos = NULL; 1448251881Speter int i; 1449251881Speter svn_client__copy_pair_t *first_pair = 1450251881Speter APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 1451251881Speter 1452251881Speter /* Open an RA session to the first copy pair's destination. We'll 1453251881Speter be verifying that every one of our copy source and destination 1454251881Speter URLs is or is beneath this sucker's repository root URL as a form 1455251881Speter of a cheap(ish) sanity check. */ 1456251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, 1457251881Speter first_pair->src_abspath_or_url, NULL, 1458251881Speter ctx, pool, pool)); 1459251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 1460251881Speter 1461251881Speter /* Verify that sources and destinations are all at or under 1462251881Speter REPOS_ROOT. While here, create a path_info struct for each 1463251881Speter src/dst pair and initialize portions of it with normalized source 1464251881Speter location information. */ 1465251881Speter path_infos = apr_array_make(pool, copy_pairs->nelts, 1466251881Speter sizeof(path_driver_info_t *)); 1467251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1468251881Speter { 1469251881Speter path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1470251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1471251881Speter svn_client__copy_pair_t *); 1472251881Speter apr_hash_t *mergeinfo; 1473251881Speter 1474251881Speter /* Are the source and destination URLs at or under REPOS_ROOT? */ 1475251881Speter if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url) 1476251881Speter && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url))) 1477251881Speter return svn_error_create 1478251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1479251881Speter _("Source and destination URLs appear not to point to the " 1480251881Speter "same repository.")); 1481251881Speter 1482251881Speter /* Run the history function to get the source's URL and revnum in the 1483251881Speter operational revision. */ 1484251881Speter SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 1485251881Speter SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url, 1486251881Speter &pair->src_revnum, 1487251881Speter NULL, NULL, 1488251881Speter ra_session, 1489251881Speter pair->src_abspath_or_url, 1490251881Speter &pair->src_peg_revision, 1491251881Speter &pair->src_op_revision, NULL, 1492251881Speter ctx, pool)); 1493251881Speter 1494251881Speter /* Go ahead and grab mergeinfo from the source, too. */ 1495251881Speter SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 1496251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 1497251881Speter &mergeinfo, ra_session, 1498251881Speter pair->src_abspath_or_url, pair->src_revnum, 1499251881Speter svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 1500251881Speter if (mergeinfo) 1501251881Speter SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool)); 1502251881Speter 1503251881Speter /* Plop an INFO structure onto our array thereof. */ 1504251881Speter info->src_url = pair->src_abspath_or_url; 1505251881Speter info->src_revnum = pair->src_revnum; 1506251881Speter info->resurrection = FALSE; 1507251881Speter APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info; 1508251881Speter } 1509251881Speter 1510251881Speter /* If this is a move, we have to open our session to the longest 1511251881Speter path common to all SRC_URLS and DST_URLS in the repository so we 1512251881Speter can do existence checks on all paths, and so we can operate on 1513251881Speter all paths in the case of a move. But if this is *not* a move, 1514251881Speter then opening our session at the longest path common to sources 1515251881Speter *and* destinations might be an optimization when the user is 1516251881Speter authorized to access all that stuff, but could cause the 1517251881Speter operation to fail altogether otherwise. See issue #3242. */ 1518251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all, 1519251881Speter pool)); 1520251881Speter top_url = is_move ? top_url_all : top_url_dst; 1521251881Speter 1522251881Speter /* Check each src/dst pair for resurrection, and verify that TOP_URL 1523251881Speter is anchored high enough to cover all the editor_t activities 1524251881Speter required for this operation. */ 1525251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1526251881Speter { 1527251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1528251881Speter svn_client__copy_pair_t *); 1529251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1530251881Speter path_driver_info_t *); 1531251881Speter 1532251881Speter /* Source and destination are the same? It's a resurrection. */ 1533251881Speter if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0) 1534251881Speter info->resurrection = TRUE; 1535251881Speter 1536251881Speter /* We need to add each dst_URL, and (in a move) we'll need to 1537251881Speter delete each src_URL. Our selection of TOP_URL so far ensures 1538251881Speter that all our destination URLs (and source URLs, for moves) 1539251881Speter are at least as deep as TOP_URL, but we need to make sure 1540251881Speter that TOP_URL is an *ancestor* of all our to-be-edited paths. 1541251881Speter 1542251881Speter Issue #683 is demonstrates this scenario. If you're 1543251881Speter resurrecting a deleted item like this: 'svn cp -rN src_URL 1544251881Speter dst_URL', then src_URL == dst_URL == top_url. In this 1545251881Speter situation, we want to open an RA session to be at least the 1546251881Speter *parent* of all three. */ 1547251881Speter if ((strcmp(top_url, pair->dst_abspath_or_url) == 0) 1548251881Speter && (strcmp(top_url, repos_root) != 0)) 1549251881Speter { 1550251881Speter top_url = svn_uri_dirname(top_url, pool); 1551251881Speter } 1552251881Speter if (is_move 1553251881Speter && (strcmp(top_url, pair->src_abspath_or_url) == 0) 1554251881Speter && (strcmp(top_url, repos_root) != 0)) 1555251881Speter { 1556251881Speter top_url = svn_uri_dirname(top_url, pool); 1557251881Speter } 1558251881Speter } 1559251881Speter 1560251881Speter /* Point the RA session to our current TOP_URL. */ 1561251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 1562251881Speter 1563251881Speter /* If we're allowed to create nonexistent parent directories of our 1564251881Speter destinations, then make a list in NEW_DIRS of the parent 1565251881Speter directories of the destination that don't yet exist. */ 1566251881Speter if (make_parents) 1567251881Speter { 1568251881Speter new_dirs = apr_array_make(pool, 0, sizeof(const char *)); 1569251881Speter 1570251881Speter /* If this is a move, TOP_URL is at least the common ancestor of 1571251881Speter all the paths (sources and destinations) involved. Assuming 1572251881Speter the sources exist (which is fair, because if they don't, this 1573251881Speter whole operation will fail anyway), TOP_URL must also exist. 1574251881Speter So it's the paths between TOP_URL and the destinations which 1575251881Speter we have to check for existence. But here, we take advantage 1576251881Speter of the knowledge of our caller. We know that if there are 1577251881Speter multiple copy/move operations being requested, then the 1578251881Speter destinations of the copies/moves will all be siblings of one 1579251881Speter another. Therefore, we need only to check for the 1580251881Speter nonexistent paths between TOP_URL and *one* of our 1581251881Speter destinations to find nonexistent parents of all of them. */ 1582251881Speter if (is_move) 1583251881Speter { 1584251881Speter /* Imagine a situation where the user tries to copy an 1585251881Speter existing source directory to nonexistent directory with 1586251881Speter --parents options specified: 1587251881Speter 1588251881Speter svn copy --parents URL/src URL/dst 1589251881Speter 1590251881Speter where src exists and dst does not. If the dirname of the 1591251881Speter destination path is equal to TOP_URL, 1592251881Speter do not try to add dst to the NEW_DIRS list since it 1593251881Speter will be added to the commit items array later in this 1594251881Speter function. */ 1595251881Speter const char *dir = svn_uri_skip_ancestor( 1596251881Speter top_url, 1597251881Speter svn_uri_dirname(first_pair->dst_abspath_or_url, 1598251881Speter pool), 1599251881Speter pool); 1600251881Speter if (dir && *dir) 1601251881Speter SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool)); 1602251881Speter } 1603251881Speter /* If, however, this is *not* a move, TOP_URL only points to the 1604251881Speter common ancestor of our destination path(s), or possibly one 1605251881Speter level higher. We'll need to do an existence crawl toward the 1606251881Speter root of the repository, starting with one of our destinations 1607251881Speter (see "... take advantage of the knowledge of our caller ..." 1608251881Speter above), and possibly adjusting TOP_URL as we go. */ 1609251881Speter else 1610251881Speter { 1611251881Speter apr_array_header_t *new_urls = 1612251881Speter apr_array_make(pool, 0, sizeof(const char *)); 1613251881Speter SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool)); 1614251881Speter 1615251881Speter /* Convert absolute URLs into relpaths relative to TOP_URL. */ 1616251881Speter for (i = 0; i < new_urls->nelts; i++) 1617251881Speter { 1618251881Speter const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *); 1619251881Speter const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool); 1620251881Speter 1621251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = dir; 1622251881Speter } 1623251881Speter } 1624251881Speter } 1625251881Speter 1626251881Speter /* For each src/dst pair, check to see if that SRC_URL is a child of 1627251881Speter the DST_URL (excepting the case where DST_URL is the repo root). 1628251881Speter If it is, and the parent of DST_URL is the current TOP_URL, then we 1629251881Speter need to reparent the session one directory higher, the parent of 1630251881Speter the DST_URL. */ 1631251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1632251881Speter { 1633251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1634251881Speter svn_client__copy_pair_t *); 1635251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1636251881Speter path_driver_info_t *); 1637251881Speter const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url, 1638251881Speter pair->src_abspath_or_url, 1639251881Speter pool); 1640251881Speter 1641251881Speter if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0) 1642251881Speter && (relpath != NULL && *relpath != '\0')) 1643251881Speter { 1644251881Speter info->resurrection = TRUE; 1645289166Speter top_url = svn_uri_get_longest_ancestor( 1646289166Speter top_url, 1647289166Speter svn_uri_dirname(pair->dst_abspath_or_url, pool), 1648289166Speter pool); 1649251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 1650251881Speter } 1651251881Speter } 1652251881Speter 1653251881Speter /* Get the portions of the SRC and DST URLs that are relative to 1654251881Speter TOP_URL (URI-decoding them while we're at it), verify that the 1655251881Speter source exists and the proposed destination does not, and toss 1656251881Speter what we've learned into the INFO array. (For copies -- that is, 1657251881Speter non-moves -- the relative source URL NULL because it isn't a 1658251881Speter child of the TOP_URL at all. That's okay, we'll deal with 1659251881Speter it.) */ 1660251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1661251881Speter { 1662251881Speter svn_client__copy_pair_t *pair = 1663251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1664251881Speter path_driver_info_t *info = 1665251881Speter APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1666251881Speter svn_node_kind_t dst_kind; 1667251881Speter const char *src_rel, *dst_rel; 1668251881Speter 1669251881Speter src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool); 1670251881Speter if (src_rel) 1671251881Speter { 1672251881Speter SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1673251881Speter &info->src_kind, pool)); 1674251881Speter } 1675251881Speter else 1676251881Speter { 1677251881Speter const char *old_url; 1678251881Speter 1679251881Speter src_rel = NULL; 1680251881Speter SVN_ERR_ASSERT(! is_move); 1681251881Speter 1682251881Speter SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 1683251881Speter pair->src_abspath_or_url, 1684251881Speter pool)); 1685251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum, 1686251881Speter &info->src_kind, pool)); 1687251881Speter SVN_ERR(svn_ra_reparent(ra_session, old_url, pool)); 1688251881Speter } 1689251881Speter if (info->src_kind == svn_node_none) 1690251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1691251881Speter _("Path '%s' does not exist in revision %ld"), 1692251881Speter pair->src_abspath_or_url, pair->src_revnum); 1693251881Speter 1694251881Speter /* Figure out the basename that will result from this operation, 1695251881Speter and ensure that we aren't trying to overwrite existing paths. */ 1696251881Speter dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool); 1697251881Speter SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1698251881Speter &dst_kind, pool)); 1699251881Speter if (dst_kind != svn_node_none) 1700299742Sdim { 1701299742Sdim const char *path = svn_uri_skip_ancestor(repos_root, 1702299742Sdim pair->dst_abspath_or_url, 1703299742Sdim pool); 1704299742Sdim return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1705299742Sdim _("Path '/%s' already exists"), path); 1706299742Sdim } 1707251881Speter 1708251881Speter /* More info for our INFO structure. */ 1709299742Sdim info->src_path = src_rel; /* May be NULL, if outside RA session scope */ 1710251881Speter info->dst_path = dst_rel; 1711251881Speter 1712251881Speter svn_hash_sets(action_hash, info->dst_path, info); 1713251881Speter if (is_move && (! info->resurrection)) 1714251881Speter svn_hash_sets(action_hash, info->src_path, info); 1715299742Sdim 1716299742Sdim if (pin_externals) 1717299742Sdim { 1718299742Sdim apr_hash_t *pinned_externals; 1719299742Sdim 1720299742Sdim SVN_ERR(resolve_pinned_externals(&pinned_externals, 1721299742Sdim externals_to_pin, pair, 1722299742Sdim ra_session, repos_root, 1723299742Sdim ctx, pool, pool)); 1724299742Sdim if (pin_externals_only_infos == NULL) 1725299742Sdim { 1726299742Sdim pin_externals_only_infos = 1727299742Sdim apr_array_make(pool, 0, sizeof(path_driver_info_t *)); 1728299742Sdim } 1729299742Sdim SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos, 1730299742Sdim path_infos, 1731299742Sdim pinned_externals, 1732299742Sdim info, pool, pool)); 1733299742Sdim } 1734251881Speter } 1735251881Speter 1736251881Speter if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1737251881Speter { 1738251881Speter /* Produce a list of new paths to add, and provide it to the 1739251881Speter mechanism used to acquire a log message. */ 1740251881Speter svn_client_commit_item3_t *item; 1741251881Speter const char *tmp_file; 1742251881Speter apr_array_header_t *commit_items 1743251881Speter = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item)); 1744251881Speter 1745251881Speter /* Add any intermediate directories to the message */ 1746251881Speter if (make_parents) 1747251881Speter { 1748251881Speter for (i = 0; i < new_dirs->nelts; i++) 1749251881Speter { 1750251881Speter const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1751251881Speter 1752251881Speter item = svn_client_commit_item3_create(pool); 1753251881Speter item->url = svn_path_url_add_component2(top_url, relpath, pool); 1754299742Sdim item->kind = svn_node_dir; 1755251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1756251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1757251881Speter } 1758251881Speter } 1759251881Speter 1760251881Speter for (i = 0; i < path_infos->nelts; i++) 1761251881Speter { 1762251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1763251881Speter path_driver_info_t *); 1764251881Speter 1765251881Speter item = svn_client_commit_item3_create(pool); 1766251881Speter item->url = svn_path_url_add_component2(top_url, info->dst_path, 1767251881Speter pool); 1768299742Sdim item->kind = info->src_kind; 1769299742Sdim item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD 1770299742Sdim | SVN_CLIENT_COMMIT_ITEM_IS_COPY; 1771299742Sdim item->copyfrom_url = info->src_url; 1772299742Sdim item->copyfrom_rev = info->src_revnum; 1773251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1774251881Speter 1775251881Speter if (is_move && (! info->resurrection)) 1776251881Speter { 1777299742Sdim item = svn_client_commit_item3_create(pool); 1778251881Speter item->url = svn_path_url_add_component2(top_url, info->src_path, 1779251881Speter pool); 1780299742Sdim item->kind = info->src_kind; 1781251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1782251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1783251881Speter } 1784251881Speter } 1785251881Speter 1786251881Speter SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1787251881Speter ctx, pool)); 1788251881Speter if (! message) 1789251881Speter return SVN_NO_ERROR; 1790251881Speter } 1791251881Speter else 1792251881Speter message = ""; 1793251881Speter 1794251881Speter /* Setup our PATHS for the path-based editor drive. */ 1795251881Speter /* First any intermediate directories. */ 1796251881Speter if (make_parents) 1797251881Speter { 1798251881Speter for (i = 0; i < new_dirs->nelts; i++) 1799251881Speter { 1800251881Speter const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1801251881Speter path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1802251881Speter 1803251881Speter info->dst_path = relpath; 1804251881Speter info->dir_add = TRUE; 1805251881Speter 1806251881Speter APR_ARRAY_PUSH(paths, const char *) = relpath; 1807251881Speter svn_hash_sets(action_hash, relpath, info); 1808251881Speter } 1809251881Speter } 1810251881Speter 1811251881Speter /* Then our copy destinations and move sources (if any). */ 1812251881Speter for (i = 0; i < path_infos->nelts; i++) 1813251881Speter { 1814251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1815251881Speter path_driver_info_t *); 1816251881Speter 1817251881Speter APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1818251881Speter if (is_move && (! info->resurrection)) 1819251881Speter APR_ARRAY_PUSH(paths, const char *) = info->src_path; 1820251881Speter } 1821251881Speter 1822299742Sdim /* Add any items which only need their externals pinned. */ 1823299742Sdim if (pin_externals_only_infos) 1824299742Sdim { 1825299742Sdim for (i = 0; i < pin_externals_only_infos->nelts; i++) 1826299742Sdim { 1827299742Sdim path_driver_info_t *info; 1828299742Sdim 1829299742Sdim info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *); 1830299742Sdim APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1831299742Sdim svn_hash_sets(action_hash, info->dst_path, info); 1832299742Sdim } 1833299742Sdim } 1834299742Sdim 1835251881Speter SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1836251881Speter message, ctx, pool)); 1837251881Speter 1838251881Speter /* Fetch RA commit editor. */ 1839251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1840251881Speter svn_client__get_shim_callbacks(ctx->wc_ctx, 1841251881Speter NULL, pool))); 1842251881Speter SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1843251881Speter commit_revprops, 1844251881Speter commit_callback, 1845251881Speter commit_baton, 1846251881Speter NULL, TRUE, /* No lock tokens */ 1847251881Speter pool)); 1848251881Speter 1849251881Speter /* Setup the callback baton. */ 1850251881Speter cb_baton.editor = editor; 1851251881Speter cb_baton.edit_baton = edit_baton; 1852251881Speter cb_baton.action_hash = action_hash; 1853251881Speter cb_baton.is_move = is_move; 1854251881Speter 1855251881Speter /* Call the path-based editor driver. */ 1856251881Speter err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1857251881Speter path_driver_cb_func, &cb_baton, pool); 1858251881Speter if (err) 1859251881Speter { 1860251881Speter /* At least try to abort the edit (and fs txn) before throwing err. */ 1861251881Speter return svn_error_compose_create( 1862251881Speter err, 1863251881Speter editor->abort_edit(edit_baton, pool)); 1864251881Speter } 1865251881Speter 1866299742Sdim if (ctx->notify_func2) 1867299742Sdim { 1868299742Sdim svn_wc_notify_t *notify; 1869299742Sdim notify = svn_wc_create_notify_url(top_url, 1870299742Sdim svn_wc_notify_commit_finalizing, 1871299742Sdim pool); 1872299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 1873299742Sdim } 1874299742Sdim 1875251881Speter /* Close the edit. */ 1876251881Speter return svn_error_trace(editor->close_edit(edit_baton, pool)); 1877251881Speter} 1878251881Speter 1879251881Speter/* Baton for check_url_kind */ 1880251881Speterstruct check_url_kind_baton 1881251881Speter{ 1882251881Speter svn_ra_session_t *session; 1883251881Speter const char *repos_root_url; 1884251881Speter svn_boolean_t should_reparent; 1885251881Speter}; 1886251881Speter 1887251881Speter/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */ 1888251881Speterstatic svn_error_t * 1889251881Spetercheck_url_kind(void *baton, 1890251881Speter svn_node_kind_t *kind, 1891251881Speter const char *url, 1892251881Speter svn_revnum_t revision, 1893251881Speter apr_pool_t *scratch_pool) 1894251881Speter{ 1895251881Speter struct check_url_kind_baton *cukb = baton; 1896251881Speter 1897251881Speter /* If we don't have a session or can't use the session, get one */ 1898251881Speter if (!svn_uri__is_ancestor(cukb->repos_root_url, url)) 1899251881Speter *kind = svn_node_none; 1900251881Speter else 1901251881Speter { 1902251881Speter cukb->should_reparent = TRUE; 1903251881Speter 1904251881Speter SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool)); 1905251881Speter 1906251881Speter SVN_ERR(svn_ra_check_path(cukb->session, "", revision, 1907251881Speter kind, scratch_pool)); 1908251881Speter } 1909251881Speter 1910251881Speter return SVN_NO_ERROR; 1911251881Speter} 1912251881Speter 1913299742Sdim/* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL 1914299742Sdim * in the COMMIT_ITEMS list. 1915299742Sdim * If the list does not already have a commit item for COMMIT_URL 1916299742Sdim * add a new commit item for the property change. 1917299742Sdim * Allocate results in RESULT_POOL. 1918299742Sdim * Use SCRATCH_POOL for temporary allocations. */ 1919299742Sdimstatic svn_error_t * 1920299742Sdimqueue_prop_change_commit_items(const char *local_abspath, 1921299742Sdim const char *commit_url, 1922299742Sdim apr_array_header_t *commit_items, 1923299742Sdim const char *propname, 1924299742Sdim svn_string_t *propval, 1925299742Sdim apr_pool_t *result_pool, 1926299742Sdim apr_pool_t *scratch_pool) 1927299742Sdim{ 1928299742Sdim svn_client_commit_item3_t *item = NULL; 1929299742Sdim svn_prop_t *prop; 1930299742Sdim int i; 1931299742Sdim 1932299742Sdim for (i = 0; i < commit_items->nelts; i++) 1933299742Sdim { 1934299742Sdim svn_client_commit_item3_t *existing_item; 1935299742Sdim 1936299742Sdim existing_item = APR_ARRAY_IDX(commit_items, i, 1937299742Sdim svn_client_commit_item3_t *); 1938299742Sdim if (strcmp(existing_item->url, commit_url) == 0) 1939299742Sdim { 1940299742Sdim item = existing_item; 1941299742Sdim break; 1942299742Sdim } 1943299742Sdim } 1944299742Sdim 1945299742Sdim if (item == NULL) 1946299742Sdim { 1947299742Sdim item = svn_client_commit_item3_create(result_pool); 1948299742Sdim item->path = local_abspath; 1949299742Sdim item->url = commit_url; 1950299742Sdim item->kind = svn_node_dir; 1951299742Sdim item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1952299742Sdim 1953299742Sdim item->incoming_prop_changes = apr_array_make(result_pool, 1, 1954299742Sdim sizeof(svn_prop_t *)); 1955299742Sdim APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1956299742Sdim } 1957299742Sdim else 1958299742Sdim item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1959299742Sdim 1960299742Sdim if (item->outgoing_prop_changes == NULL) 1961299742Sdim item->outgoing_prop_changes = apr_array_make(result_pool, 1, 1962299742Sdim sizeof(svn_prop_t *)); 1963299742Sdim 1964299742Sdim prop = apr_palloc(result_pool, sizeof(*prop)); 1965299742Sdim prop->name = propname; 1966299742Sdim prop->value = propval; 1967299742Sdim APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop; 1968299742Sdim 1969299742Sdim return SVN_NO_ERROR; 1970299742Sdim} 1971299742Sdim 1972251881Speter/* ### Copy ... 1973251881Speter * COMMIT_INFO_P is ... 1974251881Speter * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath 1975251881Speter * and each 'dst_abspath_or_url' is a URL. 1976251881Speter * MAKE_PARENTS is ... 1977251881Speter * REVPROP_TABLE is ... 1978251881Speter * CTX is ... */ 1979251881Speterstatic svn_error_t * 1980251881Speterwc_to_repos_copy(const apr_array_header_t *copy_pairs, 1981251881Speter svn_boolean_t make_parents, 1982251881Speter const apr_hash_t *revprop_table, 1983251881Speter svn_commit_callback2_t commit_callback, 1984251881Speter void *commit_baton, 1985299742Sdim svn_boolean_t pin_externals, 1986299742Sdim const apr_hash_t *externals_to_pin, 1987251881Speter svn_client_ctx_t *ctx, 1988251881Speter apr_pool_t *scratch_pool) 1989251881Speter{ 1990251881Speter const char *message; 1991251881Speter const char *top_src_path, *top_dst_url; 1992251881Speter struct check_url_kind_baton cukb; 1993251881Speter const char *top_src_abspath; 1994251881Speter svn_ra_session_t *ra_session; 1995251881Speter const svn_delta_editor_t *editor; 1996299742Sdim#ifdef ENABLE_EV2_SHIMS 1997251881Speter apr_hash_t *relpath_map = NULL; 1998299742Sdim#endif 1999251881Speter void *edit_baton; 2000251881Speter svn_client__committables_t *committables; 2001251881Speter apr_array_header_t *commit_items; 2002251881Speter apr_pool_t *iterpool; 2003251881Speter apr_array_header_t *new_dirs = NULL; 2004251881Speter apr_hash_t *commit_revprops; 2005251881Speter svn_client__copy_pair_t *first_pair; 2006251881Speter apr_pool_t *session_pool = svn_pool_create(scratch_pool); 2007299742Sdim apr_array_header_t *commit_items_for_dav; 2008251881Speter int i; 2009251881Speter 2010251881Speter /* Find the common root of all the source paths */ 2011251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, 2012251881Speter scratch_pool)); 2013251881Speter 2014251881Speter /* Do we need to lock the working copy? 1.6 didn't take a write 2015251881Speter lock, but what happens if the working copy changes during the copy 2016251881Speter operation? */ 2017251881Speter 2018251881Speter iterpool = svn_pool_create(scratch_pool); 2019251881Speter 2020251881Speter /* Determine the longest common ancestor for the destinations, and open an RA 2021251881Speter session to that location. */ 2022251881Speter /* ### But why start by getting the _parent_ of the first one? */ 2023251881Speter /* --- That works because multiple destinations always point to the same 2024251881Speter * directory. I'm rather wondering why we need to find a common 2025251881Speter * destination parent here at all, instead of simply getting 2026251881Speter * top_dst_url from get_copy_pair_ancestors() above? 2027251881Speter * It looks like the entire block of code hanging off this comment 2028251881Speter * is redundant. */ 2029251881Speter first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 2030251881Speter top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool); 2031251881Speter for (i = 1; i < copy_pairs->nelts; i++) 2032251881Speter { 2033251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2034251881Speter svn_client__copy_pair_t *); 2035251881Speter top_dst_url = svn_uri_get_longest_ancestor(top_dst_url, 2036251881Speter pair->dst_abspath_or_url, 2037251881Speter scratch_pool); 2038251881Speter } 2039251881Speter 2040251881Speter SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool)); 2041251881Speter 2042299742Sdim commit_items_for_dav = apr_array_make(session_pool, 0, 2043299742Sdim sizeof(svn_client_commit_item3_t*)); 2044299742Sdim 2045251881Speter /* Open a session to help while determining the exact targets */ 2046251881Speter SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 2047299742Sdim top_src_abspath, 2048299742Sdim commit_items_for_dav, 2049251881Speter FALSE /* write_dav_props */, 2050251881Speter TRUE /* read_dav_props */, 2051251881Speter ctx, 2052251881Speter session_pool, session_pool)); 2053251881Speter 2054251881Speter /* If requested, determine the nearest existing parent of the destination, 2055251881Speter and reparent the ra session there. */ 2056251881Speter if (make_parents) 2057251881Speter { 2058251881Speter new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *)); 2059251881Speter SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs, 2060251881Speter scratch_pool)); 2061251881Speter } 2062251881Speter 2063251881Speter /* Figure out the basename that will result from each copy and check to make 2064251881Speter sure it doesn't exist already. */ 2065251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2066251881Speter { 2067251881Speter svn_node_kind_t dst_kind; 2068251881Speter const char *dst_rel; 2069251881Speter svn_client__copy_pair_t *pair = 2070251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2071251881Speter 2072251881Speter svn_pool_clear(iterpool); 2073251881Speter dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url, 2074251881Speter iterpool); 2075251881Speter SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 2076251881Speter &dst_kind, iterpool)); 2077251881Speter if (dst_kind != svn_node_none) 2078251881Speter { 2079251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 2080251881Speter _("Path '%s' already exists"), 2081251881Speter pair->dst_abspath_or_url); 2082251881Speter } 2083251881Speter } 2084251881Speter 2085251881Speter cukb.session = ra_session; 2086251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool)); 2087251881Speter cukb.should_reparent = FALSE; 2088251881Speter 2089251881Speter /* Crawl the working copy for commit items. */ 2090251881Speter /* ### TODO: Pass check_url_func for issue #3314 handling */ 2091251881Speter SVN_ERR(svn_client__get_copy_committables(&committables, 2092251881Speter copy_pairs, 2093251881Speter check_url_kind, &cukb, 2094251881Speter ctx, scratch_pool, iterpool)); 2095251881Speter 2096251881Speter /* The committables are keyed by the repository root */ 2097251881Speter commit_items = svn_hash_gets(committables->by_repository, 2098251881Speter cukb.repos_root_url); 2099251881Speter SVN_ERR_ASSERT(commit_items != NULL); 2100251881Speter 2101251881Speter if (cukb.should_reparent) 2102251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 2103251881Speter 2104251881Speter /* If we are creating intermediate directories, tack them onto the list 2105251881Speter of committables. */ 2106251881Speter if (make_parents) 2107251881Speter { 2108251881Speter for (i = 0; i < new_dirs->nelts; i++) 2109251881Speter { 2110251881Speter const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 2111251881Speter svn_client_commit_item3_t *item; 2112251881Speter 2113251881Speter item = svn_client_commit_item3_create(scratch_pool); 2114251881Speter item->url = url; 2115299742Sdim item->kind = svn_node_dir; 2116251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 2117251881Speter item->incoming_prop_changes = apr_array_make(scratch_pool, 1, 2118251881Speter sizeof(svn_prop_t *)); 2119251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 2120251881Speter } 2121251881Speter } 2122251881Speter 2123251881Speter /* ### TODO: This extra loop would be unnecessary if this code lived 2124251881Speter ### in svn_client__get_copy_committables(), which is incidentally 2125251881Speter ### only used above (so should really be in this source file). */ 2126251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2127251881Speter { 2128251881Speter apr_hash_t *mergeinfo, *wc_mergeinfo; 2129251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2130251881Speter svn_client__copy_pair_t *); 2131251881Speter svn_client_commit_item3_t *item = 2132251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 2133251881Speter svn_client__pathrev_t *src_origin; 2134251881Speter 2135251881Speter svn_pool_clear(iterpool); 2136251881Speter 2137251881Speter SVN_ERR(svn_client__wc_node_get_origin(&src_origin, 2138251881Speter pair->src_abspath_or_url, 2139251881Speter ctx, iterpool, iterpool)); 2140251881Speter 2141251881Speter /* Set the mergeinfo for the destination to the combined merge 2142251881Speter info known to the WC and the repository. */ 2143251881Speter /* Repository mergeinfo (or NULL if it's locally added)... */ 2144251881Speter if (src_origin) 2145251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 2146251881Speter &mergeinfo, ra_session, src_origin->url, src_origin->rev, 2147251881Speter svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool)); 2148251881Speter else 2149251881Speter mergeinfo = NULL; 2150251881Speter /* ... and WC mergeinfo. */ 2151251881Speter SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 2152251881Speter pair->src_abspath_or_url, 2153251881Speter iterpool, iterpool)); 2154251881Speter if (wc_mergeinfo && mergeinfo) 2155251881Speter SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool, 2156251881Speter iterpool)); 2157251881Speter else if (! mergeinfo) 2158251881Speter mergeinfo = wc_mergeinfo; 2159299742Sdim 2160251881Speter if (mergeinfo) 2161251881Speter { 2162251881Speter /* Push a mergeinfo prop representing MERGEINFO onto the 2163251881Speter * OUTGOING_PROP_CHANGES array. */ 2164251881Speter 2165251881Speter svn_prop_t *mergeinfo_prop 2166299742Sdim = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop)); 2167251881Speter svn_string_t *prop_value; 2168251881Speter 2169251881Speter SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo, 2170299742Sdim scratch_pool)); 2171251881Speter 2172299742Sdim if (!item->outgoing_prop_changes) 2173299742Sdim { 2174299742Sdim item->outgoing_prop_changes = apr_array_make(scratch_pool, 1, 2175299742Sdim sizeof(svn_prop_t *)); 2176299742Sdim } 2177299742Sdim 2178251881Speter mergeinfo_prop->name = SVN_PROP_MERGEINFO; 2179251881Speter mergeinfo_prop->value = prop_value; 2180251881Speter APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) 2181251881Speter = mergeinfo_prop; 2182251881Speter } 2183299742Sdim 2184299742Sdim if (pin_externals) 2185299742Sdim { 2186299742Sdim apr_hash_t *pinned_externals; 2187299742Sdim apr_hash_index_t *hi; 2188299742Sdim 2189299742Sdim SVN_ERR(resolve_pinned_externals(&pinned_externals, 2190299742Sdim externals_to_pin, pair, 2191299742Sdim ra_session, cukb.repos_root_url, 2192299742Sdim ctx, scratch_pool, iterpool)); 2193299742Sdim for (hi = apr_hash_first(scratch_pool, pinned_externals); 2194299742Sdim hi; 2195299742Sdim hi = apr_hash_next(hi)) 2196299742Sdim { 2197299742Sdim const char *dst_relpath = apr_hash_this_key(hi); 2198299742Sdim svn_string_t *externals_propval = apr_hash_this_val(hi); 2199299742Sdim const char *dst_url; 2200299742Sdim const char *commit_url; 2201299742Sdim const char *src_abspath; 2202299742Sdim 2203299742Sdim if (svn_path_is_url(pair->dst_abspath_or_url)) 2204299742Sdim dst_url = pair->dst_abspath_or_url; 2205299742Sdim else 2206299742Sdim SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx, 2207299742Sdim pair->dst_abspath_or_url, 2208299742Sdim scratch_pool, iterpool)); 2209299742Sdim commit_url = svn_path_url_add_component2(dst_url, dst_relpath, 2210299742Sdim scratch_pool); 2211299742Sdim src_abspath = svn_dirent_join(pair->src_abspath_or_url, 2212299742Sdim dst_relpath, iterpool); 2213299742Sdim SVN_ERR(queue_prop_change_commit_items(src_abspath, 2214299742Sdim commit_url, commit_items, 2215299742Sdim SVN_PROP_EXTERNALS, 2216299742Sdim externals_propval, 2217299742Sdim scratch_pool, iterpool)); 2218299742Sdim } 2219299742Sdim } 2220251881Speter } 2221251881Speter 2222299742Sdim if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 2223299742Sdim { 2224299742Sdim const char *tmp_file; 2225299742Sdim 2226299742Sdim SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 2227299742Sdim ctx, scratch_pool)); 2228299742Sdim if (! message) 2229299742Sdim { 2230299742Sdim svn_pool_destroy(iterpool); 2231299742Sdim svn_pool_destroy(session_pool); 2232299742Sdim return SVN_NO_ERROR; 2233299742Sdim } 2234299742Sdim } 2235299742Sdim else 2236299742Sdim message = ""; 2237299742Sdim 2238251881Speter /* Sort and condense our COMMIT_ITEMS. */ 2239251881Speter SVN_ERR(svn_client__condense_commit_items(&top_dst_url, 2240251881Speter commit_items, scratch_pool)); 2241251881Speter 2242299742Sdim /* Add the commit items to the DAV commit item list to provide access 2243299742Sdim to dav properties (for pre http-v2 DAV) */ 2244299742Sdim apr_array_cat(commit_items_for_dav, commit_items); 2245299742Sdim 2246251881Speter#ifdef ENABLE_EV2_SHIMS 2247251881Speter if (commit_items) 2248251881Speter { 2249299742Sdim relpath_map = apr_hash_make(scratch_pool); 2250251881Speter for (i = 0; i < commit_items->nelts; i++) 2251251881Speter { 2252251881Speter svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, 2253251881Speter svn_client_commit_item3_t *); 2254251881Speter const char *relpath; 2255251881Speter 2256251881Speter if (!item->path) 2257251881Speter continue; 2258251881Speter 2259251881Speter svn_pool_clear(iterpool); 2260299742Sdim SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, 2261299742Sdim NULL, NULL, 2262251881Speter ctx->wc_ctx, item->path, FALSE, 2263251881Speter scratch_pool, iterpool)); 2264251881Speter if (relpath) 2265251881Speter svn_hash_sets(relpath_map, relpath, item->path); 2266251881Speter } 2267251881Speter } 2268251881Speter#endif 2269251881Speter 2270299742Sdim SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 2271251881Speter 2272251881Speter SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 2273251881Speter message, ctx, session_pool)); 2274251881Speter 2275251881Speter /* Fetch RA commit editor. */ 2276299742Sdim#ifdef ENABLE_EV2_SHIMS 2277251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 2278251881Speter svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map, 2279251881Speter session_pool))); 2280299742Sdim#endif 2281251881Speter SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 2282251881Speter commit_revprops, 2283251881Speter commit_callback, 2284251881Speter commit_baton, NULL, 2285251881Speter TRUE, /* No lock tokens */ 2286251881Speter session_pool)); 2287251881Speter 2288251881Speter /* Perform the commit. */ 2289251881Speter SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, 2290251881Speter editor, edit_baton, 2291299742Sdim NULL /* notify_path_prefix */, 2292251881Speter NULL, ctx, session_pool, session_pool), 2293251881Speter _("Commit failed (details follow):")); 2294251881Speter 2295251881Speter svn_pool_destroy(iterpool); 2296251881Speter svn_pool_destroy(session_pool); 2297251881Speter 2298251881Speter return SVN_NO_ERROR; 2299251881Speter} 2300251881Speter 2301251881Speter/* A baton for notification_adjust_func(). */ 2302251881Speterstruct notification_adjust_baton 2303251881Speter{ 2304251881Speter svn_wc_notify_func2_t inner_func; 2305251881Speter void *inner_baton; 2306251881Speter const char *checkout_abspath; 2307251881Speter const char *final_abspath; 2308251881Speter}; 2309251881Speter 2310251881Speter/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose 2311251881Speter * baton is BATON->inner_baton) and adjusts the notification paths that 2312251881Speter * start with BATON->checkout_abspath to start instead with 2313251881Speter * BATON->final_abspath. */ 2314251881Speterstatic void 2315251881Speternotification_adjust_func(void *baton, 2316251881Speter const svn_wc_notify_t *notify, 2317251881Speter apr_pool_t *pool) 2318251881Speter{ 2319251881Speter struct notification_adjust_baton *nb = baton; 2320251881Speter svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); 2321251881Speter const char *relpath; 2322251881Speter 2323251881Speter relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); 2324251881Speter inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); 2325251881Speter 2326251881Speter if (nb->inner_func) 2327251881Speter nb->inner_func(nb->inner_baton, inner_notify, pool); 2328251881Speter} 2329251881Speter 2330251881Speter/* Peform each individual copy operation for a repos -> wc copy. A 2331251881Speter helper for repos_to_wc_copy(). 2332251881Speter 2333251881Speter Resolve PAIR->src_revnum to a real revision number if it isn't already. */ 2334251881Speterstatic svn_error_t * 2335251881Speterrepos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, 2336251881Speter svn_client__copy_pair_t *pair, 2337251881Speter svn_boolean_t same_repositories, 2338251881Speter svn_boolean_t ignore_externals, 2339299742Sdim svn_boolean_t pin_externals, 2340299742Sdim const apr_hash_t *externals_to_pin, 2341251881Speter svn_ra_session_t *ra_session, 2342251881Speter svn_client_ctx_t *ctx, 2343251881Speter apr_pool_t *pool) 2344251881Speter{ 2345251881Speter apr_hash_t *src_mergeinfo; 2346251881Speter const char *dst_abspath = pair->dst_abspath_or_url; 2347251881Speter 2348251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 2349251881Speter 2350251881Speter if (!same_repositories && ctx->notify_func2) 2351251881Speter { 2352251881Speter svn_wc_notify_t *notify; 2353251881Speter notify = svn_wc_create_notify_url( 2354251881Speter pair->src_abspath_or_url, 2355251881Speter svn_wc_notify_foreign_copy_begin, 2356251881Speter pool); 2357251881Speter notify->kind = pair->src_kind; 2358251881Speter ctx->notify_func2(ctx->notify_baton2, notify, pool); 2359251881Speter 2360251881Speter /* Allow a theoretical cancel to get through. */ 2361251881Speter if (ctx->cancel_func) 2362251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2363251881Speter } 2364251881Speter 2365251881Speter if (pair->src_kind == svn_node_dir) 2366251881Speter { 2367251881Speter if (same_repositories) 2368251881Speter { 2369251881Speter const char *tmpdir_abspath, *tmp_abspath; 2370251881Speter 2371251881Speter /* Find a temporary location in which to check out the copy source. */ 2372251881Speter SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, 2373251881Speter pool, pool)); 2374251881Speter 2375251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, 2376251881Speter svn_io_file_del_on_close, pool, pool)); 2377251881Speter 2378251881Speter /* Make a new checkout of the requested source. While doing so, 2379251881Speter * resolve pair->src_revnum to an actual revision number in case it 2380251881Speter * was until now 'invalid' meaning 'head'. Ask this function not to 2381251881Speter * sleep for timestamps, by passing a sleep_needed output param. 2382251881Speter * Send notifications for all nodes except the root node, and adjust 2383251881Speter * them to refer to the destination rather than this temporary path. */ 2384251881Speter { 2385251881Speter svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; 2386251881Speter void *old_notify_baton2 = ctx->notify_baton2; 2387251881Speter struct notification_adjust_baton nb; 2388251881Speter svn_error_t *err; 2389251881Speter 2390251881Speter nb.inner_func = ctx->notify_func2; 2391251881Speter nb.inner_baton = ctx->notify_baton2; 2392251881Speter nb.checkout_abspath = tmp_abspath; 2393251881Speter nb.final_abspath = dst_abspath; 2394251881Speter ctx->notify_func2 = notification_adjust_func; 2395251881Speter ctx->notify_baton2 = &nb; 2396251881Speter 2397299742Sdim /* Avoid a chicken-and-egg problem: 2398299742Sdim * If pinning externals we'll need to adjust externals 2399299742Sdim * properties before checking out any externals. 2400299742Sdim * But copy needs to happen before pinning because else there 2401299742Sdim * are no svn:externals properties to pin. */ 2402299742Sdim if (pin_externals) 2403299742Sdim ignore_externals = TRUE; 2404299742Sdim 2405299742Sdim err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep, 2406251881Speter pair->src_original, 2407251881Speter tmp_abspath, 2408251881Speter &pair->src_peg_revision, 2409251881Speter &pair->src_op_revision, 2410251881Speter svn_depth_infinity, 2411251881Speter ignore_externals, FALSE, 2412299742Sdim ra_session, ctx, pool); 2413251881Speter 2414251881Speter ctx->notify_func2 = old_notify_func2; 2415251881Speter ctx->notify_baton2 = old_notify_baton2; 2416251881Speter 2417251881Speter SVN_ERR(err); 2418251881Speter } 2419251881Speter 2420251881Speter *timestamp_sleep = TRUE; 2421251881Speter 2422251881Speter /* Schedule dst_path for addition in parent, with copy history. 2423251881Speter Don't send any notification here. 2424251881Speter Then remove the temporary checkout's .svn dir in preparation for 2425251881Speter moving the rest of it into the final destination. */ 2426251881Speter SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, 2427251881Speter TRUE /* metadata_only */, 2428251881Speter ctx->cancel_func, ctx->cancel_baton, 2429251881Speter NULL, NULL, pool)); 2430251881Speter SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, 2431251881Speter FALSE, pool, pool)); 2432251881Speter SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, 2433251881Speter tmp_abspath, 2434251881Speter FALSE, FALSE, 2435251881Speter ctx->cancel_func, 2436251881Speter ctx->cancel_baton, 2437251881Speter pool)); 2438251881Speter 2439251881Speter /* Move the temporary disk tree into place. */ 2440251881Speter SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool)); 2441251881Speter } 2442251881Speter else 2443251881Speter { 2444251881Speter *timestamp_sleep = TRUE; 2445251881Speter 2446251881Speter SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url, 2447251881Speter dst_abspath, 2448251881Speter &pair->src_peg_revision, 2449251881Speter &pair->src_op_revision, 2450251881Speter svn_depth_infinity, 2451251881Speter FALSE /* make_parents */, 2452251881Speter TRUE /* already_locked */, 2453251881Speter ctx, pool)); 2454251881Speter 2455251881Speter return SVN_NO_ERROR; 2456251881Speter } 2457299742Sdim 2458299742Sdim if (pin_externals) 2459299742Sdim { 2460299742Sdim apr_hash_t *pinned_externals; 2461299742Sdim apr_hash_index_t *hi; 2462299742Sdim apr_pool_t *iterpool; 2463299742Sdim const char *repos_root_url; 2464299742Sdim apr_hash_t *new_externals; 2465299742Sdim apr_hash_t *new_depths; 2466299742Sdim 2467299742Sdim SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); 2468299742Sdim SVN_ERR(resolve_pinned_externals(&pinned_externals, 2469299742Sdim externals_to_pin, pair, 2470299742Sdim ra_session, repos_root_url, 2471299742Sdim ctx, pool, pool)); 2472299742Sdim 2473299742Sdim iterpool = svn_pool_create(pool); 2474299742Sdim for (hi = apr_hash_first(pool, pinned_externals); 2475299742Sdim hi; 2476299742Sdim hi = apr_hash_next(hi)) 2477299742Sdim { 2478299742Sdim const char *dst_relpath = apr_hash_this_key(hi); 2479299742Sdim svn_string_t *externals_propval = apr_hash_this_val(hi); 2480299742Sdim const char *local_abspath; 2481299742Sdim 2482299742Sdim svn_pool_clear(iterpool); 2483299742Sdim 2484299742Sdim local_abspath = svn_dirent_join(pair->dst_abspath_or_url, 2485299742Sdim dst_relpath, iterpool); 2486299742Sdim /* ### use a work queue? */ 2487299742Sdim SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 2488299742Sdim SVN_PROP_EXTERNALS, externals_propval, 2489299742Sdim svn_depth_empty, TRUE /* skip_checks */, 2490299742Sdim NULL /* changelist_filter */, 2491299742Sdim ctx->cancel_func, ctx->cancel_baton, 2492299742Sdim NULL, NULL, /* no extra notification */ 2493299742Sdim iterpool)); 2494299742Sdim } 2495299742Sdim 2496299742Sdim /* Now update all externals in the newly created copy. */ 2497299742Sdim SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, 2498299742Sdim &new_depths, 2499299742Sdim ctx->wc_ctx, 2500299742Sdim dst_abspath, 2501299742Sdim svn_depth_infinity, 2502299742Sdim iterpool, iterpool)); 2503299742Sdim SVN_ERR(svn_client__handle_externals(new_externals, 2504299742Sdim new_depths, 2505299742Sdim repos_root_url, dst_abspath, 2506299742Sdim svn_depth_infinity, 2507299742Sdim timestamp_sleep, 2508299742Sdim ra_session, 2509299742Sdim ctx, iterpool)); 2510299742Sdim svn_pool_destroy(iterpool); 2511299742Sdim } 2512251881Speter } /* end directory case */ 2513251881Speter 2514251881Speter else if (pair->src_kind == svn_node_file) 2515251881Speter { 2516251881Speter apr_hash_t *new_props; 2517251881Speter const char *src_rel; 2518251881Speter svn_stream_t *new_base_contents = svn_stream_buffered(pool); 2519251881Speter 2520251881Speter SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 2521251881Speter pair->src_abspath_or_url, 2522251881Speter pool)); 2523251881Speter /* Fetch the file content. While doing so, resolve pair->src_revnum 2524251881Speter * to an actual revision number if it's 'invalid' meaning 'head'. */ 2525251881Speter SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, 2526251881Speter new_base_contents, 2527251881Speter &pair->src_revnum, &new_props, pool)); 2528251881Speter 2529251881Speter if (new_props && ! same_repositories) 2530251881Speter svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); 2531251881Speter 2532251881Speter *timestamp_sleep = TRUE; 2533251881Speter 2534251881Speter SVN_ERR(svn_wc_add_repos_file4( 2535251881Speter ctx->wc_ctx, dst_abspath, 2536251881Speter new_base_contents, NULL, new_props, NULL, 2537251881Speter same_repositories ? pair->src_abspath_or_url : NULL, 2538251881Speter same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, 2539251881Speter ctx->cancel_func, ctx->cancel_baton, 2540251881Speter pool)); 2541251881Speter } 2542251881Speter 2543251881Speter /* Record the implied mergeinfo (before the notification callback 2544251881Speter is invoked for the root node). */ 2545251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 2546251881Speter &src_mergeinfo, ra_session, 2547251881Speter pair->src_abspath_or_url, pair->src_revnum, 2548251881Speter svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 2549251881Speter SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); 2550251881Speter 2551251881Speter /* Do our own notification for the root node, even if we could possibly 2552251881Speter have delegated it. See also issue #1552. 2553251881Speter 2554251881Speter ### Maybe this notification should mention the mergeinfo change. */ 2555251881Speter if (ctx->notify_func2) 2556251881Speter { 2557251881Speter svn_wc_notify_t *notify = svn_wc_create_notify( 2558251881Speter dst_abspath, svn_wc_notify_add, pool); 2559251881Speter notify->kind = pair->src_kind; 2560299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, pool); 2561251881Speter } 2562251881Speter 2563251881Speter return SVN_NO_ERROR; 2564251881Speter} 2565251881Speter 2566251881Speterstatic svn_error_t * 2567251881Speterrepos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, 2568251881Speter const apr_array_header_t *copy_pairs, 2569251881Speter const char *top_dst_path, 2570251881Speter svn_boolean_t ignore_externals, 2571299742Sdim svn_boolean_t pin_externals, 2572299742Sdim const apr_hash_t *externals_to_pin, 2573251881Speter svn_ra_session_t *ra_session, 2574251881Speter svn_client_ctx_t *ctx, 2575251881Speter apr_pool_t *scratch_pool) 2576251881Speter{ 2577251881Speter int i; 2578251881Speter svn_boolean_t same_repositories; 2579251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2580251881Speter 2581251881Speter /* We've already checked for physical obstruction by a working file. 2582251881Speter But there could also be logical obstruction by an entry whose 2583251881Speter working file happens to be missing.*/ 2584253734Speter SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, 2585253734Speter ctx, scratch_pool, iterpool)); 2586251881Speter 2587251881Speter /* Decide whether the two repositories are the same or not. */ 2588251881Speter { 2589251881Speter svn_error_t *src_err, *dst_err; 2590251881Speter const char *parent; 2591251881Speter const char *parent_abspath; 2592251881Speter const char *src_uuid, *dst_uuid; 2593251881Speter 2594251881Speter /* Get the repository uuid of SRC_URL */ 2595251881Speter src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool); 2596251881Speter if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 2597251881Speter return svn_error_trace(src_err); 2598251881Speter 2599251881Speter /* Get repository uuid of dst's parent directory, since dst may 2600251881Speter not exist. ### TODO: we should probably walk up the wc here, 2601251881Speter in case the parent dir has an imaginary URL. */ 2602251881Speter if (copy_pairs->nelts == 1) 2603251881Speter parent = svn_dirent_dirname(top_dst_path, scratch_pool); 2604251881Speter else 2605251881Speter parent = top_dst_path; 2606251881Speter 2607251881Speter SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool)); 2608251881Speter dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, 2609251881Speter parent_abspath, ctx, 2610251881Speter iterpool, iterpool); 2611251881Speter if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 2612251881Speter return dst_err; 2613251881Speter 2614251881Speter /* If either of the UUIDs are nonexistent, then at least one of 2615251881Speter the repositories must be very old. Rather than punish the 2616251881Speter user, just assume the repositories are different, so no 2617251881Speter copy-history is attempted. */ 2618251881Speter if (src_err || dst_err || (! src_uuid) || (! dst_uuid)) 2619251881Speter same_repositories = FALSE; 2620251881Speter else 2621251881Speter same_repositories = (strcmp(src_uuid, dst_uuid) == 0); 2622251881Speter } 2623251881Speter 2624251881Speter /* Perform the move for each of the copy_pairs. */ 2625251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2626251881Speter { 2627251881Speter /* Check for cancellation */ 2628251881Speter if (ctx->cancel_func) 2629251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2630251881Speter 2631251881Speter svn_pool_clear(iterpool); 2632251881Speter 2633251881Speter SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, 2634251881Speter APR_ARRAY_IDX(copy_pairs, i, 2635251881Speter svn_client__copy_pair_t *), 2636251881Speter same_repositories, 2637251881Speter ignore_externals, 2638299742Sdim pin_externals, externals_to_pin, 2639251881Speter ra_session, ctx, iterpool)); 2640251881Speter } 2641251881Speter svn_pool_destroy(iterpool); 2642251881Speter 2643251881Speter return SVN_NO_ERROR; 2644251881Speter} 2645251881Speter 2646251881Speterstatic svn_error_t * 2647251881Speterrepos_to_wc_copy(svn_boolean_t *timestamp_sleep, 2648251881Speter const apr_array_header_t *copy_pairs, 2649251881Speter svn_boolean_t make_parents, 2650251881Speter svn_boolean_t ignore_externals, 2651299742Sdim svn_boolean_t pin_externals, 2652299742Sdim const apr_hash_t *externals_to_pin, 2653251881Speter svn_client_ctx_t *ctx, 2654251881Speter apr_pool_t *pool) 2655251881Speter{ 2656251881Speter svn_ra_session_t *ra_session; 2657251881Speter const char *top_src_url, *top_dst_path; 2658251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2659251881Speter const char *lock_abspath; 2660251881Speter int i; 2661251881Speter 2662251881Speter /* Get the real path for the source, based upon its peg revision. */ 2663251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2664251881Speter { 2665251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2666251881Speter svn_client__copy_pair_t *); 2667251881Speter const char *src; 2668251881Speter 2669251881Speter svn_pool_clear(iterpool); 2670251881Speter 2671251881Speter SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL, 2672251881Speter NULL, 2673251881Speter pair->src_abspath_or_url, 2674251881Speter &pair->src_peg_revision, 2675251881Speter &pair->src_op_revision, NULL, 2676251881Speter ctx, iterpool)); 2677251881Speter 2678251881Speter pair->src_original = pair->src_abspath_or_url; 2679251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, src); 2680251881Speter } 2681251881Speter 2682251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, 2683251881Speter NULL, pool)); 2684251881Speter lock_abspath = top_dst_path; 2685251881Speter if (copy_pairs->nelts == 1) 2686251881Speter { 2687251881Speter top_src_url = svn_uri_dirname(top_src_url, pool); 2688251881Speter lock_abspath = svn_dirent_dirname(top_dst_path, pool); 2689251881Speter } 2690251881Speter 2691251881Speter /* Open a repository session to the longest common src ancestor. We do not 2692251881Speter (yet) have a working copy, so we don't have a corresponding path and 2693251881Speter tempfiles cannot go into the admin area. */ 2694251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath, 2695251881Speter ctx, pool, pool)); 2696251881Speter 2697251881Speter /* Get the correct src path for the peg revision used, and verify that we 2698251881Speter aren't overwriting an existing path. */ 2699251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2700251881Speter { 2701251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2702251881Speter svn_client__copy_pair_t *); 2703251881Speter svn_node_kind_t dst_parent_kind, dst_kind; 2704251881Speter const char *dst_parent; 2705251881Speter const char *src_rel; 2706251881Speter 2707251881Speter svn_pool_clear(iterpool); 2708251881Speter 2709251881Speter /* Next, make sure that the path exists in the repository. */ 2710251881Speter SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 2711251881Speter pair->src_abspath_or_url, 2712251881Speter iterpool)); 2713251881Speter SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 2714251881Speter &pair->src_kind, pool)); 2715251881Speter if (pair->src_kind == svn_node_none) 2716251881Speter { 2717251881Speter if (SVN_IS_VALID_REVNUM(pair->src_revnum)) 2718251881Speter return svn_error_createf 2719251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 2720251881Speter _("Path '%s' not found in revision %ld"), 2721251881Speter pair->src_abspath_or_url, pair->src_revnum); 2722251881Speter else 2723251881Speter return svn_error_createf 2724251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 2725251881Speter _("Path '%s' not found in head revision"), 2726251881Speter pair->src_abspath_or_url); 2727251881Speter } 2728251881Speter 2729251881Speter /* Figure out about dst. */ 2730251881Speter SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 2731251881Speter iterpool)); 2732251881Speter if (dst_kind != svn_node_none) 2733251881Speter { 2734251881Speter return svn_error_createf( 2735251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 2736251881Speter _("Path '%s' already exists"), 2737251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2738251881Speter } 2739251881Speter 2740251881Speter /* Make sure the destination parent is a directory and produce a clear 2741251881Speter error message if it is not. */ 2742251881Speter dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); 2743251881Speter SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); 2744251881Speter if (make_parents && dst_parent_kind == svn_node_none) 2745251881Speter { 2746251881Speter SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, 2747251881Speter iterpool)); 2748251881Speter } 2749251881Speter else if (dst_parent_kind != svn_node_dir) 2750251881Speter { 2751251881Speter return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 2752251881Speter _("Path '%s' is not a directory"), 2753251881Speter svn_dirent_local_style(dst_parent, pool)); 2754251881Speter } 2755251881Speter } 2756251881Speter svn_pool_destroy(iterpool); 2757251881Speter 2758251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 2759251881Speter repos_to_wc_copy_locked(timestamp_sleep, 2760251881Speter copy_pairs, top_dst_path, ignore_externals, 2761299742Sdim pin_externals, externals_to_pin, 2762251881Speter ra_session, ctx, pool), 2763251881Speter ctx->wc_ctx, lock_abspath, FALSE, pool); 2764251881Speter return SVN_NO_ERROR; 2765251881Speter} 2766251881Speter 2767251881Speter#define NEED_REPOS_REVNUM(revision) \ 2768251881Speter ((revision.kind != svn_opt_revision_unspecified) \ 2769251881Speter && (revision.kind != svn_opt_revision_working)) 2770251881Speter 2771251881Speter/* ... 2772251881Speter * 2773251881Speter * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not 2774251881Speter * change *TIMESTAMP_SLEEP. This output will be valid even if the 2775251881Speter * function returns an error. 2776251881Speter * 2777251881Speter * Perform all allocations in POOL. 2778251881Speter */ 2779251881Speterstatic svn_error_t * 2780251881Spetertry_copy(svn_boolean_t *timestamp_sleep, 2781251881Speter const apr_array_header_t *sources, 2782251881Speter const char *dst_path_in, 2783251881Speter svn_boolean_t is_move, 2784251881Speter svn_boolean_t allow_mixed_revisions, 2785251881Speter svn_boolean_t metadata_only, 2786251881Speter svn_boolean_t make_parents, 2787251881Speter svn_boolean_t ignore_externals, 2788299742Sdim svn_boolean_t pin_externals, 2789299742Sdim const apr_hash_t *externals_to_pin, 2790251881Speter const apr_hash_t *revprop_table, 2791251881Speter svn_commit_callback2_t commit_callback, 2792251881Speter void *commit_baton, 2793251881Speter svn_client_ctx_t *ctx, 2794251881Speter apr_pool_t *pool) 2795251881Speter{ 2796251881Speter apr_array_header_t *copy_pairs = 2797251881Speter apr_array_make(pool, sources->nelts, 2798251881Speter sizeof(svn_client__copy_pair_t *)); 2799251881Speter svn_boolean_t srcs_are_urls, dst_is_url; 2800251881Speter int i; 2801251881Speter 2802299742Sdim /* Assert instead of crashing if the sources list is empty. */ 2803299742Sdim SVN_ERR_ASSERT(sources->nelts > 0); 2804299742Sdim 2805251881Speter /* Are either of our paths URLs? Just check the first src_path. If 2806251881Speter there are more than one, we'll check for homogeneity among them 2807251881Speter down below. */ 2808251881Speter srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0, 2809251881Speter svn_client_copy_source_t *)->path); 2810251881Speter dst_is_url = svn_path_is_url(dst_path_in); 2811251881Speter if (!dst_is_url) 2812251881Speter SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool)); 2813251881Speter 2814251881Speter /* If we have multiple source paths, it implies the dst_path is a 2815251881Speter directory we are moving or copying into. Populate the COPY_PAIRS 2816251881Speter array to contain a destination path for each of the source paths. */ 2817251881Speter if (sources->nelts > 1) 2818251881Speter { 2819251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2820251881Speter 2821251881Speter for (i = 0; i < sources->nelts; i++) 2822251881Speter { 2823251881Speter svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i, 2824251881Speter svn_client_copy_source_t *); 2825299742Sdim svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair)); 2826251881Speter const char *src_basename; 2827251881Speter svn_boolean_t src_is_url = svn_path_is_url(source->path); 2828251881Speter 2829251881Speter svn_pool_clear(iterpool); 2830251881Speter 2831251881Speter if (src_is_url) 2832251881Speter { 2833251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2834251881Speter src_basename = svn_uri_basename(pair->src_abspath_or_url, 2835251881Speter iterpool); 2836251881Speter } 2837251881Speter else 2838251881Speter { 2839251881Speter SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2840251881Speter source->path, pool)); 2841251881Speter src_basename = svn_dirent_basename(pair->src_abspath_or_url, 2842251881Speter iterpool); 2843251881Speter } 2844251881Speter 2845251881Speter pair->src_op_revision = *source->revision; 2846251881Speter pair->src_peg_revision = *source->peg_revision; 2847299742Sdim pair->src_kind = svn_node_unknown; 2848251881Speter 2849251881Speter SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2850251881Speter &pair->src_op_revision, 2851251881Speter src_is_url, 2852251881Speter TRUE, 2853251881Speter iterpool)); 2854251881Speter 2855251881Speter /* Check to see if all the sources are urls or all working copy 2856251881Speter * paths. */ 2857251881Speter if (src_is_url != srcs_are_urls) 2858251881Speter return svn_error_create 2859251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2860251881Speter _("Cannot mix repository and working copy sources")); 2861251881Speter 2862251881Speter if (dst_is_url) 2863251881Speter pair->dst_abspath_or_url = 2864251881Speter svn_path_url_add_component2(dst_path_in, src_basename, pool); 2865251881Speter else 2866251881Speter pair->dst_abspath_or_url = svn_dirent_join(dst_path_in, 2867251881Speter src_basename, pool); 2868251881Speter APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2869251881Speter } 2870251881Speter 2871251881Speter svn_pool_destroy(iterpool); 2872251881Speter } 2873251881Speter else 2874251881Speter { 2875251881Speter /* Only one source path. */ 2876299742Sdim svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair)); 2877251881Speter svn_client_copy_source_t *source = 2878251881Speter APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *); 2879251881Speter svn_boolean_t src_is_url = svn_path_is_url(source->path); 2880251881Speter 2881251881Speter if (src_is_url) 2882251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2883251881Speter else 2884251881Speter SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2885251881Speter source->path, pool)); 2886251881Speter pair->src_op_revision = *source->revision; 2887251881Speter pair->src_peg_revision = *source->peg_revision; 2888299742Sdim pair->src_kind = svn_node_unknown; 2889251881Speter 2890251881Speter SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2891251881Speter &pair->src_op_revision, 2892251881Speter src_is_url, TRUE, pool)); 2893251881Speter 2894251881Speter pair->dst_abspath_or_url = dst_path_in; 2895251881Speter APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2896251881Speter } 2897251881Speter 2898251881Speter if (!srcs_are_urls && !dst_is_url) 2899251881Speter { 2900251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2901251881Speter 2902251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2903251881Speter { 2904251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2905251881Speter svn_client__copy_pair_t *); 2906251881Speter 2907251881Speter svn_pool_clear(iterpool); 2908251881Speter 2909251881Speter if (svn_dirent_is_child(pair->src_abspath_or_url, 2910251881Speter pair->dst_abspath_or_url, iterpool)) 2911251881Speter return svn_error_createf 2912251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2913251881Speter _("Cannot copy path '%s' into its own child '%s'"), 2914251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool), 2915251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2916251881Speter } 2917251881Speter 2918251881Speter svn_pool_destroy(iterpool); 2919251881Speter } 2920251881Speter 2921251881Speter /* A file external should not be moved since the file external is 2922251881Speter implemented as a switched file and it would delete the file the 2923251881Speter file external is switched to, which is not the behavior the user 2924251881Speter would probably want. */ 2925251881Speter if (is_move && !srcs_are_urls) 2926251881Speter { 2927251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2928251881Speter 2929251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2930251881Speter { 2931251881Speter svn_client__copy_pair_t *pair = 2932251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2933251881Speter svn_node_kind_t external_kind; 2934251881Speter const char *defining_abspath; 2935251881Speter 2936251881Speter svn_pool_clear(iterpool); 2937251881Speter 2938251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2939251881Speter SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, 2940251881Speter NULL, NULL, NULL, ctx->wc_ctx, 2941251881Speter pair->src_abspath_or_url, 2942251881Speter pair->src_abspath_or_url, TRUE, 2943251881Speter iterpool, iterpool)); 2944251881Speter 2945251881Speter if (external_kind != svn_node_none) 2946251881Speter return svn_error_createf( 2947251881Speter SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL, 2948251881Speter NULL, 2949251881Speter _("Cannot move the external at '%s'; please " 2950251881Speter "edit the svn:externals property on '%s'."), 2951251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool), 2952251881Speter svn_dirent_local_style(defining_abspath, pool)); 2953251881Speter } 2954251881Speter svn_pool_destroy(iterpool); 2955251881Speter } 2956251881Speter 2957251881Speter if (is_move) 2958251881Speter { 2959251881Speter /* Disallow moves between the working copy and the repository. */ 2960251881Speter if (srcs_are_urls != dst_is_url) 2961251881Speter { 2962251881Speter return svn_error_create 2963251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2964251881Speter _("Moves between the working copy and the repository are not " 2965251881Speter "supported")); 2966251881Speter } 2967251881Speter 2968251881Speter /* Disallow moving any path/URL onto or into itself. */ 2969251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2970251881Speter { 2971251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2972251881Speter svn_client__copy_pair_t *); 2973251881Speter 2974251881Speter if (strcmp(pair->src_abspath_or_url, 2975251881Speter pair->dst_abspath_or_url) == 0) 2976251881Speter return svn_error_createf( 2977251881Speter SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2978251881Speter srcs_are_urls ? 2979251881Speter _("Cannot move URL '%s' into itself") : 2980251881Speter _("Cannot move path '%s' into itself"), 2981251881Speter srcs_are_urls ? 2982251881Speter pair->src_abspath_or_url : 2983251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2984251881Speter } 2985251881Speter } 2986251881Speter else 2987251881Speter { 2988251881Speter if (!srcs_are_urls) 2989251881Speter { 2990251881Speter /* If we are doing a wc->* copy, but with an operational revision 2991251881Speter other than the working copy revision, we are really doing a 2992251881Speter repo->* copy, because we're going to need to get the rev from the 2993251881Speter repo. */ 2994251881Speter 2995251881Speter svn_boolean_t need_repos_op_rev = FALSE; 2996251881Speter svn_boolean_t need_repos_peg_rev = FALSE; 2997251881Speter 2998251881Speter /* Check to see if any revision is something other than 2999251881Speter svn_opt_revision_unspecified or svn_opt_revision_working. */ 3000251881Speter for (i = 0; i < copy_pairs->nelts; i++) 3001251881Speter { 3002251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 3003251881Speter svn_client__copy_pair_t *); 3004251881Speter 3005251881Speter if (NEED_REPOS_REVNUM(pair->src_op_revision)) 3006251881Speter need_repos_op_rev = TRUE; 3007251881Speter 3008251881Speter if (NEED_REPOS_REVNUM(pair->src_peg_revision)) 3009251881Speter need_repos_peg_rev = TRUE; 3010251881Speter 3011251881Speter if (need_repos_op_rev || need_repos_peg_rev) 3012251881Speter break; 3013251881Speter } 3014251881Speter 3015251881Speter if (need_repos_op_rev || need_repos_peg_rev) 3016251881Speter { 3017251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 3018251881Speter 3019251881Speter for (i = 0; i < copy_pairs->nelts; i++) 3020251881Speter { 3021251881Speter const char *copyfrom_repos_root_url; 3022251881Speter const char *copyfrom_repos_relpath; 3023251881Speter const char *url; 3024251881Speter svn_revnum_t copyfrom_rev; 3025251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 3026251881Speter svn_client__copy_pair_t *); 3027251881Speter 3028251881Speter svn_pool_clear(iterpool); 3029251881Speter 3030251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 3031251881Speter 3032251881Speter SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev, 3033251881Speter ©from_repos_relpath, 3034251881Speter ©from_repos_root_url, 3035299742Sdim NULL, NULL, NULL, 3036251881Speter ctx->wc_ctx, 3037251881Speter pair->src_abspath_or_url, 3038251881Speter TRUE, iterpool, iterpool)); 3039251881Speter 3040251881Speter if (copyfrom_repos_relpath) 3041251881Speter url = svn_path_url_add_component2(copyfrom_repos_root_url, 3042251881Speter copyfrom_repos_relpath, 3043251881Speter pool); 3044251881Speter else 3045251881Speter return svn_error_createf 3046251881Speter (SVN_ERR_ENTRY_MISSING_URL, NULL, 3047251881Speter _("'%s' does not have a URL associated with it"), 3048251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool)); 3049251881Speter 3050251881Speter pair->src_abspath_or_url = url; 3051251881Speter 3052251881Speter if (!need_repos_peg_rev 3053251881Speter || pair->src_peg_revision.kind == svn_opt_revision_base) 3054251881Speter { 3055251881Speter /* Default the peg revision to that of the WC entry. */ 3056251881Speter pair->src_peg_revision.kind = svn_opt_revision_number; 3057251881Speter pair->src_peg_revision.value.number = copyfrom_rev; 3058251881Speter } 3059251881Speter 3060251881Speter if (pair->src_op_revision.kind == svn_opt_revision_base) 3061251881Speter { 3062251881Speter /* Use the entry's revision as the operational rev. */ 3063251881Speter pair->src_op_revision.kind = svn_opt_revision_number; 3064251881Speter pair->src_op_revision.value.number = copyfrom_rev; 3065251881Speter } 3066251881Speter } 3067251881Speter 3068251881Speter svn_pool_destroy(iterpool); 3069251881Speter srcs_are_urls = TRUE; 3070251881Speter } 3071251881Speter } 3072251881Speter } 3073251881Speter 3074251881Speter /* Now, call the right handler for the operation. */ 3075251881Speter if ((! srcs_are_urls) && (! dst_is_url)) 3076251881Speter { 3077251881Speter SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, 3078253734Speter metadata_only, ctx, pool, pool)); 3079251881Speter 3080251881Speter /* Copy or move all targets. */ 3081251881Speter if (is_move) 3082251881Speter return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep, 3083251881Speter copy_pairs, dst_path_in, 3084251881Speter allow_mixed_revisions, 3085251881Speter metadata_only, 3086251881Speter ctx, pool)); 3087251881Speter else 3088251881Speter { 3089251881Speter /* We ignore these values, so assert the default value */ 3090299742Sdim SVN_ERR_ASSERT(allow_mixed_revisions); 3091251881Speter return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep, 3092299742Sdim copy_pairs, 3093299742Sdim metadata_only, 3094299742Sdim pin_externals, 3095299742Sdim externals_to_pin, 3096299742Sdim ctx, pool)); 3097251881Speter } 3098251881Speter } 3099251881Speter else if ((! srcs_are_urls) && (dst_is_url)) 3100251881Speter { 3101251881Speter return svn_error_trace( 3102251881Speter wc_to_repos_copy(copy_pairs, make_parents, revprop_table, 3103299742Sdim commit_callback, commit_baton, 3104299742Sdim pin_externals, externals_to_pin, ctx, pool)); 3105251881Speter } 3106251881Speter else if ((srcs_are_urls) && (! dst_is_url)) 3107251881Speter { 3108251881Speter return svn_error_trace( 3109251881Speter repos_to_wc_copy(timestamp_sleep, 3110251881Speter copy_pairs, make_parents, ignore_externals, 3111299742Sdim pin_externals, externals_to_pin, ctx, pool)); 3112251881Speter } 3113251881Speter else 3114251881Speter { 3115251881Speter return svn_error_trace( 3116251881Speter repos_to_repos_copy(copy_pairs, make_parents, revprop_table, 3117251881Speter commit_callback, commit_baton, ctx, is_move, 3118299742Sdim pin_externals, externals_to_pin, pool)); 3119251881Speter } 3120251881Speter} 3121251881Speter 3122251881Speter 3123251881Speter 3124251881Speter/* Public Interfaces */ 3125251881Spetersvn_error_t * 3126299742Sdimsvn_client_copy7(const apr_array_header_t *sources, 3127251881Speter const char *dst_path, 3128251881Speter svn_boolean_t copy_as_child, 3129251881Speter svn_boolean_t make_parents, 3130251881Speter svn_boolean_t ignore_externals, 3131299742Sdim svn_boolean_t metadata_only, 3132299742Sdim svn_boolean_t pin_externals, 3133299742Sdim const apr_hash_t *externals_to_pin, 3134251881Speter const apr_hash_t *revprop_table, 3135251881Speter svn_commit_callback2_t commit_callback, 3136251881Speter void *commit_baton, 3137251881Speter svn_client_ctx_t *ctx, 3138251881Speter apr_pool_t *pool) 3139251881Speter{ 3140251881Speter svn_error_t *err; 3141251881Speter svn_boolean_t timestamp_sleep = FALSE; 3142251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3143251881Speter 3144251881Speter if (sources->nelts > 1 && !copy_as_child) 3145251881Speter return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 3146251881Speter NULL, NULL); 3147251881Speter 3148251881Speter err = try_copy(×tamp_sleep, 3149251881Speter sources, dst_path, 3150251881Speter FALSE /* is_move */, 3151251881Speter TRUE /* allow_mixed_revisions */, 3152299742Sdim metadata_only, 3153251881Speter make_parents, 3154251881Speter ignore_externals, 3155299742Sdim pin_externals, 3156299742Sdim externals_to_pin, 3157251881Speter revprop_table, 3158251881Speter commit_callback, commit_baton, 3159251881Speter ctx, 3160251881Speter subpool); 3161251881Speter 3162251881Speter /* If the destination exists, try to copy the sources as children of the 3163251881Speter destination. */ 3164251881Speter if (copy_as_child && err && (sources->nelts == 1) 3165251881Speter && (err->apr_err == SVN_ERR_ENTRY_EXISTS 3166251881Speter || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 3167251881Speter { 3168251881Speter const char *src_path = APR_ARRAY_IDX(sources, 0, 3169251881Speter svn_client_copy_source_t *)->path; 3170251881Speter const char *src_basename; 3171251881Speter svn_boolean_t src_is_url = svn_path_is_url(src_path); 3172251881Speter svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 3173251881Speter 3174251881Speter svn_error_clear(err); 3175251881Speter svn_pool_clear(subpool); 3176251881Speter 3177251881Speter src_basename = src_is_url ? svn_uri_basename(src_path, subpool) 3178251881Speter : svn_dirent_basename(src_path, subpool); 3179251881Speter dst_path 3180251881Speter = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 3181251881Speter subpool) 3182251881Speter : svn_dirent_join(dst_path, src_basename, subpool); 3183251881Speter 3184251881Speter err = try_copy(×tamp_sleep, 3185251881Speter sources, dst_path, 3186251881Speter FALSE /* is_move */, 3187251881Speter TRUE /* allow_mixed_revisions */, 3188299742Sdim metadata_only, 3189251881Speter make_parents, 3190251881Speter ignore_externals, 3191299742Sdim pin_externals, 3192299742Sdim externals_to_pin, 3193251881Speter revprop_table, 3194251881Speter commit_callback, commit_baton, 3195251881Speter ctx, 3196251881Speter subpool); 3197251881Speter } 3198251881Speter 3199251881Speter /* Sleep if required. DST_PATH is not a URL in these cases. */ 3200251881Speter if (timestamp_sleep) 3201251881Speter svn_io_sleep_for_timestamps(dst_path, subpool); 3202251881Speter 3203251881Speter svn_pool_destroy(subpool); 3204251881Speter return svn_error_trace(err); 3205251881Speter} 3206251881Speter 3207251881Speter 3208251881Spetersvn_error_t * 3209251881Spetersvn_client_move7(const apr_array_header_t *src_paths, 3210251881Speter const char *dst_path, 3211251881Speter svn_boolean_t move_as_child, 3212251881Speter svn_boolean_t make_parents, 3213251881Speter svn_boolean_t allow_mixed_revisions, 3214251881Speter svn_boolean_t metadata_only, 3215251881Speter const apr_hash_t *revprop_table, 3216251881Speter svn_commit_callback2_t commit_callback, 3217251881Speter void *commit_baton, 3218251881Speter svn_client_ctx_t *ctx, 3219251881Speter apr_pool_t *pool) 3220251881Speter{ 3221251881Speter const svn_opt_revision_t head_revision 3222251881Speter = { svn_opt_revision_head, { 0 } }; 3223251881Speter svn_error_t *err; 3224251881Speter svn_boolean_t timestamp_sleep = FALSE; 3225251881Speter int i; 3226251881Speter apr_pool_t *subpool = svn_pool_create(pool); 3227251881Speter apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts, 3228251881Speter sizeof(const svn_client_copy_source_t *)); 3229251881Speter 3230251881Speter if (src_paths->nelts > 1 && !move_as_child) 3231251881Speter return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 3232251881Speter NULL, NULL); 3233251881Speter 3234251881Speter for (i = 0; i < src_paths->nelts; i++) 3235251881Speter { 3236251881Speter const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *); 3237251881Speter svn_client_copy_source_t *copy_source = apr_palloc(pool, 3238251881Speter sizeof(*copy_source)); 3239251881Speter 3240251881Speter copy_source->path = src_path; 3241251881Speter copy_source->revision = &head_revision; 3242251881Speter copy_source->peg_revision = &head_revision; 3243251881Speter 3244251881Speter APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source; 3245251881Speter } 3246251881Speter 3247251881Speter err = try_copy(×tamp_sleep, 3248251881Speter sources, dst_path, 3249251881Speter TRUE /* is_move */, 3250251881Speter allow_mixed_revisions, 3251251881Speter metadata_only, 3252251881Speter make_parents, 3253251881Speter FALSE /* ignore_externals */, 3254299742Sdim FALSE /* pin_externals */, 3255299742Sdim NULL /* externals_to_pin */, 3256251881Speter revprop_table, 3257251881Speter commit_callback, commit_baton, 3258251881Speter ctx, 3259251881Speter subpool); 3260251881Speter 3261251881Speter /* If the destination exists, try to move the sources as children of the 3262251881Speter destination. */ 3263251881Speter if (move_as_child && err && (src_paths->nelts == 1) 3264251881Speter && (err->apr_err == SVN_ERR_ENTRY_EXISTS 3265251881Speter || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 3266251881Speter { 3267251881Speter const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *); 3268251881Speter const char *src_basename; 3269251881Speter svn_boolean_t src_is_url = svn_path_is_url(src_path); 3270251881Speter svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 3271251881Speter 3272251881Speter svn_error_clear(err); 3273251881Speter svn_pool_clear(subpool); 3274251881Speter 3275251881Speter src_basename = src_is_url ? svn_uri_basename(src_path, pool) 3276251881Speter : svn_dirent_basename(src_path, pool); 3277251881Speter dst_path 3278251881Speter = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 3279251881Speter subpool) 3280251881Speter : svn_dirent_join(dst_path, src_basename, subpool); 3281251881Speter 3282251881Speter err = try_copy(×tamp_sleep, 3283251881Speter sources, dst_path, 3284251881Speter TRUE /* is_move */, 3285251881Speter allow_mixed_revisions, 3286251881Speter metadata_only, 3287251881Speter make_parents, 3288251881Speter FALSE /* ignore_externals */, 3289299742Sdim FALSE /* pin_externals */, 3290299742Sdim NULL /* externals_to_pin */, 3291251881Speter revprop_table, 3292251881Speter commit_callback, commit_baton, 3293251881Speter ctx, 3294251881Speter subpool); 3295251881Speter } 3296251881Speter 3297251881Speter /* Sleep if required. DST_PATH is not a URL in these cases. */ 3298251881Speter if (timestamp_sleep) 3299251881Speter svn_io_sleep_for_timestamps(dst_path, subpool); 3300251881Speter 3301251881Speter svn_pool_destroy(subpool); 3302251881Speter return svn_error_trace(err); 3303251881Speter} 3304