copy.c revision 299742
1/* 2 * copy.c: copy/move wrappers around wc 'copy' functionality. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <string.h> 31#include "svn_hash.h" 32#include "svn_client.h" 33#include "svn_error.h" 34#include "svn_error_codes.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_opt.h" 38#include "svn_time.h" 39#include "svn_props.h" 40#include "svn_mergeinfo.h" 41#include "svn_pools.h" 42 43#include "client.h" 44#include "mergeinfo.h" 45 46#include "svn_private_config.h" 47#include "private/svn_wc_private.h" 48#include "private/svn_ra_private.h" 49#include "private/svn_mergeinfo_private.h" 50#include "private/svn_client_private.h" 51 52 53/* 54 * OUR BASIC APPROACH TO COPIES 55 * ============================ 56 * 57 * for each source/destination pair 58 * if (not exist src_path) 59 * return ERR_BAD_SRC error 60 * 61 * if (exist dst_path) 62 * return ERR_OBSTRUCTION error 63 * else 64 * copy src_path into parent_of_dst_path as basename (dst_path) 65 * 66 * if (this is a move) 67 * delete src_path 68 */ 69 70 71 72/*** Code. ***/ 73 74/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding 75 MERGEINFO to any mergeinfo pre-existing in the WC. */ 76static svn_error_t * 77extend_wc_mergeinfo(const char *target_abspath, 78 apr_hash_t *mergeinfo, 79 svn_client_ctx_t *ctx, 80 apr_pool_t *pool) 81{ 82 apr_hash_t *wc_mergeinfo; 83 84 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo 85 updating it. */ 86 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 87 target_abspath, pool, pool)); 88 89 /* Combine the provided mergeinfo with any mergeinfo from the WC. */ 90 if (wc_mergeinfo && mergeinfo) 91 SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool)); 92 else if (! wc_mergeinfo) 93 wc_mergeinfo = mergeinfo; 94 95 return svn_error_trace( 96 svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo, 97 FALSE, ctx, pool)); 98} 99 100/* Find the longest common ancestor of paths in COPY_PAIRS. If 101 SRC_ANCESTOR is NULL, ignore source paths in this calculation. If 102 DST_ANCESTOR is NULL, ignore destination paths in this calculation. 103 COMMON_ANCESTOR will be the common ancestor of both the 104 SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not 105 NULL. 106 */ 107static svn_error_t * 108get_copy_pair_ancestors(const apr_array_header_t *copy_pairs, 109 const char **src_ancestor, 110 const char **dst_ancestor, 111 const char **common_ancestor, 112 apr_pool_t *pool) 113{ 114 apr_pool_t *subpool = svn_pool_create(pool); 115 svn_client__copy_pair_t *first; 116 const char *first_dst; 117 const char *first_src; 118 const char *top_dst; 119 svn_boolean_t src_is_url; 120 svn_boolean_t dst_is_url; 121 char *top_src; 122 int i; 123 124 first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 125 126 /* Because all the destinations are in the same directory, we can easily 127 determine their common ancestor. */ 128 first_dst = first->dst_abspath_or_url; 129 dst_is_url = svn_path_is_url(first_dst); 130 131 if (copy_pairs->nelts == 1) 132 top_dst = apr_pstrdup(subpool, first_dst); 133 else 134 top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool) 135 : svn_dirent_dirname(first_dst, subpool); 136 137 /* Sources can came from anywhere, so we have to actually do some 138 work for them. */ 139 first_src = first->src_abspath_or_url; 140 src_is_url = svn_path_is_url(first_src); 141 top_src = apr_pstrdup(subpool, first_src); 142 for (i = 1; i < copy_pairs->nelts; i++) 143 { 144 /* We don't need to clear the subpool here for several reasons: 145 1) If we do, we can't use it to allocate the initial versions of 146 top_src and top_dst (above). 147 2) We don't return any errors in the following loop, so we 148 are guanteed to destroy the subpool at the end of this function. 149 3) The number of iterations is likely to be few, and the loop will 150 be through quickly, so memory leakage will not be significant, 151 in time or space. 152 */ 153 const svn_client__copy_pair_t *pair = 154 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 155 156 top_src = src_is_url 157 ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url, 158 subpool) 159 : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url, 160 subpool); 161 } 162 163 if (src_ancestor) 164 *src_ancestor = apr_pstrdup(pool, top_src); 165 166 if (dst_ancestor) 167 *dst_ancestor = apr_pstrdup(pool, top_dst); 168 169 if (common_ancestor) 170 *common_ancestor = 171 src_is_url 172 ? svn_uri_get_longest_ancestor(top_src, top_dst, pool) 173 : svn_dirent_get_longest_ancestor(top_src, top_dst, pool); 174 175 svn_pool_destroy(subpool); 176 177 return SVN_NO_ERROR; 178} 179 180/* Quote a string if it would be handled as multiple or different tokens 181 during externals parsing */ 182static const char * 183maybe_quote(const char *value, 184 apr_pool_t *result_pool) 185{ 186 apr_status_t status; 187 char **argv; 188 189 status = apr_tokenize_to_argv(value, &argv, result_pool); 190 191 if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0) 192 return apr_pstrdup(result_pool, value); 193 194 { 195 svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool); 196 const char *c; 197 198 svn_stringbuf_appendbyte(sb, '\"'); 199 200 for (c = value; *c; c++) 201 { 202 if (*c == '\\' || *c == '\"' || *c == '\'') 203 svn_stringbuf_appendbyte(sb, '\\'); 204 205 svn_stringbuf_appendbyte(sb, *c); 206 } 207 208 svn_stringbuf_appendbyte(sb, '\"'); 209 210#ifdef SVN_DEBUG 211 status = apr_tokenize_to_argv(sb->data, &argv, result_pool); 212 213 SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1] 214 && !strcmp(argv[0], value)); 215#endif 216 217 return sb->data; 218 } 219} 220 221/* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for 222 * use as a line in an svn:externals property, based on the external item 223 * ITEM and the additional parser information in INFO. Pin the external 224 * to EXTERNAL_PEGREV. Use POOL for all allocations. */ 225static svn_error_t * 226make_external_description(const char **new_external_description, 227 const char *local_abspath_or_url, 228 svn_wc_external_item2_t *item, 229 svn_wc__externals_parser_info_t *info, 230 svn_opt_revision_t external_pegrev, 231 apr_pool_t *pool) 232{ 233 const char *rev_str; 234 const char *peg_rev_str; 235 236 switch (info->format) 237 { 238 case svn_wc__external_description_format_1: 239 if (external_pegrev.kind == svn_opt_revision_unspecified) 240 { 241 /* If info->rev_str is NULL, this yields an empty string. */ 242 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); 243 } 244 else if (info->rev_str && item->revision.kind != svn_opt_revision_head) 245 rev_str = apr_psprintf(pool, "%s ", info->rev_str); 246 else 247 { 248 /* ### can't handle svn_opt_revision_date without info->rev_str */ 249 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); 250 rev_str = apr_psprintf(pool, "-r%ld ", 251 external_pegrev.value.number); 252 } 253 254 *new_external_description = 255 apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool), 256 rev_str, 257 maybe_quote(item->url, pool)); 258 break; 259 260 case svn_wc__external_description_format_2: 261 if (external_pegrev.kind == svn_opt_revision_unspecified) 262 { 263 /* If info->rev_str is NULL, this yields an empty string. */ 264 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); 265 } 266 else if (info->rev_str && item->revision.kind != svn_opt_revision_head) 267 rev_str = apr_psprintf(pool, "%s ", info->rev_str); 268 else 269 rev_str = ""; 270 271 if (external_pegrev.kind == svn_opt_revision_unspecified) 272 peg_rev_str = info->peg_rev_str ? info->peg_rev_str : ""; 273 else if (info->peg_rev_str && 274 item->peg_revision.kind != svn_opt_revision_head) 275 peg_rev_str = info->peg_rev_str; 276 else 277 { 278 /* ### can't handle svn_opt_revision_date without info->rev_str */ 279 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); 280 peg_rev_str = apr_psprintf(pool, "@%ld", 281 external_pegrev.value.number); 282 } 283 284 *new_external_description = 285 apr_psprintf(pool, "%s%s %s\n", rev_str, 286 maybe_quote(apr_psprintf(pool, "%s%s", item->url, 287 peg_rev_str), 288 pool), 289 maybe_quote(item->target_dir, pool)); 290 break; 291 292 default: 293 return svn_error_createf( 294 SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, 295 _("%s property defined at '%s' is using an unsupported " 296 "syntax"), SVN_PROP_EXTERNALS, 297 svn_dirent_local_style(local_abspath_or_url, pool)); 298 } 299 300 return SVN_NO_ERROR; 301} 302 303/* Pin all externals listed in EXTERNALS_PROP_VAL to their 304 * last-changed revision. Set *PINNED_EXTERNALS to a new property 305 * value allocated in RESULT_POOL, or to NULL if none of the externals 306 * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the 307 * path or URL defining the svn:externals property. Use SCRATCH_POOL 308 * for temporary allocations. 309 */ 310static svn_error_t * 311pin_externals_prop(svn_string_t **pinned_externals, 312 svn_string_t *externals_prop_val, 313 const apr_hash_t *externals_to_pin, 314 const char *repos_root_url, 315 const char *local_abspath_or_url, 316 svn_client_ctx_t *ctx, 317 apr_pool_t *result_pool, 318 apr_pool_t *scratch_pool) 319{ 320 svn_stringbuf_t *buf; 321 apr_array_header_t *external_items; 322 apr_array_header_t *parser_infos; 323 apr_array_header_t *items_to_pin; 324 int pinned_items; 325 int i; 326 apr_pool_t *iterpool; 327 328 SVN_ERR(svn_wc__parse_externals_description(&external_items, 329 &parser_infos, 330 local_abspath_or_url, 331 externals_prop_val->data, 332 FALSE /* canonicalize_url */, 333 scratch_pool)); 334 335 if (externals_to_pin) 336 { 337 items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin, 338 local_abspath_or_url); 339 if (!items_to_pin) 340 { 341 /* No pinning at all for this path. */ 342 *pinned_externals = NULL; 343 return SVN_NO_ERROR; 344 } 345 } 346 else 347 items_to_pin = NULL; 348 349 buf = svn_stringbuf_create_empty(scratch_pool); 350 iterpool = svn_pool_create(scratch_pool); 351 pinned_items = 0; 352 for (i = 0; i < external_items->nelts; i++) 353 { 354 svn_wc_external_item2_t *item; 355 svn_wc__externals_parser_info_t *info; 356 svn_opt_revision_t external_pegrev; 357 const char *pinned_desc; 358 359 svn_pool_clear(iterpool); 360 361 item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *); 362 info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *); 363 364 if (items_to_pin) 365 { 366 int j; 367 svn_wc_external_item2_t *item_to_pin = NULL; 368 369 for (j = 0; j < items_to_pin->nelts; j++) 370 { 371 svn_wc_external_item2_t *const current = 372 APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *); 373 374 375 if (current 376 && 0 == strcmp(item->url, current->url) 377 && 0 == strcmp(item->target_dir, current->target_dir)) 378 { 379 item_to_pin = current; 380 break; 381 } 382 } 383 384 /* If this item is not in our list of external items to pin then 385 * simply keep the external at its original value. */ 386 if (!item_to_pin) 387 { 388 const char *desc; 389 390 external_pegrev.kind = svn_opt_revision_unspecified; 391 SVN_ERR(make_external_description(&desc, local_abspath_or_url, 392 item, info, external_pegrev, 393 iterpool)); 394 svn_stringbuf_appendcstr(buf, desc); 395 continue; 396 } 397 } 398 399 if (item->peg_revision.kind == svn_opt_revision_date) 400 { 401 /* Already pinned ... copy the peg date. */ 402 external_pegrev.kind = svn_opt_revision_date; 403 external_pegrev.value.date = item->peg_revision.value.date; 404 } 405 else if (item->peg_revision.kind == svn_opt_revision_number) 406 { 407 /* Already pinned ... copy the peg revision number. */ 408 external_pegrev.kind = svn_opt_revision_number; 409 external_pegrev.value.number = item->peg_revision.value.number; 410 } 411 else 412 { 413 SVN_ERR_ASSERT( 414 item->peg_revision.kind == svn_opt_revision_head || 415 item->peg_revision.kind == svn_opt_revision_unspecified); 416 417 /* We're actually going to change the peg revision. */ 418 ++pinned_items; 419 420 if (svn_path_is_url(local_abspath_or_url)) 421 { 422 const char *resolved_url; 423 svn_ra_session_t *external_ra_session; 424 svn_revnum_t latest_revnum; 425 426 SVN_ERR(svn_wc__resolve_relative_external_url( 427 &resolved_url, item, repos_root_url, 428 local_abspath_or_url, iterpool, iterpool)); 429 SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session, 430 NULL, resolved_url, 431 NULL, NULL, FALSE, 432 FALSE, ctx, 433 iterpool, 434 iterpool)); 435 SVN_ERR(svn_ra_get_latest_revnum(external_ra_session, 436 &latest_revnum, 437 iterpool)); 438 439 external_pegrev.kind = svn_opt_revision_number; 440 external_pegrev.value.number = latest_revnum; 441 } 442 else 443 { 444 const char *external_abspath; 445 svn_node_kind_t external_kind; 446 svn_revnum_t external_checked_out_rev; 447 448 external_abspath = svn_dirent_join(local_abspath_or_url, 449 item->target_dir, 450 iterpool); 451 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, 452 NULL, NULL, ctx->wc_ctx, 453 local_abspath_or_url, 454 external_abspath, TRUE, 455 iterpool, 456 iterpool)); 457 if (external_kind == svn_node_none) 458 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 459 NULL, 460 _("Cannot pin external '%s' defined " 461 "in %s at '%s' because it is not " 462 "checked out in the working copy " 463 "at '%s'"), 464 item->url, SVN_PROP_EXTERNALS, 465 svn_dirent_local_style( 466 local_abspath_or_url, iterpool), 467 svn_dirent_local_style( 468 external_abspath, iterpool)); 469 else if (external_kind == svn_node_dir) 470 { 471 svn_boolean_t is_switched; 472 svn_boolean_t is_modified; 473 svn_revnum_t min_rev; 474 svn_revnum_t max_rev; 475 476 /* Perform some sanity checks on the checked-out external. */ 477 478 SVN_ERR(svn_wc__has_switched_subtrees(&is_switched, 479 ctx->wc_ctx, 480 external_abspath, NULL, 481 iterpool)); 482 if (is_switched) 483 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 484 NULL, 485 _("Cannot pin external '%s' defined " 486 "in %s at '%s' because '%s' has " 487 "switched subtrees (switches " 488 "cannot be represented in %s)"), 489 item->url, SVN_PROP_EXTERNALS, 490 svn_dirent_local_style( 491 local_abspath_or_url, iterpool), 492 svn_dirent_local_style( 493 external_abspath, iterpool), 494 SVN_PROP_EXTERNALS); 495 496 SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, 497 external_abspath, TRUE, 498 ctx->cancel_func, 499 ctx->cancel_baton, 500 iterpool)); 501 if (is_modified) 502 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 503 NULL, 504 _("Cannot pin external '%s' defined " 505 "in %s at '%s' because '%s' has " 506 "local modifications (local " 507 "modifications cannot be " 508 "represented in %s)"), 509 item->url, SVN_PROP_EXTERNALS, 510 svn_dirent_local_style( 511 local_abspath_or_url, iterpool), 512 svn_dirent_local_style( 513 external_abspath, iterpool), 514 SVN_PROP_EXTERNALS); 515 516 SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx, 517 external_abspath, FALSE, 518 iterpool)); 519 if (min_rev != max_rev) 520 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, 521 NULL, 522 _("Cannot pin external '%s' defined " 523 "in %s at '%s' because '%s' is a " 524 "mixed-revision working copy " 525 "(mixed-revisions cannot be " 526 "represented in %s)"), 527 item->url, SVN_PROP_EXTERNALS, 528 svn_dirent_local_style( 529 local_abspath_or_url, iterpool), 530 svn_dirent_local_style( 531 external_abspath, iterpool), 532 SVN_PROP_EXTERNALS); 533 external_checked_out_rev = min_rev; 534 } 535 else 536 { 537 SVN_ERR_ASSERT(external_kind == svn_node_file); 538 SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev, 539 NULL, NULL, NULL, 540 ctx->wc_ctx, external_abspath, 541 iterpool, iterpool)); 542 } 543 544 external_pegrev.kind = svn_opt_revision_number; 545 external_pegrev.value.number = external_checked_out_rev; 546 } 547 } 548 549 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date || 550 external_pegrev.kind == svn_opt_revision_number); 551 552 SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url, 553 item, info, external_pegrev, iterpool)); 554 555 svn_stringbuf_appendcstr(buf, pinned_desc); 556 } 557 svn_pool_destroy(iterpool); 558 559 if (pinned_items > 0) 560 *pinned_externals = svn_string_create_from_buf(buf, result_pool); 561 else 562 *pinned_externals = NULL; 563 564 return SVN_NO_ERROR; 565} 566 567/* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths 568 * to svn:externals property values (as const char *), where some or all 569 * external references have been pinned. 570 * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals 571 * mentioned in EXTERNALS_TO_PIN. 572 * The pinning operation takes place as part of the copy operation for 573 * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL 574 * to contact the repository containing the externals definition, if neccesary. 575 * Use CX to fopen additional RA sessions to external repositories, if 576 * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL. 577 * Use SCRATCH_POOL for temporary allocations. */ 578static svn_error_t * 579resolve_pinned_externals(apr_hash_t **pinned_externals, 580 const apr_hash_t *externals_to_pin, 581 svn_client__copy_pair_t *pair, 582 svn_ra_session_t *ra_session, 583 const char *repos_root_url, 584 svn_client_ctx_t *ctx, 585 apr_pool_t *result_pool, 586 apr_pool_t *scratch_pool) 587{ 588 const char *old_url = NULL; 589 apr_hash_t *externals_props; 590 apr_hash_index_t *hi; 591 apr_pool_t *iterpool; 592 593 *pinned_externals = apr_hash_make(result_pool); 594 595 if (svn_path_is_url(pair->src_abspath_or_url)) 596 { 597 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 598 pair->src_abspath_or_url, 599 scratch_pool)); 600 externals_props = apr_hash_make(scratch_pool); 601 SVN_ERR(svn_client__remote_propget(externals_props, NULL, 602 SVN_PROP_EXTERNALS, 603 pair->src_abspath_or_url, "", 604 svn_node_dir, 605 pair->src_revnum, 606 ra_session, 607 svn_depth_infinity, 608 scratch_pool, 609 scratch_pool)); 610 } 611 else 612 { 613 SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL, 614 ctx->wc_ctx, 615 pair->src_abspath_or_url, 616 svn_depth_infinity, 617 scratch_pool, scratch_pool)); 618 619 /* ### gather_definitions returns propvals as const char * */ 620 for (hi = apr_hash_first(scratch_pool, externals_props); 621 hi; 622 hi = apr_hash_next(hi)) 623 { 624 const char *local_abspath_or_url = apr_hash_this_key(hi); 625 const char *propval = apr_hash_this_val(hi); 626 svn_string_t *new_propval = svn_string_create(propval, scratch_pool); 627 628 svn_hash_sets(externals_props, local_abspath_or_url, new_propval); 629 } 630 } 631 632 if (apr_hash_count(externals_props) == 0) 633 { 634 if (old_url) 635 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); 636 return SVN_NO_ERROR; 637 } 638 639 iterpool = svn_pool_create(scratch_pool); 640 for (hi = apr_hash_first(scratch_pool, externals_props); 641 hi; 642 hi = apr_hash_next(hi)) 643 { 644 const char *local_abspath_or_url = apr_hash_this_key(hi); 645 svn_string_t *externals_propval = apr_hash_this_val(hi); 646 const char *relpath; 647 svn_string_t *new_propval; 648 649 svn_pool_clear(iterpool); 650 651 SVN_ERR(pin_externals_prop(&new_propval, externals_propval, 652 externals_to_pin, 653 repos_root_url, local_abspath_or_url, ctx, 654 result_pool, iterpool)); 655 if (new_propval) 656 { 657 if (svn_path_is_url(pair->src_abspath_or_url)) 658 relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url, 659 local_abspath_or_url, 660 result_pool); 661 else 662 relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url, 663 local_abspath_or_url); 664 SVN_ERR_ASSERT(relpath); 665 666 svn_hash_sets(*pinned_externals, relpath, new_propval); 667 } 668 } 669 svn_pool_destroy(iterpool); 670 671 if (old_url) 672 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); 673 674 return SVN_NO_ERROR; 675} 676 677 678 679/* The guts of do_wc_to_wc_copies */ 680static svn_error_t * 681do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep, 682 const apr_array_header_t *copy_pairs, 683 const char *dst_parent, 684 svn_boolean_t metadata_only, 685 svn_boolean_t pin_externals, 686 const apr_hash_t *externals_to_pin, 687 svn_client_ctx_t *ctx, 688 apr_pool_t *scratch_pool) 689{ 690 int i; 691 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 692 svn_error_t *err = SVN_NO_ERROR; 693 694 for (i = 0; i < copy_pairs->nelts; i++) 695 { 696 const char *dst_abspath; 697 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 698 svn_client__copy_pair_t *); 699 apr_hash_t *pinned_externals = NULL; 700 701 svn_pool_clear(iterpool); 702 703 /* Check for cancellation */ 704 if (ctx->cancel_func) 705 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 706 707 if (pin_externals) 708 { 709 const char *repos_root_url; 710 711 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url, 712 NULL, NULL, NULL, ctx->wc_ctx, 713 pair->src_abspath_or_url, FALSE, 714 scratch_pool, iterpool)); 715 SVN_ERR(resolve_pinned_externals(&pinned_externals, 716 externals_to_pin, pair, NULL, 717 repos_root_url, ctx, 718 iterpool, iterpool)); 719 } 720 721 /* Perform the copy */ 722 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name, 723 iterpool); 724 *timestamp_sleep = TRUE; 725 err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath, 726 metadata_only, 727 ctx->cancel_func, ctx->cancel_baton, 728 ctx->notify_func2, ctx->notify_baton2, iterpool); 729 if (err) 730 break; 731 732 if (pinned_externals) 733 { 734 apr_hash_index_t *hi; 735 736 for (hi = apr_hash_first(iterpool, pinned_externals); 737 hi; 738 hi = apr_hash_next(hi)) 739 { 740 const char *dst_relpath = apr_hash_this_key(hi); 741 svn_string_t *externals_propval = apr_hash_this_val(hi); 742 const char *local_abspath; 743 744 local_abspath = svn_dirent_join(pair->dst_abspath_or_url, 745 dst_relpath, iterpool); 746 /* ### use a work queue? */ 747 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 748 SVN_PROP_EXTERNALS, externals_propval, 749 svn_depth_empty, TRUE /* skip_checks */, 750 NULL /* changelist_filter */, 751 ctx->cancel_func, ctx->cancel_baton, 752 NULL, NULL, /* no extra notification */ 753 iterpool)); 754 } 755 } 756 } 757 svn_pool_destroy(iterpool); 758 759 SVN_ERR(err); 760 return SVN_NO_ERROR; 761} 762 763/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary 764 allocations. */ 765static svn_error_t * 766do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep, 767 const apr_array_header_t *copy_pairs, 768 svn_boolean_t metadata_only, 769 svn_boolean_t pin_externals, 770 const apr_hash_t *externals_to_pin, 771 svn_client_ctx_t *ctx, 772 apr_pool_t *pool) 773{ 774 const char *dst_parent, *dst_parent_abspath; 775 776 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool)); 777 if (copy_pairs->nelts == 1) 778 dst_parent = svn_dirent_dirname(dst_parent, pool); 779 780 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool)); 781 782 SVN_WC__CALL_WITH_WRITE_LOCK( 783 do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent, 784 metadata_only, pin_externals, 785 externals_to_pin, ctx, pool), 786 ctx->wc_ctx, dst_parent_abspath, FALSE, pool); 787 788 return SVN_NO_ERROR; 789} 790 791/* The locked bit of do_wc_to_wc_moves. */ 792static svn_error_t * 793do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair, 794 const char *dst_parent_abspath, 795 svn_boolean_t lock_src, 796 svn_boolean_t lock_dst, 797 svn_boolean_t allow_mixed_revisions, 798 svn_boolean_t metadata_only, 799 svn_client_ctx_t *ctx, 800 apr_pool_t *scratch_pool) 801{ 802 const char *dst_abspath; 803 804 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name, 805 scratch_pool); 806 807 SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url, 808 dst_abspath, metadata_only, 809 allow_mixed_revisions, 810 ctx->cancel_func, ctx->cancel_baton, 811 ctx->notify_func2, ctx->notify_baton2, 812 scratch_pool)); 813 814 return SVN_NO_ERROR; 815} 816 817/* Wrapper to add an optional second lock */ 818static svn_error_t * 819do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair, 820 const char *dst_parent_abspath, 821 svn_boolean_t lock_src, 822 svn_boolean_t lock_dst, 823 svn_boolean_t allow_mixed_revisions, 824 svn_boolean_t metadata_only, 825 svn_client_ctx_t *ctx, 826 apr_pool_t *scratch_pool) 827{ 828 if (lock_dst) 829 SVN_WC__CALL_WITH_WRITE_LOCK( 830 do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 831 lock_dst, allow_mixed_revisions, 832 metadata_only, 833 ctx, scratch_pool), 834 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool); 835 else 836 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 837 lock_dst, allow_mixed_revisions, 838 metadata_only, 839 ctx, scratch_pool)); 840 841 return SVN_NO_ERROR; 842} 843 844/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC 845 afterwards. Use POOL for temporary allocations. */ 846static svn_error_t * 847do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep, 848 const apr_array_header_t *copy_pairs, 849 const char *dst_path, 850 svn_boolean_t allow_mixed_revisions, 851 svn_boolean_t metadata_only, 852 svn_client_ctx_t *ctx, 853 apr_pool_t *pool) 854{ 855 int i; 856 apr_pool_t *iterpool = svn_pool_create(pool); 857 svn_error_t *err = SVN_NO_ERROR; 858 859 for (i = 0; i < copy_pairs->nelts; i++) 860 { 861 const char *src_parent_abspath; 862 svn_boolean_t lock_src, lock_dst; 863 const char *src_wcroot_abspath; 864 const char *dst_wcroot_abspath; 865 866 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 867 svn_client__copy_pair_t *); 868 svn_pool_clear(iterpool); 869 870 /* Check for cancellation */ 871 if (ctx->cancel_func) 872 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 873 874 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url, 875 iterpool); 876 877 SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath, 878 ctx->wc_ctx, src_parent_abspath, 879 iterpool, iterpool)); 880 SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath, 881 ctx->wc_ctx, pair->dst_parent_abspath, 882 iterpool, iterpool)); 883 884 /* We now need to lock the right combination of batons. 885 Four cases: 886 1) src_parent == dst_parent 887 2) src_parent is parent of dst_parent 888 3) dst_parent is parent of src_parent 889 4) src_parent and dst_parent are disjoint 890 We can handle 1) as either 2) or 3) */ 891 if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0 892 || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath, 893 NULL) 894 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath, 895 NULL))) 896 { 897 lock_src = TRUE; 898 lock_dst = FALSE; 899 } 900 else if (svn_dirent_is_child(pair->dst_parent_abspath, 901 src_parent_abspath, NULL) 902 && !svn_dirent_is_child(pair->dst_parent_abspath, 903 src_wcroot_abspath, NULL)) 904 { 905 lock_src = FALSE; 906 lock_dst = TRUE; 907 } 908 else 909 { 910 lock_src = TRUE; 911 lock_dst = TRUE; 912 } 913 914 *timestamp_sleep = TRUE; 915 916 /* Perform the copy and then the delete. */ 917 if (lock_src) 918 SVN_WC__CALL_WITH_WRITE_LOCK( 919 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 920 lock_src, lock_dst, 921 allow_mixed_revisions, 922 metadata_only, 923 ctx, iterpool), 924 ctx->wc_ctx, src_parent_abspath, 925 FALSE, iterpool); 926 else 927 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 928 lock_src, lock_dst, 929 allow_mixed_revisions, 930 metadata_only, 931 ctx, iterpool)); 932 933 } 934 svn_pool_destroy(iterpool); 935 936 return svn_error_trace(err); 937} 938 939/* Verify that the destinations stored in COPY_PAIRS are valid working copy 940 destinations and set pair->dst_parent_abspath and pair->base_name for each 941 item to the resulting location if they do */ 942static svn_error_t * 943verify_wc_dsts(const apr_array_header_t *copy_pairs, 944 svn_boolean_t make_parents, 945 svn_boolean_t is_move, 946 svn_boolean_t metadata_only, 947 svn_client_ctx_t *ctx, 948 apr_pool_t *result_pool, 949 apr_pool_t *scratch_pool) 950{ 951 int i; 952 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 953 954 /* Check that DST does not exist, but its parent does */ 955 for (i = 0; i < copy_pairs->nelts; i++) 956 { 957 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 958 svn_client__copy_pair_t *); 959 svn_node_kind_t dst_kind, dst_parent_kind; 960 961 svn_pool_clear(iterpool); 962 963 /* If DST_PATH does not exist, then its basename will become a new 964 file or dir added to its parent (possibly an implicit '.'). 965 Else, just error out. */ 966 SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx, 967 pair->dst_abspath_or_url, 968 FALSE /* show_deleted */, 969 TRUE /* show_hidden */, 970 iterpool)); 971 if (dst_kind != svn_node_none) 972 { 973 svn_boolean_t is_excluded; 974 svn_boolean_t is_server_excluded; 975 976 SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded, 977 &is_server_excluded, ctx->wc_ctx, 978 pair->dst_abspath_or_url, FALSE, 979 iterpool)); 980 981 if (is_excluded || is_server_excluded) 982 { 983 return svn_error_createf( 984 SVN_ERR_WC_OBSTRUCTED_UPDATE, 985 NULL, _("Path '%s' exists, but is excluded"), 986 svn_dirent_local_style(pair->dst_abspath_or_url, iterpool)); 987 } 988 else 989 return svn_error_createf( 990 SVN_ERR_ENTRY_EXISTS, NULL, 991 _("Path '%s' already exists"), 992 svn_dirent_local_style(pair->dst_abspath_or_url, 993 scratch_pool)); 994 } 995 996 /* Check that there is no unversioned obstruction */ 997 if (metadata_only) 998 dst_kind = svn_node_none; 999 else 1000 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 1001 iterpool)); 1002 1003 if (dst_kind != svn_node_none) 1004 { 1005 if (is_move 1006 && copy_pairs->nelts == 1 1007 && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool), 1008 svn_dirent_dirname(pair->dst_abspath_or_url, 1009 iterpool)) == 0) 1010 { 1011 const char *dst; 1012 char *dst_apr; 1013 apr_status_t apr_err; 1014 /* We have a rename inside a directory, which might collide 1015 just because the case insensivity of the filesystem makes 1016 the source match the destination. */ 1017 1018 SVN_ERR(svn_path_cstring_from_utf8(&dst, 1019 pair->dst_abspath_or_url, 1020 scratch_pool)); 1021 1022 apr_err = apr_filepath_merge(&dst_apr, NULL, dst, 1023 APR_FILEPATH_TRUENAME, iterpool); 1024 1025 if (!apr_err) 1026 { 1027 /* And now bring it back to our canonical format */ 1028 SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool)); 1029 dst = svn_dirent_canonicalize(dst, iterpool); 1030 } 1031 /* else: Don't report this error; just report the normal error */ 1032 1033 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0) 1034 { 1035 /* Ok, we have a single case only rename. Get out of here */ 1036 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 1037 pair->dst_abspath_or_url, result_pool); 1038 1039 svn_pool_destroy(iterpool); 1040 return SVN_NO_ERROR; 1041 } 1042 } 1043 1044 return svn_error_createf( 1045 SVN_ERR_ENTRY_EXISTS, NULL, 1046 _("Path '%s' already exists as unversioned node"), 1047 svn_dirent_local_style(pair->dst_abspath_or_url, 1048 scratch_pool)); 1049 } 1050 1051 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 1052 pair->dst_abspath_or_url, result_pool); 1053 1054 /* Make sure the destination parent is a directory and produce a clear 1055 error message if it is not. */ 1056 SVN_ERR(svn_wc_read_kind2(&dst_parent_kind, 1057 ctx->wc_ctx, pair->dst_parent_abspath, 1058 FALSE, TRUE, 1059 iterpool)); 1060 if (make_parents && dst_parent_kind == svn_node_none) 1061 { 1062 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath, 1063 TRUE, ctx, iterpool)); 1064 } 1065 else if (dst_parent_kind != svn_node_dir) 1066 { 1067 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1068 _("Path '%s' is not a directory"), 1069 svn_dirent_local_style( 1070 pair->dst_parent_abspath, scratch_pool)); 1071 } 1072 1073 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath, 1074 &dst_parent_kind, scratch_pool)); 1075 1076 if (dst_parent_kind != svn_node_dir) 1077 return svn_error_createf(SVN_ERR_WC_MISSING, NULL, 1078 _("Path '%s' is not a directory"), 1079 svn_dirent_local_style( 1080 pair->dst_parent_abspath, scratch_pool)); 1081 } 1082 1083 svn_pool_destroy(iterpool); 1084 1085 return SVN_NO_ERROR; 1086} 1087 1088static svn_error_t * 1089verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs, 1090 svn_boolean_t make_parents, 1091 svn_boolean_t is_move, 1092 svn_boolean_t metadata_only, 1093 svn_client_ctx_t *ctx, 1094 apr_pool_t *result_pool, 1095 apr_pool_t *scratch_pool) 1096{ 1097 int i; 1098 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1099 1100 /* Check that all of our SRCs exist. */ 1101 for (i = 0; i < copy_pairs->nelts; i++) 1102 { 1103 svn_boolean_t deleted_ok; 1104 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1105 svn_client__copy_pair_t *); 1106 svn_pool_clear(iterpool); 1107 1108 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base 1109 || pair->src_op_revision.kind == svn_opt_revision_base); 1110 1111 /* Verify that SRC_PATH exists. */ 1112 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx, 1113 pair->src_abspath_or_url, 1114 deleted_ok, FALSE, iterpool)); 1115 if (pair->src_kind == svn_node_none) 1116 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1117 _("Path '%s' does not exist"), 1118 svn_dirent_local_style( 1119 pair->src_abspath_or_url, 1120 scratch_pool)); 1121 } 1122 1123 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx, 1124 result_pool, iterpool)); 1125 1126 svn_pool_destroy(iterpool); 1127 1128 return SVN_NO_ERROR; 1129} 1130 1131 1132/* Path-specific state used as part of path_driver_cb_baton. */ 1133typedef struct path_driver_info_t 1134{ 1135 const char *src_url; 1136 const char *src_path; 1137 const char *dst_path; 1138 svn_node_kind_t src_kind; 1139 svn_revnum_t src_revnum; 1140 svn_boolean_t resurrection; 1141 svn_boolean_t dir_add; 1142 svn_string_t *mergeinfo; /* the new mergeinfo for the target */ 1143 svn_string_t *externals; /* new externals definitions for the target */ 1144 svn_boolean_t only_pin_externals; 1145} path_driver_info_t; 1146 1147 1148/* The baton used with the path_driver_cb_func() callback for a copy 1149 or move operation. */ 1150struct path_driver_cb_baton 1151{ 1152 /* The editor (and its state) used to perform the operation. */ 1153 const svn_delta_editor_t *editor; 1154 void *edit_baton; 1155 1156 /* A hash of path -> path_driver_info_t *'s. */ 1157 apr_hash_t *action_hash; 1158 1159 /* Whether the operation is a move or copy. */ 1160 svn_boolean_t is_move; 1161}; 1162 1163static svn_error_t * 1164path_driver_cb_func(void **dir_baton, 1165 void *parent_baton, 1166 void *callback_baton, 1167 const char *path, 1168 apr_pool_t *pool) 1169{ 1170 struct path_driver_cb_baton *cb_baton = callback_baton; 1171 svn_boolean_t do_delete = FALSE, do_add = FALSE; 1172 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path); 1173 1174 /* Initialize return value. */ 1175 *dir_baton = NULL; 1176 1177 /* This function should never get an empty PATH. We can neither 1178 create nor delete the empty PATH, so if someone is calling us 1179 with such, the code is just plain wrong. */ 1180 SVN_ERR_ASSERT(! svn_path_is_empty(path)); 1181 1182 /* Check to see if we need to add the path as a parent directory. */ 1183 if (path_info->dir_add) 1184 { 1185 return cb_baton->editor->add_directory(path, parent_baton, NULL, 1186 SVN_INVALID_REVNUM, pool, 1187 dir_baton); 1188 } 1189 1190 /* If this is a resurrection, we know the source and dest paths are 1191 the same, and that our driver will only be calling us once. */ 1192 if (path_info->resurrection) 1193 { 1194 /* If this is a move, we do nothing. Otherwise, we do the copy. */ 1195 if (! cb_baton->is_move) 1196 do_add = TRUE; 1197 } 1198 /* Not a resurrection. */ 1199 else 1200 { 1201 /* If this is a move, we check PATH to see if it is the source 1202 or the destination of the move. */ 1203 if (cb_baton->is_move) 1204 { 1205 if (strcmp(path_info->src_path, path) == 0) 1206 do_delete = TRUE; 1207 else 1208 do_add = TRUE; 1209 } 1210 /* Not a move? This must just be the copy addition. */ 1211 else 1212 { 1213 do_add = !path_info->only_pin_externals; 1214 } 1215 } 1216 1217 if (do_delete) 1218 { 1219 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM, 1220 parent_baton, pool)); 1221 } 1222 if (do_add) 1223 { 1224 SVN_ERR(svn_path_check_valid(path, pool)); 1225 1226 if (path_info->src_kind == svn_node_file) 1227 { 1228 void *file_baton; 1229 SVN_ERR(cb_baton->editor->add_file(path, parent_baton, 1230 path_info->src_url, 1231 path_info->src_revnum, 1232 pool, &file_baton)); 1233 if (path_info->mergeinfo) 1234 SVN_ERR(cb_baton->editor->change_file_prop(file_baton, 1235 SVN_PROP_MERGEINFO, 1236 path_info->mergeinfo, 1237 pool)); 1238 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool)); 1239 } 1240 else 1241 { 1242 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, 1243 path_info->src_url, 1244 path_info->src_revnum, 1245 pool, dir_baton)); 1246 if (path_info->mergeinfo) 1247 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, 1248 SVN_PROP_MERGEINFO, 1249 path_info->mergeinfo, 1250 pool)); 1251 } 1252 } 1253 1254 if (path_info->externals) 1255 { 1256 if (*dir_baton == NULL) 1257 SVN_ERR(cb_baton->editor->open_directory(path, parent_baton, 1258 SVN_INVALID_REVNUM, 1259 pool, dir_baton)); 1260 1261 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS, 1262 path_info->externals, pool)); 1263 } 1264 1265 return SVN_NO_ERROR; 1266} 1267 1268 1269/* Starting with the path DIR relative to the RA_SESSION's session 1270 URL, work up through DIR's parents until an existing node is found. 1271 Push each nonexistent path onto the array NEW_DIRS, allocating in 1272 POOL. Raise an error if the existing node is not a directory. 1273 1274 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 1275 ### implementation susceptible to race conditions. */ 1276static svn_error_t * 1277find_absent_parents1(svn_ra_session_t *ra_session, 1278 const char *dir, 1279 apr_array_header_t *new_dirs, 1280 apr_pool_t *pool) 1281{ 1282 svn_node_kind_t kind; 1283 apr_pool_t *iterpool = svn_pool_create(pool); 1284 1285 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind, 1286 iterpool)); 1287 1288 while (kind == svn_node_none) 1289 { 1290 svn_pool_clear(iterpool); 1291 1292 APR_ARRAY_PUSH(new_dirs, const char *) = dir; 1293 dir = svn_dirent_dirname(dir, pool); 1294 1295 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, 1296 &kind, iterpool)); 1297 } 1298 1299 if (kind != svn_node_dir) 1300 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1301 _("Path '%s' already exists, but is not a " 1302 "directory"), dir); 1303 1304 svn_pool_destroy(iterpool); 1305 return SVN_NO_ERROR; 1306} 1307 1308/* Starting with the URL *TOP_DST_URL which is also the root of 1309 RA_SESSION, work up through its parents until an existing node is 1310 found. Push each nonexistent URL onto the array NEW_DIRS, 1311 allocating in POOL. Raise an error if the existing node is not a 1312 directory. 1313 1314 Set *TOP_DST_URL and the RA session's root to the existing node's URL. 1315 1316 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 1317 ### implementation susceptible to race conditions. */ 1318static svn_error_t * 1319find_absent_parents2(svn_ra_session_t *ra_session, 1320 const char **top_dst_url, 1321 apr_array_header_t *new_dirs, 1322 apr_pool_t *pool) 1323{ 1324 const char *root_url = *top_dst_url; 1325 svn_node_kind_t kind; 1326 1327 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 1328 pool)); 1329 1330 while (kind == svn_node_none) 1331 { 1332 APR_ARRAY_PUSH(new_dirs, const char *) = root_url; 1333 root_url = svn_uri_dirname(root_url, pool); 1334 1335 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool)); 1336 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 1337 pool)); 1338 } 1339 1340 if (kind != svn_node_dir) 1341 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1342 _("Path '%s' already exists, but is not a directory"), 1343 root_url); 1344 1345 *top_dst_url = root_url; 1346 return SVN_NO_ERROR; 1347} 1348 1349/* Queue property changes for pinning svn:externals properties set on 1350 * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS 1351 * is keyed by the relative path of each descendant which should have some 1352 * or all of its externals pinned, with the corresponding pinned svn:externals 1353 * properties as values. Property changes are queued in a new list of path 1354 * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an 1355 * existing item is found for the descendant. Allocate results in RESULT_POOL. 1356 * Use SCRATCH_POOL for temporary allocations. */ 1357static svn_error_t * 1358queue_externals_change_path_infos(apr_array_header_t *new_path_infos, 1359 apr_array_header_t *path_infos, 1360 apr_hash_t *pinned_externals, 1361 path_driver_info_t *parent_info, 1362 apr_pool_t *result_pool, 1363 apr_pool_t *scratch_pool) 1364{ 1365 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1366 apr_hash_index_t *hi; 1367 1368 for (hi = apr_hash_first(scratch_pool, pinned_externals); 1369 hi; 1370 hi = apr_hash_next(hi)) 1371 { 1372 const char *dst_relpath = apr_hash_this_key(hi); 1373 svn_string_t *externals_prop = apr_hash_this_val(hi); 1374 const char *src_url; 1375 path_driver_info_t *info; 1376 int i; 1377 1378 svn_pool_clear(iterpool); 1379 1380 src_url = svn_path_url_add_component2(parent_info->src_url, 1381 dst_relpath, iterpool); 1382 1383 /* Try to find a path info the external change can be applied to. */ 1384 info = NULL; 1385 for (i = 0; i < path_infos->nelts; i++) 1386 { 1387 path_driver_info_t *existing_info; 1388 1389 existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1390 if (strcmp(src_url, existing_info->src_url) == 0) 1391 { 1392 info = existing_info; 1393 break; 1394 } 1395 } 1396 1397 if (info == NULL) 1398 { 1399 /* A copied-along child needs its externals pinned. 1400 Create a new path info for this property change. */ 1401 info = apr_pcalloc(result_pool, sizeof(*info)); 1402 info->src_url = svn_path_url_add_component2( 1403 parent_info->src_url, dst_relpath, 1404 result_pool); 1405 info->src_path = NULL; /* Only needed on copied dirs */ 1406 info->dst_path = svn_relpath_join(parent_info->dst_path, 1407 dst_relpath, 1408 result_pool); 1409 info->src_kind = svn_node_dir; 1410 info->only_pin_externals = TRUE; 1411 APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info; 1412 } 1413 1414 info->externals = externals_prop; 1415 } 1416 1417 svn_pool_destroy(iterpool); 1418 1419 return SVN_NO_ERROR; 1420} 1421 1422static svn_error_t * 1423repos_to_repos_copy(const apr_array_header_t *copy_pairs, 1424 svn_boolean_t make_parents, 1425 const apr_hash_t *revprop_table, 1426 svn_commit_callback2_t commit_callback, 1427 void *commit_baton, 1428 svn_client_ctx_t *ctx, 1429 svn_boolean_t is_move, 1430 svn_boolean_t pin_externals, 1431 const apr_hash_t *externals_to_pin, 1432 apr_pool_t *pool) 1433{ 1434 svn_error_t *err; 1435 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts, 1436 sizeof(const char *)); 1437 apr_hash_t *action_hash = apr_hash_make(pool); 1438 apr_array_header_t *path_infos; 1439 const char *top_url, *top_url_all, *top_url_dst; 1440 const char *message, *repos_root; 1441 svn_ra_session_t *ra_session = NULL; 1442 const svn_delta_editor_t *editor; 1443 void *edit_baton; 1444 struct path_driver_cb_baton cb_baton; 1445 apr_array_header_t *new_dirs = NULL; 1446 apr_hash_t *commit_revprops; 1447 apr_array_header_t *pin_externals_only_infos = NULL; 1448 int i; 1449 svn_client__copy_pair_t *first_pair = 1450 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 1451 1452 /* Open an RA session to the first copy pair's destination. We'll 1453 be verifying that every one of our copy source and destination 1454 URLs is or is beneath this sucker's repository root URL as a form 1455 of a cheap(ish) sanity check. */ 1456 SVN_ERR(svn_client_open_ra_session2(&ra_session, 1457 first_pair->src_abspath_or_url, NULL, 1458 ctx, pool, pool)); 1459 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 1460 1461 /* Verify that sources and destinations are all at or under 1462 REPOS_ROOT. While here, create a path_info struct for each 1463 src/dst pair and initialize portions of it with normalized source 1464 location information. */ 1465 path_infos = apr_array_make(pool, copy_pairs->nelts, 1466 sizeof(path_driver_info_t *)); 1467 for (i = 0; i < copy_pairs->nelts; i++) 1468 { 1469 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1470 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1471 svn_client__copy_pair_t *); 1472 apr_hash_t *mergeinfo; 1473 1474 /* Are the source and destination URLs at or under REPOS_ROOT? */ 1475 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url) 1476 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url))) 1477 return svn_error_create 1478 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 1479 _("Source and destination URLs appear not to point to the " 1480 "same repository.")); 1481 1482 /* Run the history function to get the source's URL and revnum in the 1483 operational revision. */ 1484 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 1485 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url, 1486 &pair->src_revnum, 1487 NULL, NULL, 1488 ra_session, 1489 pair->src_abspath_or_url, 1490 &pair->src_peg_revision, 1491 &pair->src_op_revision, NULL, 1492 ctx, pool)); 1493 1494 /* Go ahead and grab mergeinfo from the source, too. */ 1495 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 1496 SVN_ERR(svn_client__get_repos_mergeinfo( 1497 &mergeinfo, ra_session, 1498 pair->src_abspath_or_url, pair->src_revnum, 1499 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 1500 if (mergeinfo) 1501 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool)); 1502 1503 /* Plop an INFO structure onto our array thereof. */ 1504 info->src_url = pair->src_abspath_or_url; 1505 info->src_revnum = pair->src_revnum; 1506 info->resurrection = FALSE; 1507 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info; 1508 } 1509 1510 /* If this is a move, we have to open our session to the longest 1511 path common to all SRC_URLS and DST_URLS in the repository so we 1512 can do existence checks on all paths, and so we can operate on 1513 all paths in the case of a move. But if this is *not* a move, 1514 then opening our session at the longest path common to sources 1515 *and* destinations might be an optimization when the user is 1516 authorized to access all that stuff, but could cause the 1517 operation to fail altogether otherwise. See issue #3242. */ 1518 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all, 1519 pool)); 1520 top_url = is_move ? top_url_all : top_url_dst; 1521 1522 /* Check each src/dst pair for resurrection, and verify that TOP_URL 1523 is anchored high enough to cover all the editor_t activities 1524 required for this operation. */ 1525 for (i = 0; i < copy_pairs->nelts; i++) 1526 { 1527 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1528 svn_client__copy_pair_t *); 1529 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1530 path_driver_info_t *); 1531 1532 /* Source and destination are the same? It's a resurrection. */ 1533 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0) 1534 info->resurrection = TRUE; 1535 1536 /* We need to add each dst_URL, and (in a move) we'll need to 1537 delete each src_URL. Our selection of TOP_URL so far ensures 1538 that all our destination URLs (and source URLs, for moves) 1539 are at least as deep as TOP_URL, but we need to make sure 1540 that TOP_URL is an *ancestor* of all our to-be-edited paths. 1541 1542 Issue #683 is demonstrates this scenario. If you're 1543 resurrecting a deleted item like this: 'svn cp -rN src_URL 1544 dst_URL', then src_URL == dst_URL == top_url. In this 1545 situation, we want to open an RA session to be at least the 1546 *parent* of all three. */ 1547 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0) 1548 && (strcmp(top_url, repos_root) != 0)) 1549 { 1550 top_url = svn_uri_dirname(top_url, pool); 1551 } 1552 if (is_move 1553 && (strcmp(top_url, pair->src_abspath_or_url) == 0) 1554 && (strcmp(top_url, repos_root) != 0)) 1555 { 1556 top_url = svn_uri_dirname(top_url, pool); 1557 } 1558 } 1559 1560 /* Point the RA session to our current TOP_URL. */ 1561 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 1562 1563 /* If we're allowed to create nonexistent parent directories of our 1564 destinations, then make a list in NEW_DIRS of the parent 1565 directories of the destination that don't yet exist. */ 1566 if (make_parents) 1567 { 1568 new_dirs = apr_array_make(pool, 0, sizeof(const char *)); 1569 1570 /* If this is a move, TOP_URL is at least the common ancestor of 1571 all the paths (sources and destinations) involved. Assuming 1572 the sources exist (which is fair, because if they don't, this 1573 whole operation will fail anyway), TOP_URL must also exist. 1574 So it's the paths between TOP_URL and the destinations which 1575 we have to check for existence. But here, we take advantage 1576 of the knowledge of our caller. We know that if there are 1577 multiple copy/move operations being requested, then the 1578 destinations of the copies/moves will all be siblings of one 1579 another. Therefore, we need only to check for the 1580 nonexistent paths between TOP_URL and *one* of our 1581 destinations to find nonexistent parents of all of them. */ 1582 if (is_move) 1583 { 1584 /* Imagine a situation where the user tries to copy an 1585 existing source directory to nonexistent directory with 1586 --parents options specified: 1587 1588 svn copy --parents URL/src URL/dst 1589 1590 where src exists and dst does not. If the dirname of the 1591 destination path is equal to TOP_URL, 1592 do not try to add dst to the NEW_DIRS list since it 1593 will be added to the commit items array later in this 1594 function. */ 1595 const char *dir = svn_uri_skip_ancestor( 1596 top_url, 1597 svn_uri_dirname(first_pair->dst_abspath_or_url, 1598 pool), 1599 pool); 1600 if (dir && *dir) 1601 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool)); 1602 } 1603 /* If, however, this is *not* a move, TOP_URL only points to the 1604 common ancestor of our destination path(s), or possibly one 1605 level higher. We'll need to do an existence crawl toward the 1606 root of the repository, starting with one of our destinations 1607 (see "... take advantage of the knowledge of our caller ..." 1608 above), and possibly adjusting TOP_URL as we go. */ 1609 else 1610 { 1611 apr_array_header_t *new_urls = 1612 apr_array_make(pool, 0, sizeof(const char *)); 1613 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool)); 1614 1615 /* Convert absolute URLs into relpaths relative to TOP_URL. */ 1616 for (i = 0; i < new_urls->nelts; i++) 1617 { 1618 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *); 1619 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool); 1620 1621 APR_ARRAY_PUSH(new_dirs, const char *) = dir; 1622 } 1623 } 1624 } 1625 1626 /* For each src/dst pair, check to see if that SRC_URL is a child of 1627 the DST_URL (excepting the case where DST_URL is the repo root). 1628 If it is, and the parent of DST_URL is the current TOP_URL, then we 1629 need to reparent the session one directory higher, the parent of 1630 the DST_URL. */ 1631 for (i = 0; i < copy_pairs->nelts; i++) 1632 { 1633 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1634 svn_client__copy_pair_t *); 1635 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1636 path_driver_info_t *); 1637 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url, 1638 pair->src_abspath_or_url, 1639 pool); 1640 1641 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0) 1642 && (relpath != NULL && *relpath != '\0')) 1643 { 1644 info->resurrection = TRUE; 1645 top_url = svn_uri_get_longest_ancestor( 1646 top_url, 1647 svn_uri_dirname(pair->dst_abspath_or_url, pool), 1648 pool); 1649 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 1650 } 1651 } 1652 1653 /* Get the portions of the SRC and DST URLs that are relative to 1654 TOP_URL (URI-decoding them while we're at it), verify that the 1655 source exists and the proposed destination does not, and toss 1656 what we've learned into the INFO array. (For copies -- that is, 1657 non-moves -- the relative source URL NULL because it isn't a 1658 child of the TOP_URL at all. That's okay, we'll deal with 1659 it.) */ 1660 for (i = 0; i < copy_pairs->nelts; i++) 1661 { 1662 svn_client__copy_pair_t *pair = 1663 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1664 path_driver_info_t *info = 1665 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1666 svn_node_kind_t dst_kind; 1667 const char *src_rel, *dst_rel; 1668 1669 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool); 1670 if (src_rel) 1671 { 1672 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1673 &info->src_kind, pool)); 1674 } 1675 else 1676 { 1677 const char *old_url; 1678 1679 src_rel = NULL; 1680 SVN_ERR_ASSERT(! is_move); 1681 1682 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 1683 pair->src_abspath_or_url, 1684 pool)); 1685 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum, 1686 &info->src_kind, pool)); 1687 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool)); 1688 } 1689 if (info->src_kind == svn_node_none) 1690 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1691 _("Path '%s' does not exist in revision %ld"), 1692 pair->src_abspath_or_url, pair->src_revnum); 1693 1694 /* Figure out the basename that will result from this operation, 1695 and ensure that we aren't trying to overwrite existing paths. */ 1696 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool); 1697 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1698 &dst_kind, pool)); 1699 if (dst_kind != svn_node_none) 1700 { 1701 const char *path = svn_uri_skip_ancestor(repos_root, 1702 pair->dst_abspath_or_url, 1703 pool); 1704 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1705 _("Path '/%s' already exists"), path); 1706 } 1707 1708 /* More info for our INFO structure. */ 1709 info->src_path = src_rel; /* May be NULL, if outside RA session scope */ 1710 info->dst_path = dst_rel; 1711 1712 svn_hash_sets(action_hash, info->dst_path, info); 1713 if (is_move && (! info->resurrection)) 1714 svn_hash_sets(action_hash, info->src_path, info); 1715 1716 if (pin_externals) 1717 { 1718 apr_hash_t *pinned_externals; 1719 1720 SVN_ERR(resolve_pinned_externals(&pinned_externals, 1721 externals_to_pin, pair, 1722 ra_session, repos_root, 1723 ctx, pool, pool)); 1724 if (pin_externals_only_infos == NULL) 1725 { 1726 pin_externals_only_infos = 1727 apr_array_make(pool, 0, sizeof(path_driver_info_t *)); 1728 } 1729 SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos, 1730 path_infos, 1731 pinned_externals, 1732 info, pool, pool)); 1733 } 1734 } 1735 1736 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1737 { 1738 /* Produce a list of new paths to add, and provide it to the 1739 mechanism used to acquire a log message. */ 1740 svn_client_commit_item3_t *item; 1741 const char *tmp_file; 1742 apr_array_header_t *commit_items 1743 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item)); 1744 1745 /* Add any intermediate directories to the message */ 1746 if (make_parents) 1747 { 1748 for (i = 0; i < new_dirs->nelts; i++) 1749 { 1750 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1751 1752 item = svn_client_commit_item3_create(pool); 1753 item->url = svn_path_url_add_component2(top_url, relpath, pool); 1754 item->kind = svn_node_dir; 1755 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1756 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1757 } 1758 } 1759 1760 for (i = 0; i < path_infos->nelts; i++) 1761 { 1762 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1763 path_driver_info_t *); 1764 1765 item = svn_client_commit_item3_create(pool); 1766 item->url = svn_path_url_add_component2(top_url, info->dst_path, 1767 pool); 1768 item->kind = info->src_kind; 1769 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD 1770 | SVN_CLIENT_COMMIT_ITEM_IS_COPY; 1771 item->copyfrom_url = info->src_url; 1772 item->copyfrom_rev = info->src_revnum; 1773 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1774 1775 if (is_move && (! info->resurrection)) 1776 { 1777 item = svn_client_commit_item3_create(pool); 1778 item->url = svn_path_url_add_component2(top_url, info->src_path, 1779 pool); 1780 item->kind = info->src_kind; 1781 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1782 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1783 } 1784 } 1785 1786 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1787 ctx, pool)); 1788 if (! message) 1789 return SVN_NO_ERROR; 1790 } 1791 else 1792 message = ""; 1793 1794 /* Setup our PATHS for the path-based editor drive. */ 1795 /* First any intermediate directories. */ 1796 if (make_parents) 1797 { 1798 for (i = 0; i < new_dirs->nelts; i++) 1799 { 1800 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1801 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1802 1803 info->dst_path = relpath; 1804 info->dir_add = TRUE; 1805 1806 APR_ARRAY_PUSH(paths, const char *) = relpath; 1807 svn_hash_sets(action_hash, relpath, info); 1808 } 1809 } 1810 1811 /* Then our copy destinations and move sources (if any). */ 1812 for (i = 0; i < path_infos->nelts; i++) 1813 { 1814 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1815 path_driver_info_t *); 1816 1817 APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1818 if (is_move && (! info->resurrection)) 1819 APR_ARRAY_PUSH(paths, const char *) = info->src_path; 1820 } 1821 1822 /* Add any items which only need their externals pinned. */ 1823 if (pin_externals_only_infos) 1824 { 1825 for (i = 0; i < pin_externals_only_infos->nelts; i++) 1826 { 1827 path_driver_info_t *info; 1828 1829 info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *); 1830 APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1831 svn_hash_sets(action_hash, info->dst_path, info); 1832 } 1833 } 1834 1835 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1836 message, ctx, pool)); 1837 1838 /* Fetch RA commit editor. */ 1839 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1840 svn_client__get_shim_callbacks(ctx->wc_ctx, 1841 NULL, pool))); 1842 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1843 commit_revprops, 1844 commit_callback, 1845 commit_baton, 1846 NULL, TRUE, /* No lock tokens */ 1847 pool)); 1848 1849 /* Setup the callback baton. */ 1850 cb_baton.editor = editor; 1851 cb_baton.edit_baton = edit_baton; 1852 cb_baton.action_hash = action_hash; 1853 cb_baton.is_move = is_move; 1854 1855 /* Call the path-based editor driver. */ 1856 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1857 path_driver_cb_func, &cb_baton, pool); 1858 if (err) 1859 { 1860 /* At least try to abort the edit (and fs txn) before throwing err. */ 1861 return svn_error_compose_create( 1862 err, 1863 editor->abort_edit(edit_baton, pool)); 1864 } 1865 1866 if (ctx->notify_func2) 1867 { 1868 svn_wc_notify_t *notify; 1869 notify = svn_wc_create_notify_url(top_url, 1870 svn_wc_notify_commit_finalizing, 1871 pool); 1872 ctx->notify_func2(ctx->notify_baton2, notify, pool); 1873 } 1874 1875 /* Close the edit. */ 1876 return svn_error_trace(editor->close_edit(edit_baton, pool)); 1877} 1878 1879/* Baton for check_url_kind */ 1880struct check_url_kind_baton 1881{ 1882 svn_ra_session_t *session; 1883 const char *repos_root_url; 1884 svn_boolean_t should_reparent; 1885}; 1886 1887/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */ 1888static svn_error_t * 1889check_url_kind(void *baton, 1890 svn_node_kind_t *kind, 1891 const char *url, 1892 svn_revnum_t revision, 1893 apr_pool_t *scratch_pool) 1894{ 1895 struct check_url_kind_baton *cukb = baton; 1896 1897 /* If we don't have a session or can't use the session, get one */ 1898 if (!svn_uri__is_ancestor(cukb->repos_root_url, url)) 1899 *kind = svn_node_none; 1900 else 1901 { 1902 cukb->should_reparent = TRUE; 1903 1904 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool)); 1905 1906 SVN_ERR(svn_ra_check_path(cukb->session, "", revision, 1907 kind, scratch_pool)); 1908 } 1909 1910 return SVN_NO_ERROR; 1911} 1912 1913/* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL 1914 * in the COMMIT_ITEMS list. 1915 * If the list does not already have a commit item for COMMIT_URL 1916 * add a new commit item for the property change. 1917 * Allocate results in RESULT_POOL. 1918 * Use SCRATCH_POOL for temporary allocations. */ 1919static svn_error_t * 1920queue_prop_change_commit_items(const char *local_abspath, 1921 const char *commit_url, 1922 apr_array_header_t *commit_items, 1923 const char *propname, 1924 svn_string_t *propval, 1925 apr_pool_t *result_pool, 1926 apr_pool_t *scratch_pool) 1927{ 1928 svn_client_commit_item3_t *item = NULL; 1929 svn_prop_t *prop; 1930 int i; 1931 1932 for (i = 0; i < commit_items->nelts; i++) 1933 { 1934 svn_client_commit_item3_t *existing_item; 1935 1936 existing_item = APR_ARRAY_IDX(commit_items, i, 1937 svn_client_commit_item3_t *); 1938 if (strcmp(existing_item->url, commit_url) == 0) 1939 { 1940 item = existing_item; 1941 break; 1942 } 1943 } 1944 1945 if (item == NULL) 1946 { 1947 item = svn_client_commit_item3_create(result_pool); 1948 item->path = local_abspath; 1949 item->url = commit_url; 1950 item->kind = svn_node_dir; 1951 item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1952 1953 item->incoming_prop_changes = apr_array_make(result_pool, 1, 1954 sizeof(svn_prop_t *)); 1955 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1956 } 1957 else 1958 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 1959 1960 if (item->outgoing_prop_changes == NULL) 1961 item->outgoing_prop_changes = apr_array_make(result_pool, 1, 1962 sizeof(svn_prop_t *)); 1963 1964 prop = apr_palloc(result_pool, sizeof(*prop)); 1965 prop->name = propname; 1966 prop->value = propval; 1967 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop; 1968 1969 return SVN_NO_ERROR; 1970} 1971 1972/* ### Copy ... 1973 * COMMIT_INFO_P is ... 1974 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath 1975 * and each 'dst_abspath_or_url' is a URL. 1976 * MAKE_PARENTS is ... 1977 * REVPROP_TABLE is ... 1978 * CTX is ... */ 1979static svn_error_t * 1980wc_to_repos_copy(const apr_array_header_t *copy_pairs, 1981 svn_boolean_t make_parents, 1982 const apr_hash_t *revprop_table, 1983 svn_commit_callback2_t commit_callback, 1984 void *commit_baton, 1985 svn_boolean_t pin_externals, 1986 const apr_hash_t *externals_to_pin, 1987 svn_client_ctx_t *ctx, 1988 apr_pool_t *scratch_pool) 1989{ 1990 const char *message; 1991 const char *top_src_path, *top_dst_url; 1992 struct check_url_kind_baton cukb; 1993 const char *top_src_abspath; 1994 svn_ra_session_t *ra_session; 1995 const svn_delta_editor_t *editor; 1996#ifdef ENABLE_EV2_SHIMS 1997 apr_hash_t *relpath_map = NULL; 1998#endif 1999 void *edit_baton; 2000 svn_client__committables_t *committables; 2001 apr_array_header_t *commit_items; 2002 apr_pool_t *iterpool; 2003 apr_array_header_t *new_dirs = NULL; 2004 apr_hash_t *commit_revprops; 2005 svn_client__copy_pair_t *first_pair; 2006 apr_pool_t *session_pool = svn_pool_create(scratch_pool); 2007 apr_array_header_t *commit_items_for_dav; 2008 int i; 2009 2010 /* Find the common root of all the source paths */ 2011 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, 2012 scratch_pool)); 2013 2014 /* Do we need to lock the working copy? 1.6 didn't take a write 2015 lock, but what happens if the working copy changes during the copy 2016 operation? */ 2017 2018 iterpool = svn_pool_create(scratch_pool); 2019 2020 /* Determine the longest common ancestor for the destinations, and open an RA 2021 session to that location. */ 2022 /* ### But why start by getting the _parent_ of the first one? */ 2023 /* --- That works because multiple destinations always point to the same 2024 * directory. I'm rather wondering why we need to find a common 2025 * destination parent here at all, instead of simply getting 2026 * top_dst_url from get_copy_pair_ancestors() above? 2027 * It looks like the entire block of code hanging off this comment 2028 * is redundant. */ 2029 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 2030 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool); 2031 for (i = 1; i < copy_pairs->nelts; i++) 2032 { 2033 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2034 svn_client__copy_pair_t *); 2035 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url, 2036 pair->dst_abspath_or_url, 2037 scratch_pool); 2038 } 2039 2040 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool)); 2041 2042 commit_items_for_dav = apr_array_make(session_pool, 0, 2043 sizeof(svn_client_commit_item3_t*)); 2044 2045 /* Open a session to help while determining the exact targets */ 2046 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 2047 top_src_abspath, 2048 commit_items_for_dav, 2049 FALSE /* write_dav_props */, 2050 TRUE /* read_dav_props */, 2051 ctx, 2052 session_pool, session_pool)); 2053 2054 /* If requested, determine the nearest existing parent of the destination, 2055 and reparent the ra session there. */ 2056 if (make_parents) 2057 { 2058 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *)); 2059 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs, 2060 scratch_pool)); 2061 } 2062 2063 /* Figure out the basename that will result from each copy and check to make 2064 sure it doesn't exist already. */ 2065 for (i = 0; i < copy_pairs->nelts; i++) 2066 { 2067 svn_node_kind_t dst_kind; 2068 const char *dst_rel; 2069 svn_client__copy_pair_t *pair = 2070 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2071 2072 svn_pool_clear(iterpool); 2073 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url, 2074 iterpool); 2075 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 2076 &dst_kind, iterpool)); 2077 if (dst_kind != svn_node_none) 2078 { 2079 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 2080 _("Path '%s' already exists"), 2081 pair->dst_abspath_or_url); 2082 } 2083 } 2084 2085 cukb.session = ra_session; 2086 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool)); 2087 cukb.should_reparent = FALSE; 2088 2089 /* Crawl the working copy for commit items. */ 2090 /* ### TODO: Pass check_url_func for issue #3314 handling */ 2091 SVN_ERR(svn_client__get_copy_committables(&committables, 2092 copy_pairs, 2093 check_url_kind, &cukb, 2094 ctx, scratch_pool, iterpool)); 2095 2096 /* The committables are keyed by the repository root */ 2097 commit_items = svn_hash_gets(committables->by_repository, 2098 cukb.repos_root_url); 2099 SVN_ERR_ASSERT(commit_items != NULL); 2100 2101 if (cukb.should_reparent) 2102 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 2103 2104 /* If we are creating intermediate directories, tack them onto the list 2105 of committables. */ 2106 if (make_parents) 2107 { 2108 for (i = 0; i < new_dirs->nelts; i++) 2109 { 2110 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 2111 svn_client_commit_item3_t *item; 2112 2113 item = svn_client_commit_item3_create(scratch_pool); 2114 item->url = url; 2115 item->kind = svn_node_dir; 2116 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 2117 item->incoming_prop_changes = apr_array_make(scratch_pool, 1, 2118 sizeof(svn_prop_t *)); 2119 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 2120 } 2121 } 2122 2123 /* ### TODO: This extra loop would be unnecessary if this code lived 2124 ### in svn_client__get_copy_committables(), which is incidentally 2125 ### only used above (so should really be in this source file). */ 2126 for (i = 0; i < copy_pairs->nelts; i++) 2127 { 2128 apr_hash_t *mergeinfo, *wc_mergeinfo; 2129 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2130 svn_client__copy_pair_t *); 2131 svn_client_commit_item3_t *item = 2132 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 2133 svn_client__pathrev_t *src_origin; 2134 2135 svn_pool_clear(iterpool); 2136 2137 SVN_ERR(svn_client__wc_node_get_origin(&src_origin, 2138 pair->src_abspath_or_url, 2139 ctx, iterpool, iterpool)); 2140 2141 /* Set the mergeinfo for the destination to the combined merge 2142 info known to the WC and the repository. */ 2143 /* Repository mergeinfo (or NULL if it's locally added)... */ 2144 if (src_origin) 2145 SVN_ERR(svn_client__get_repos_mergeinfo( 2146 &mergeinfo, ra_session, src_origin->url, src_origin->rev, 2147 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool)); 2148 else 2149 mergeinfo = NULL; 2150 /* ... and WC mergeinfo. */ 2151 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 2152 pair->src_abspath_or_url, 2153 iterpool, iterpool)); 2154 if (wc_mergeinfo && mergeinfo) 2155 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool, 2156 iterpool)); 2157 else if (! mergeinfo) 2158 mergeinfo = wc_mergeinfo; 2159 2160 if (mergeinfo) 2161 { 2162 /* Push a mergeinfo prop representing MERGEINFO onto the 2163 * OUTGOING_PROP_CHANGES array. */ 2164 2165 svn_prop_t *mergeinfo_prop 2166 = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop)); 2167 svn_string_t *prop_value; 2168 2169 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo, 2170 scratch_pool)); 2171 2172 if (!item->outgoing_prop_changes) 2173 { 2174 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1, 2175 sizeof(svn_prop_t *)); 2176 } 2177 2178 mergeinfo_prop->name = SVN_PROP_MERGEINFO; 2179 mergeinfo_prop->value = prop_value; 2180 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) 2181 = mergeinfo_prop; 2182 } 2183 2184 if (pin_externals) 2185 { 2186 apr_hash_t *pinned_externals; 2187 apr_hash_index_t *hi; 2188 2189 SVN_ERR(resolve_pinned_externals(&pinned_externals, 2190 externals_to_pin, pair, 2191 ra_session, cukb.repos_root_url, 2192 ctx, scratch_pool, iterpool)); 2193 for (hi = apr_hash_first(scratch_pool, pinned_externals); 2194 hi; 2195 hi = apr_hash_next(hi)) 2196 { 2197 const char *dst_relpath = apr_hash_this_key(hi); 2198 svn_string_t *externals_propval = apr_hash_this_val(hi); 2199 const char *dst_url; 2200 const char *commit_url; 2201 const char *src_abspath; 2202 2203 if (svn_path_is_url(pair->dst_abspath_or_url)) 2204 dst_url = pair->dst_abspath_or_url; 2205 else 2206 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx, 2207 pair->dst_abspath_or_url, 2208 scratch_pool, iterpool)); 2209 commit_url = svn_path_url_add_component2(dst_url, dst_relpath, 2210 scratch_pool); 2211 src_abspath = svn_dirent_join(pair->src_abspath_or_url, 2212 dst_relpath, iterpool); 2213 SVN_ERR(queue_prop_change_commit_items(src_abspath, 2214 commit_url, commit_items, 2215 SVN_PROP_EXTERNALS, 2216 externals_propval, 2217 scratch_pool, iterpool)); 2218 } 2219 } 2220 } 2221 2222 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 2223 { 2224 const char *tmp_file; 2225 2226 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 2227 ctx, scratch_pool)); 2228 if (! message) 2229 { 2230 svn_pool_destroy(iterpool); 2231 svn_pool_destroy(session_pool); 2232 return SVN_NO_ERROR; 2233 } 2234 } 2235 else 2236 message = ""; 2237 2238 /* Sort and condense our COMMIT_ITEMS. */ 2239 SVN_ERR(svn_client__condense_commit_items(&top_dst_url, 2240 commit_items, scratch_pool)); 2241 2242 /* Add the commit items to the DAV commit item list to provide access 2243 to dav properties (for pre http-v2 DAV) */ 2244 apr_array_cat(commit_items_for_dav, commit_items); 2245 2246#ifdef ENABLE_EV2_SHIMS 2247 if (commit_items) 2248 { 2249 relpath_map = apr_hash_make(scratch_pool); 2250 for (i = 0; i < commit_items->nelts; i++) 2251 { 2252 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, 2253 svn_client_commit_item3_t *); 2254 const char *relpath; 2255 2256 if (!item->path) 2257 continue; 2258 2259 svn_pool_clear(iterpool); 2260 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, 2261 NULL, NULL, 2262 ctx->wc_ctx, item->path, FALSE, 2263 scratch_pool, iterpool)); 2264 if (relpath) 2265 svn_hash_sets(relpath_map, relpath, item->path); 2266 } 2267 } 2268#endif 2269 2270 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 2271 2272 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 2273 message, ctx, session_pool)); 2274 2275 /* Fetch RA commit editor. */ 2276#ifdef ENABLE_EV2_SHIMS 2277 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 2278 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map, 2279 session_pool))); 2280#endif 2281 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 2282 commit_revprops, 2283 commit_callback, 2284 commit_baton, NULL, 2285 TRUE, /* No lock tokens */ 2286 session_pool)); 2287 2288 /* Perform the commit. */ 2289 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, 2290 editor, edit_baton, 2291 NULL /* notify_path_prefix */, 2292 NULL, ctx, session_pool, session_pool), 2293 _("Commit failed (details follow):")); 2294 2295 svn_pool_destroy(iterpool); 2296 svn_pool_destroy(session_pool); 2297 2298 return SVN_NO_ERROR; 2299} 2300 2301/* A baton for notification_adjust_func(). */ 2302struct notification_adjust_baton 2303{ 2304 svn_wc_notify_func2_t inner_func; 2305 void *inner_baton; 2306 const char *checkout_abspath; 2307 const char *final_abspath; 2308}; 2309 2310/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose 2311 * baton is BATON->inner_baton) and adjusts the notification paths that 2312 * start with BATON->checkout_abspath to start instead with 2313 * BATON->final_abspath. */ 2314static void 2315notification_adjust_func(void *baton, 2316 const svn_wc_notify_t *notify, 2317 apr_pool_t *pool) 2318{ 2319 struct notification_adjust_baton *nb = baton; 2320 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); 2321 const char *relpath; 2322 2323 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); 2324 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); 2325 2326 if (nb->inner_func) 2327 nb->inner_func(nb->inner_baton, inner_notify, pool); 2328} 2329 2330/* Peform each individual copy operation for a repos -> wc copy. A 2331 helper for repos_to_wc_copy(). 2332 2333 Resolve PAIR->src_revnum to a real revision number if it isn't already. */ 2334static svn_error_t * 2335repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, 2336 svn_client__copy_pair_t *pair, 2337 svn_boolean_t same_repositories, 2338 svn_boolean_t ignore_externals, 2339 svn_boolean_t pin_externals, 2340 const apr_hash_t *externals_to_pin, 2341 svn_ra_session_t *ra_session, 2342 svn_client_ctx_t *ctx, 2343 apr_pool_t *pool) 2344{ 2345 apr_hash_t *src_mergeinfo; 2346 const char *dst_abspath = pair->dst_abspath_or_url; 2347 2348 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 2349 2350 if (!same_repositories && ctx->notify_func2) 2351 { 2352 svn_wc_notify_t *notify; 2353 notify = svn_wc_create_notify_url( 2354 pair->src_abspath_or_url, 2355 svn_wc_notify_foreign_copy_begin, 2356 pool); 2357 notify->kind = pair->src_kind; 2358 ctx->notify_func2(ctx->notify_baton2, notify, pool); 2359 2360 /* Allow a theoretical cancel to get through. */ 2361 if (ctx->cancel_func) 2362 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2363 } 2364 2365 if (pair->src_kind == svn_node_dir) 2366 { 2367 if (same_repositories) 2368 { 2369 const char *tmpdir_abspath, *tmp_abspath; 2370 2371 /* Find a temporary location in which to check out the copy source. */ 2372 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, 2373 pool, pool)); 2374 2375 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, 2376 svn_io_file_del_on_close, pool, pool)); 2377 2378 /* Make a new checkout of the requested source. While doing so, 2379 * resolve pair->src_revnum to an actual revision number in case it 2380 * was until now 'invalid' meaning 'head'. Ask this function not to 2381 * sleep for timestamps, by passing a sleep_needed output param. 2382 * Send notifications for all nodes except the root node, and adjust 2383 * them to refer to the destination rather than this temporary path. */ 2384 { 2385 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; 2386 void *old_notify_baton2 = ctx->notify_baton2; 2387 struct notification_adjust_baton nb; 2388 svn_error_t *err; 2389 2390 nb.inner_func = ctx->notify_func2; 2391 nb.inner_baton = ctx->notify_baton2; 2392 nb.checkout_abspath = tmp_abspath; 2393 nb.final_abspath = dst_abspath; 2394 ctx->notify_func2 = notification_adjust_func; 2395 ctx->notify_baton2 = &nb; 2396 2397 /* Avoid a chicken-and-egg problem: 2398 * If pinning externals we'll need to adjust externals 2399 * properties before checking out any externals. 2400 * But copy needs to happen before pinning because else there 2401 * are no svn:externals properties to pin. */ 2402 if (pin_externals) 2403 ignore_externals = TRUE; 2404 2405 err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep, 2406 pair->src_original, 2407 tmp_abspath, 2408 &pair->src_peg_revision, 2409 &pair->src_op_revision, 2410 svn_depth_infinity, 2411 ignore_externals, FALSE, 2412 ra_session, ctx, pool); 2413 2414 ctx->notify_func2 = old_notify_func2; 2415 ctx->notify_baton2 = old_notify_baton2; 2416 2417 SVN_ERR(err); 2418 } 2419 2420 *timestamp_sleep = TRUE; 2421 2422 /* Schedule dst_path for addition in parent, with copy history. 2423 Don't send any notification here. 2424 Then remove the temporary checkout's .svn dir in preparation for 2425 moving the rest of it into the final destination. */ 2426 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, 2427 TRUE /* metadata_only */, 2428 ctx->cancel_func, ctx->cancel_baton, 2429 NULL, NULL, pool)); 2430 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, 2431 FALSE, pool, pool)); 2432 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, 2433 tmp_abspath, 2434 FALSE, FALSE, 2435 ctx->cancel_func, 2436 ctx->cancel_baton, 2437 pool)); 2438 2439 /* Move the temporary disk tree into place. */ 2440 SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool)); 2441 } 2442 else 2443 { 2444 *timestamp_sleep = TRUE; 2445 2446 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url, 2447 dst_abspath, 2448 &pair->src_peg_revision, 2449 &pair->src_op_revision, 2450 svn_depth_infinity, 2451 FALSE /* make_parents */, 2452 TRUE /* already_locked */, 2453 ctx, pool)); 2454 2455 return SVN_NO_ERROR; 2456 } 2457 2458 if (pin_externals) 2459 { 2460 apr_hash_t *pinned_externals; 2461 apr_hash_index_t *hi; 2462 apr_pool_t *iterpool; 2463 const char *repos_root_url; 2464 apr_hash_t *new_externals; 2465 apr_hash_t *new_depths; 2466 2467 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); 2468 SVN_ERR(resolve_pinned_externals(&pinned_externals, 2469 externals_to_pin, pair, 2470 ra_session, repos_root_url, 2471 ctx, pool, pool)); 2472 2473 iterpool = svn_pool_create(pool); 2474 for (hi = apr_hash_first(pool, pinned_externals); 2475 hi; 2476 hi = apr_hash_next(hi)) 2477 { 2478 const char *dst_relpath = apr_hash_this_key(hi); 2479 svn_string_t *externals_propval = apr_hash_this_val(hi); 2480 const char *local_abspath; 2481 2482 svn_pool_clear(iterpool); 2483 2484 local_abspath = svn_dirent_join(pair->dst_abspath_or_url, 2485 dst_relpath, iterpool); 2486 /* ### use a work queue? */ 2487 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, 2488 SVN_PROP_EXTERNALS, externals_propval, 2489 svn_depth_empty, TRUE /* skip_checks */, 2490 NULL /* changelist_filter */, 2491 ctx->cancel_func, ctx->cancel_baton, 2492 NULL, NULL, /* no extra notification */ 2493 iterpool)); 2494 } 2495 2496 /* Now update all externals in the newly created copy. */ 2497 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, 2498 &new_depths, 2499 ctx->wc_ctx, 2500 dst_abspath, 2501 svn_depth_infinity, 2502 iterpool, iterpool)); 2503 SVN_ERR(svn_client__handle_externals(new_externals, 2504 new_depths, 2505 repos_root_url, dst_abspath, 2506 svn_depth_infinity, 2507 timestamp_sleep, 2508 ra_session, 2509 ctx, iterpool)); 2510 svn_pool_destroy(iterpool); 2511 } 2512 } /* end directory case */ 2513 2514 else if (pair->src_kind == svn_node_file) 2515 { 2516 apr_hash_t *new_props; 2517 const char *src_rel; 2518 svn_stream_t *new_base_contents = svn_stream_buffered(pool); 2519 2520 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 2521 pair->src_abspath_or_url, 2522 pool)); 2523 /* Fetch the file content. While doing so, resolve pair->src_revnum 2524 * to an actual revision number if it's 'invalid' meaning 'head'. */ 2525 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, 2526 new_base_contents, 2527 &pair->src_revnum, &new_props, pool)); 2528 2529 if (new_props && ! same_repositories) 2530 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); 2531 2532 *timestamp_sleep = TRUE; 2533 2534 SVN_ERR(svn_wc_add_repos_file4( 2535 ctx->wc_ctx, dst_abspath, 2536 new_base_contents, NULL, new_props, NULL, 2537 same_repositories ? pair->src_abspath_or_url : NULL, 2538 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, 2539 ctx->cancel_func, ctx->cancel_baton, 2540 pool)); 2541 } 2542 2543 /* Record the implied mergeinfo (before the notification callback 2544 is invoked for the root node). */ 2545 SVN_ERR(svn_client__get_repos_mergeinfo( 2546 &src_mergeinfo, ra_session, 2547 pair->src_abspath_or_url, pair->src_revnum, 2548 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 2549 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); 2550 2551 /* Do our own notification for the root node, even if we could possibly 2552 have delegated it. See also issue #1552. 2553 2554 ### Maybe this notification should mention the mergeinfo change. */ 2555 if (ctx->notify_func2) 2556 { 2557 svn_wc_notify_t *notify = svn_wc_create_notify( 2558 dst_abspath, svn_wc_notify_add, pool); 2559 notify->kind = pair->src_kind; 2560 ctx->notify_func2(ctx->notify_baton2, notify, pool); 2561 } 2562 2563 return SVN_NO_ERROR; 2564} 2565 2566static svn_error_t * 2567repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, 2568 const apr_array_header_t *copy_pairs, 2569 const char *top_dst_path, 2570 svn_boolean_t ignore_externals, 2571 svn_boolean_t pin_externals, 2572 const apr_hash_t *externals_to_pin, 2573 svn_ra_session_t *ra_session, 2574 svn_client_ctx_t *ctx, 2575 apr_pool_t *scratch_pool) 2576{ 2577 int i; 2578 svn_boolean_t same_repositories; 2579 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2580 2581 /* We've already checked for physical obstruction by a working file. 2582 But there could also be logical obstruction by an entry whose 2583 working file happens to be missing.*/ 2584 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, 2585 ctx, scratch_pool, iterpool)); 2586 2587 /* Decide whether the two repositories are the same or not. */ 2588 { 2589 svn_error_t *src_err, *dst_err; 2590 const char *parent; 2591 const char *parent_abspath; 2592 const char *src_uuid, *dst_uuid; 2593 2594 /* Get the repository uuid of SRC_URL */ 2595 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool); 2596 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 2597 return svn_error_trace(src_err); 2598 2599 /* Get repository uuid of dst's parent directory, since dst may 2600 not exist. ### TODO: we should probably walk up the wc here, 2601 in case the parent dir has an imaginary URL. */ 2602 if (copy_pairs->nelts == 1) 2603 parent = svn_dirent_dirname(top_dst_path, scratch_pool); 2604 else 2605 parent = top_dst_path; 2606 2607 SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool)); 2608 dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, 2609 parent_abspath, ctx, 2610 iterpool, iterpool); 2611 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 2612 return dst_err; 2613 2614 /* If either of the UUIDs are nonexistent, then at least one of 2615 the repositories must be very old. Rather than punish the 2616 user, just assume the repositories are different, so no 2617 copy-history is attempted. */ 2618 if (src_err || dst_err || (! src_uuid) || (! dst_uuid)) 2619 same_repositories = FALSE; 2620 else 2621 same_repositories = (strcmp(src_uuid, dst_uuid) == 0); 2622 } 2623 2624 /* Perform the move for each of the copy_pairs. */ 2625 for (i = 0; i < copy_pairs->nelts; i++) 2626 { 2627 /* Check for cancellation */ 2628 if (ctx->cancel_func) 2629 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 2630 2631 svn_pool_clear(iterpool); 2632 2633 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, 2634 APR_ARRAY_IDX(copy_pairs, i, 2635 svn_client__copy_pair_t *), 2636 same_repositories, 2637 ignore_externals, 2638 pin_externals, externals_to_pin, 2639 ra_session, ctx, iterpool)); 2640 } 2641 svn_pool_destroy(iterpool); 2642 2643 return SVN_NO_ERROR; 2644} 2645 2646static svn_error_t * 2647repos_to_wc_copy(svn_boolean_t *timestamp_sleep, 2648 const apr_array_header_t *copy_pairs, 2649 svn_boolean_t make_parents, 2650 svn_boolean_t ignore_externals, 2651 svn_boolean_t pin_externals, 2652 const apr_hash_t *externals_to_pin, 2653 svn_client_ctx_t *ctx, 2654 apr_pool_t *pool) 2655{ 2656 svn_ra_session_t *ra_session; 2657 const char *top_src_url, *top_dst_path; 2658 apr_pool_t *iterpool = svn_pool_create(pool); 2659 const char *lock_abspath; 2660 int i; 2661 2662 /* Get the real path for the source, based upon its peg revision. */ 2663 for (i = 0; i < copy_pairs->nelts; i++) 2664 { 2665 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2666 svn_client__copy_pair_t *); 2667 const char *src; 2668 2669 svn_pool_clear(iterpool); 2670 2671 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL, 2672 NULL, 2673 pair->src_abspath_or_url, 2674 &pair->src_peg_revision, 2675 &pair->src_op_revision, NULL, 2676 ctx, iterpool)); 2677 2678 pair->src_original = pair->src_abspath_or_url; 2679 pair->src_abspath_or_url = apr_pstrdup(pool, src); 2680 } 2681 2682 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, 2683 NULL, pool)); 2684 lock_abspath = top_dst_path; 2685 if (copy_pairs->nelts == 1) 2686 { 2687 top_src_url = svn_uri_dirname(top_src_url, pool); 2688 lock_abspath = svn_dirent_dirname(top_dst_path, pool); 2689 } 2690 2691 /* Open a repository session to the longest common src ancestor. We do not 2692 (yet) have a working copy, so we don't have a corresponding path and 2693 tempfiles cannot go into the admin area. */ 2694 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath, 2695 ctx, pool, pool)); 2696 2697 /* Get the correct src path for the peg revision used, and verify that we 2698 aren't overwriting an existing path. */ 2699 for (i = 0; i < copy_pairs->nelts; i++) 2700 { 2701 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2702 svn_client__copy_pair_t *); 2703 svn_node_kind_t dst_parent_kind, dst_kind; 2704 const char *dst_parent; 2705 const char *src_rel; 2706 2707 svn_pool_clear(iterpool); 2708 2709 /* Next, make sure that the path exists in the repository. */ 2710 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 2711 pair->src_abspath_or_url, 2712 iterpool)); 2713 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 2714 &pair->src_kind, pool)); 2715 if (pair->src_kind == svn_node_none) 2716 { 2717 if (SVN_IS_VALID_REVNUM(pair->src_revnum)) 2718 return svn_error_createf 2719 (SVN_ERR_FS_NOT_FOUND, NULL, 2720 _("Path '%s' not found in revision %ld"), 2721 pair->src_abspath_or_url, pair->src_revnum); 2722 else 2723 return svn_error_createf 2724 (SVN_ERR_FS_NOT_FOUND, NULL, 2725 _("Path '%s' not found in head revision"), 2726 pair->src_abspath_or_url); 2727 } 2728 2729 /* Figure out about dst. */ 2730 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 2731 iterpool)); 2732 if (dst_kind != svn_node_none) 2733 { 2734 return svn_error_createf( 2735 SVN_ERR_ENTRY_EXISTS, NULL, 2736 _("Path '%s' already exists"), 2737 svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2738 } 2739 2740 /* Make sure the destination parent is a directory and produce a clear 2741 error message if it is not. */ 2742 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); 2743 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); 2744 if (make_parents && dst_parent_kind == svn_node_none) 2745 { 2746 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, 2747 iterpool)); 2748 } 2749 else if (dst_parent_kind != svn_node_dir) 2750 { 2751 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 2752 _("Path '%s' is not a directory"), 2753 svn_dirent_local_style(dst_parent, pool)); 2754 } 2755 } 2756 svn_pool_destroy(iterpool); 2757 2758 SVN_WC__CALL_WITH_WRITE_LOCK( 2759 repos_to_wc_copy_locked(timestamp_sleep, 2760 copy_pairs, top_dst_path, ignore_externals, 2761 pin_externals, externals_to_pin, 2762 ra_session, ctx, pool), 2763 ctx->wc_ctx, lock_abspath, FALSE, pool); 2764 return SVN_NO_ERROR; 2765} 2766 2767#define NEED_REPOS_REVNUM(revision) \ 2768 ((revision.kind != svn_opt_revision_unspecified) \ 2769 && (revision.kind != svn_opt_revision_working)) 2770 2771/* ... 2772 * 2773 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not 2774 * change *TIMESTAMP_SLEEP. This output will be valid even if the 2775 * function returns an error. 2776 * 2777 * Perform all allocations in POOL. 2778 */ 2779static svn_error_t * 2780try_copy(svn_boolean_t *timestamp_sleep, 2781 const apr_array_header_t *sources, 2782 const char *dst_path_in, 2783 svn_boolean_t is_move, 2784 svn_boolean_t allow_mixed_revisions, 2785 svn_boolean_t metadata_only, 2786 svn_boolean_t make_parents, 2787 svn_boolean_t ignore_externals, 2788 svn_boolean_t pin_externals, 2789 const apr_hash_t *externals_to_pin, 2790 const apr_hash_t *revprop_table, 2791 svn_commit_callback2_t commit_callback, 2792 void *commit_baton, 2793 svn_client_ctx_t *ctx, 2794 apr_pool_t *pool) 2795{ 2796 apr_array_header_t *copy_pairs = 2797 apr_array_make(pool, sources->nelts, 2798 sizeof(svn_client__copy_pair_t *)); 2799 svn_boolean_t srcs_are_urls, dst_is_url; 2800 int i; 2801 2802 /* Assert instead of crashing if the sources list is empty. */ 2803 SVN_ERR_ASSERT(sources->nelts > 0); 2804 2805 /* Are either of our paths URLs? Just check the first src_path. If 2806 there are more than one, we'll check for homogeneity among them 2807 down below. */ 2808 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0, 2809 svn_client_copy_source_t *)->path); 2810 dst_is_url = svn_path_is_url(dst_path_in); 2811 if (!dst_is_url) 2812 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool)); 2813 2814 /* If we have multiple source paths, it implies the dst_path is a 2815 directory we are moving or copying into. Populate the COPY_PAIRS 2816 array to contain a destination path for each of the source paths. */ 2817 if (sources->nelts > 1) 2818 { 2819 apr_pool_t *iterpool = svn_pool_create(pool); 2820 2821 for (i = 0; i < sources->nelts; i++) 2822 { 2823 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i, 2824 svn_client_copy_source_t *); 2825 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair)); 2826 const char *src_basename; 2827 svn_boolean_t src_is_url = svn_path_is_url(source->path); 2828 2829 svn_pool_clear(iterpool); 2830 2831 if (src_is_url) 2832 { 2833 pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2834 src_basename = svn_uri_basename(pair->src_abspath_or_url, 2835 iterpool); 2836 } 2837 else 2838 { 2839 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2840 source->path, pool)); 2841 src_basename = svn_dirent_basename(pair->src_abspath_or_url, 2842 iterpool); 2843 } 2844 2845 pair->src_op_revision = *source->revision; 2846 pair->src_peg_revision = *source->peg_revision; 2847 pair->src_kind = svn_node_unknown; 2848 2849 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2850 &pair->src_op_revision, 2851 src_is_url, 2852 TRUE, 2853 iterpool)); 2854 2855 /* Check to see if all the sources are urls or all working copy 2856 * paths. */ 2857 if (src_is_url != srcs_are_urls) 2858 return svn_error_create 2859 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2860 _("Cannot mix repository and working copy sources")); 2861 2862 if (dst_is_url) 2863 pair->dst_abspath_or_url = 2864 svn_path_url_add_component2(dst_path_in, src_basename, pool); 2865 else 2866 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in, 2867 src_basename, pool); 2868 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2869 } 2870 2871 svn_pool_destroy(iterpool); 2872 } 2873 else 2874 { 2875 /* Only one source path. */ 2876 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair)); 2877 svn_client_copy_source_t *source = 2878 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *); 2879 svn_boolean_t src_is_url = svn_path_is_url(source->path); 2880 2881 if (src_is_url) 2882 pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2883 else 2884 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2885 source->path, pool)); 2886 pair->src_op_revision = *source->revision; 2887 pair->src_peg_revision = *source->peg_revision; 2888 pair->src_kind = svn_node_unknown; 2889 2890 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2891 &pair->src_op_revision, 2892 src_is_url, TRUE, pool)); 2893 2894 pair->dst_abspath_or_url = dst_path_in; 2895 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2896 } 2897 2898 if (!srcs_are_urls && !dst_is_url) 2899 { 2900 apr_pool_t *iterpool = svn_pool_create(pool); 2901 2902 for (i = 0; i < copy_pairs->nelts; i++) 2903 { 2904 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2905 svn_client__copy_pair_t *); 2906 2907 svn_pool_clear(iterpool); 2908 2909 if (svn_dirent_is_child(pair->src_abspath_or_url, 2910 pair->dst_abspath_or_url, iterpool)) 2911 return svn_error_createf 2912 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2913 _("Cannot copy path '%s' into its own child '%s'"), 2914 svn_dirent_local_style(pair->src_abspath_or_url, pool), 2915 svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2916 } 2917 2918 svn_pool_destroy(iterpool); 2919 } 2920 2921 /* A file external should not be moved since the file external is 2922 implemented as a switched file and it would delete the file the 2923 file external is switched to, which is not the behavior the user 2924 would probably want. */ 2925 if (is_move && !srcs_are_urls) 2926 { 2927 apr_pool_t *iterpool = svn_pool_create(pool); 2928 2929 for (i = 0; i < copy_pairs->nelts; i++) 2930 { 2931 svn_client__copy_pair_t *pair = 2932 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2933 svn_node_kind_t external_kind; 2934 const char *defining_abspath; 2935 2936 svn_pool_clear(iterpool); 2937 2938 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2939 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, 2940 NULL, NULL, NULL, ctx->wc_ctx, 2941 pair->src_abspath_or_url, 2942 pair->src_abspath_or_url, TRUE, 2943 iterpool, iterpool)); 2944 2945 if (external_kind != svn_node_none) 2946 return svn_error_createf( 2947 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL, 2948 NULL, 2949 _("Cannot move the external at '%s'; please " 2950 "edit the svn:externals property on '%s'."), 2951 svn_dirent_local_style(pair->src_abspath_or_url, pool), 2952 svn_dirent_local_style(defining_abspath, pool)); 2953 } 2954 svn_pool_destroy(iterpool); 2955 } 2956 2957 if (is_move) 2958 { 2959 /* Disallow moves between the working copy and the repository. */ 2960 if (srcs_are_urls != dst_is_url) 2961 { 2962 return svn_error_create 2963 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2964 _("Moves between the working copy and the repository are not " 2965 "supported")); 2966 } 2967 2968 /* Disallow moving any path/URL onto or into itself. */ 2969 for (i = 0; i < copy_pairs->nelts; i++) 2970 { 2971 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2972 svn_client__copy_pair_t *); 2973 2974 if (strcmp(pair->src_abspath_or_url, 2975 pair->dst_abspath_or_url) == 0) 2976 return svn_error_createf( 2977 SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2978 srcs_are_urls ? 2979 _("Cannot move URL '%s' into itself") : 2980 _("Cannot move path '%s' into itself"), 2981 srcs_are_urls ? 2982 pair->src_abspath_or_url : 2983 svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2984 } 2985 } 2986 else 2987 { 2988 if (!srcs_are_urls) 2989 { 2990 /* If we are doing a wc->* copy, but with an operational revision 2991 other than the working copy revision, we are really doing a 2992 repo->* copy, because we're going to need to get the rev from the 2993 repo. */ 2994 2995 svn_boolean_t need_repos_op_rev = FALSE; 2996 svn_boolean_t need_repos_peg_rev = FALSE; 2997 2998 /* Check to see if any revision is something other than 2999 svn_opt_revision_unspecified or svn_opt_revision_working. */ 3000 for (i = 0; i < copy_pairs->nelts; i++) 3001 { 3002 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 3003 svn_client__copy_pair_t *); 3004 3005 if (NEED_REPOS_REVNUM(pair->src_op_revision)) 3006 need_repos_op_rev = TRUE; 3007 3008 if (NEED_REPOS_REVNUM(pair->src_peg_revision)) 3009 need_repos_peg_rev = TRUE; 3010 3011 if (need_repos_op_rev || need_repos_peg_rev) 3012 break; 3013 } 3014 3015 if (need_repos_op_rev || need_repos_peg_rev) 3016 { 3017 apr_pool_t *iterpool = svn_pool_create(pool); 3018 3019 for (i = 0; i < copy_pairs->nelts; i++) 3020 { 3021 const char *copyfrom_repos_root_url; 3022 const char *copyfrom_repos_relpath; 3023 const char *url; 3024 svn_revnum_t copyfrom_rev; 3025 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 3026 svn_client__copy_pair_t *); 3027 3028 svn_pool_clear(iterpool); 3029 3030 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 3031 3032 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev, 3033 ©from_repos_relpath, 3034 ©from_repos_root_url, 3035 NULL, NULL, NULL, 3036 ctx->wc_ctx, 3037 pair->src_abspath_or_url, 3038 TRUE, iterpool, iterpool)); 3039 3040 if (copyfrom_repos_relpath) 3041 url = svn_path_url_add_component2(copyfrom_repos_root_url, 3042 copyfrom_repos_relpath, 3043 pool); 3044 else 3045 return svn_error_createf 3046 (SVN_ERR_ENTRY_MISSING_URL, NULL, 3047 _("'%s' does not have a URL associated with it"), 3048 svn_dirent_local_style(pair->src_abspath_or_url, pool)); 3049 3050 pair->src_abspath_or_url = url; 3051 3052 if (!need_repos_peg_rev 3053 || pair->src_peg_revision.kind == svn_opt_revision_base) 3054 { 3055 /* Default the peg revision to that of the WC entry. */ 3056 pair->src_peg_revision.kind = svn_opt_revision_number; 3057 pair->src_peg_revision.value.number = copyfrom_rev; 3058 } 3059 3060 if (pair->src_op_revision.kind == svn_opt_revision_base) 3061 { 3062 /* Use the entry's revision as the operational rev. */ 3063 pair->src_op_revision.kind = svn_opt_revision_number; 3064 pair->src_op_revision.value.number = copyfrom_rev; 3065 } 3066 } 3067 3068 svn_pool_destroy(iterpool); 3069 srcs_are_urls = TRUE; 3070 } 3071 } 3072 } 3073 3074 /* Now, call the right handler for the operation. */ 3075 if ((! srcs_are_urls) && (! dst_is_url)) 3076 { 3077 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, 3078 metadata_only, ctx, pool, pool)); 3079 3080 /* Copy or move all targets. */ 3081 if (is_move) 3082 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep, 3083 copy_pairs, dst_path_in, 3084 allow_mixed_revisions, 3085 metadata_only, 3086 ctx, pool)); 3087 else 3088 { 3089 /* We ignore these values, so assert the default value */ 3090 SVN_ERR_ASSERT(allow_mixed_revisions); 3091 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep, 3092 copy_pairs, 3093 metadata_only, 3094 pin_externals, 3095 externals_to_pin, 3096 ctx, pool)); 3097 } 3098 } 3099 else if ((! srcs_are_urls) && (dst_is_url)) 3100 { 3101 return svn_error_trace( 3102 wc_to_repos_copy(copy_pairs, make_parents, revprop_table, 3103 commit_callback, commit_baton, 3104 pin_externals, externals_to_pin, ctx, pool)); 3105 } 3106 else if ((srcs_are_urls) && (! dst_is_url)) 3107 { 3108 return svn_error_trace( 3109 repos_to_wc_copy(timestamp_sleep, 3110 copy_pairs, make_parents, ignore_externals, 3111 pin_externals, externals_to_pin, ctx, pool)); 3112 } 3113 else 3114 { 3115 return svn_error_trace( 3116 repos_to_repos_copy(copy_pairs, make_parents, revprop_table, 3117 commit_callback, commit_baton, ctx, is_move, 3118 pin_externals, externals_to_pin, pool)); 3119 } 3120} 3121 3122 3123 3124/* Public Interfaces */ 3125svn_error_t * 3126svn_client_copy7(const apr_array_header_t *sources, 3127 const char *dst_path, 3128 svn_boolean_t copy_as_child, 3129 svn_boolean_t make_parents, 3130 svn_boolean_t ignore_externals, 3131 svn_boolean_t metadata_only, 3132 svn_boolean_t pin_externals, 3133 const apr_hash_t *externals_to_pin, 3134 const apr_hash_t *revprop_table, 3135 svn_commit_callback2_t commit_callback, 3136 void *commit_baton, 3137 svn_client_ctx_t *ctx, 3138 apr_pool_t *pool) 3139{ 3140 svn_error_t *err; 3141 svn_boolean_t timestamp_sleep = FALSE; 3142 apr_pool_t *subpool = svn_pool_create(pool); 3143 3144 if (sources->nelts > 1 && !copy_as_child) 3145 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 3146 NULL, NULL); 3147 3148 err = try_copy(×tamp_sleep, 3149 sources, dst_path, 3150 FALSE /* is_move */, 3151 TRUE /* allow_mixed_revisions */, 3152 metadata_only, 3153 make_parents, 3154 ignore_externals, 3155 pin_externals, 3156 externals_to_pin, 3157 revprop_table, 3158 commit_callback, commit_baton, 3159 ctx, 3160 subpool); 3161 3162 /* If the destination exists, try to copy the sources as children of the 3163 destination. */ 3164 if (copy_as_child && err && (sources->nelts == 1) 3165 && (err->apr_err == SVN_ERR_ENTRY_EXISTS 3166 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 3167 { 3168 const char *src_path = APR_ARRAY_IDX(sources, 0, 3169 svn_client_copy_source_t *)->path; 3170 const char *src_basename; 3171 svn_boolean_t src_is_url = svn_path_is_url(src_path); 3172 svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 3173 3174 svn_error_clear(err); 3175 svn_pool_clear(subpool); 3176 3177 src_basename = src_is_url ? svn_uri_basename(src_path, subpool) 3178 : svn_dirent_basename(src_path, subpool); 3179 dst_path 3180 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 3181 subpool) 3182 : svn_dirent_join(dst_path, src_basename, subpool); 3183 3184 err = try_copy(×tamp_sleep, 3185 sources, dst_path, 3186 FALSE /* is_move */, 3187 TRUE /* allow_mixed_revisions */, 3188 metadata_only, 3189 make_parents, 3190 ignore_externals, 3191 pin_externals, 3192 externals_to_pin, 3193 revprop_table, 3194 commit_callback, commit_baton, 3195 ctx, 3196 subpool); 3197 } 3198 3199 /* Sleep if required. DST_PATH is not a URL in these cases. */ 3200 if (timestamp_sleep) 3201 svn_io_sleep_for_timestamps(dst_path, subpool); 3202 3203 svn_pool_destroy(subpool); 3204 return svn_error_trace(err); 3205} 3206 3207 3208svn_error_t * 3209svn_client_move7(const apr_array_header_t *src_paths, 3210 const char *dst_path, 3211 svn_boolean_t move_as_child, 3212 svn_boolean_t make_parents, 3213 svn_boolean_t allow_mixed_revisions, 3214 svn_boolean_t metadata_only, 3215 const apr_hash_t *revprop_table, 3216 svn_commit_callback2_t commit_callback, 3217 void *commit_baton, 3218 svn_client_ctx_t *ctx, 3219 apr_pool_t *pool) 3220{ 3221 const svn_opt_revision_t head_revision 3222 = { svn_opt_revision_head, { 0 } }; 3223 svn_error_t *err; 3224 svn_boolean_t timestamp_sleep = FALSE; 3225 int i; 3226 apr_pool_t *subpool = svn_pool_create(pool); 3227 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts, 3228 sizeof(const svn_client_copy_source_t *)); 3229 3230 if (src_paths->nelts > 1 && !move_as_child) 3231 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 3232 NULL, NULL); 3233 3234 for (i = 0; i < src_paths->nelts; i++) 3235 { 3236 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *); 3237 svn_client_copy_source_t *copy_source = apr_palloc(pool, 3238 sizeof(*copy_source)); 3239 3240 copy_source->path = src_path; 3241 copy_source->revision = &head_revision; 3242 copy_source->peg_revision = &head_revision; 3243 3244 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source; 3245 } 3246 3247 err = try_copy(×tamp_sleep, 3248 sources, dst_path, 3249 TRUE /* is_move */, 3250 allow_mixed_revisions, 3251 metadata_only, 3252 make_parents, 3253 FALSE /* ignore_externals */, 3254 FALSE /* pin_externals */, 3255 NULL /* externals_to_pin */, 3256 revprop_table, 3257 commit_callback, commit_baton, 3258 ctx, 3259 subpool); 3260 3261 /* If the destination exists, try to move the sources as children of the 3262 destination. */ 3263 if (move_as_child && err && (src_paths->nelts == 1) 3264 && (err->apr_err == SVN_ERR_ENTRY_EXISTS 3265 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 3266 { 3267 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *); 3268 const char *src_basename; 3269 svn_boolean_t src_is_url = svn_path_is_url(src_path); 3270 svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 3271 3272 svn_error_clear(err); 3273 svn_pool_clear(subpool); 3274 3275 src_basename = src_is_url ? svn_uri_basename(src_path, pool) 3276 : svn_dirent_basename(src_path, pool); 3277 dst_path 3278 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 3279 subpool) 3280 : svn_dirent_join(dst_path, src_basename, subpool); 3281 3282 err = try_copy(×tamp_sleep, 3283 sources, dst_path, 3284 TRUE /* is_move */, 3285 allow_mixed_revisions, 3286 metadata_only, 3287 make_parents, 3288 FALSE /* ignore_externals */, 3289 FALSE /* pin_externals */, 3290 NULL /* externals_to_pin */, 3291 revprop_table, 3292 commit_callback, commit_baton, 3293 ctx, 3294 subpool); 3295 } 3296 3297 /* Sleep if required. DST_PATH is not a URL in these cases. */ 3298 if (timestamp_sleep) 3299 svn_io_sleep_for_timestamps(dst_path, subpool); 3300 3301 svn_pool_destroy(subpool); 3302 return svn_error_trace(err); 3303} 3304