1251881Speter/* 2251881Speter * copy.c: copy/move wrappers around wc 'copy' functionality. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <string.h> 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_client.h" 33251881Speter#include "svn_error.h" 34251881Speter#include "svn_error_codes.h" 35251881Speter#include "svn_dirent_uri.h" 36251881Speter#include "svn_path.h" 37251881Speter#include "svn_opt.h" 38251881Speter#include "svn_time.h" 39251881Speter#include "svn_props.h" 40251881Speter#include "svn_mergeinfo.h" 41251881Speter#include "svn_pools.h" 42251881Speter 43251881Speter#include "client.h" 44251881Speter#include "mergeinfo.h" 45251881Speter 46251881Speter#include "svn_private_config.h" 47251881Speter#include "private/svn_wc_private.h" 48251881Speter#include "private/svn_ra_private.h" 49251881Speter#include "private/svn_mergeinfo_private.h" 50251881Speter#include "private/svn_client_private.h" 51251881Speter 52251881Speter 53251881Speter/* 54251881Speter * OUR BASIC APPROACH TO COPIES 55251881Speter * ============================ 56251881Speter * 57251881Speter * for each source/destination pair 58251881Speter * if (not exist src_path) 59251881Speter * return ERR_BAD_SRC error 60251881Speter * 61251881Speter * if (exist dst_path) 62251881Speter * return ERR_OBSTRUCTION error 63251881Speter * else 64251881Speter * copy src_path into parent_of_dst_path as basename (dst_path) 65251881Speter * 66251881Speter * if (this is a move) 67251881Speter * delete src_path 68251881Speter */ 69251881Speter 70251881Speter 71251881Speter 72251881Speter/*** Code. ***/ 73251881Speter 74251881Speter/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding 75251881Speter MERGEINFO to any mergeinfo pre-existing in the WC. */ 76251881Speterstatic svn_error_t * 77251881Speterextend_wc_mergeinfo(const char *target_abspath, 78251881Speter apr_hash_t *mergeinfo, 79251881Speter svn_client_ctx_t *ctx, 80251881Speter apr_pool_t *pool) 81251881Speter{ 82251881Speter apr_hash_t *wc_mergeinfo; 83251881Speter 84251881Speter /* Get a fresh copy of the pre-existing state of the WC's mergeinfo 85251881Speter updating it. */ 86251881Speter SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 87251881Speter target_abspath, pool, pool)); 88251881Speter 89251881Speter /* Combine the provided mergeinfo with any mergeinfo from the WC. */ 90251881Speter if (wc_mergeinfo && mergeinfo) 91251881Speter SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool)); 92251881Speter else if (! wc_mergeinfo) 93251881Speter wc_mergeinfo = mergeinfo; 94251881Speter 95251881Speter return svn_error_trace( 96251881Speter svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo, 97251881Speter FALSE, ctx, pool)); 98251881Speter} 99251881Speter 100251881Speter/* Find the longest common ancestor of paths in COPY_PAIRS. If 101251881Speter SRC_ANCESTOR is NULL, ignore source paths in this calculation. If 102251881Speter DST_ANCESTOR is NULL, ignore destination paths in this calculation. 103251881Speter COMMON_ANCESTOR will be the common ancestor of both the 104251881Speter SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not 105251881Speter NULL. 106251881Speter */ 107251881Speterstatic svn_error_t * 108251881Speterget_copy_pair_ancestors(const apr_array_header_t *copy_pairs, 109251881Speter const char **src_ancestor, 110251881Speter const char **dst_ancestor, 111251881Speter const char **common_ancestor, 112251881Speter apr_pool_t *pool) 113251881Speter{ 114251881Speter apr_pool_t *subpool = svn_pool_create(pool); 115251881Speter svn_client__copy_pair_t *first; 116251881Speter const char *first_dst; 117251881Speter const char *first_src; 118251881Speter const char *top_dst; 119251881Speter svn_boolean_t src_is_url; 120251881Speter svn_boolean_t dst_is_url; 121251881Speter char *top_src; 122251881Speter int i; 123251881Speter 124251881Speter first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 125251881Speter 126251881Speter /* Because all the destinations are in the same directory, we can easily 127251881Speter determine their common ancestor. */ 128251881Speter first_dst = first->dst_abspath_or_url; 129251881Speter dst_is_url = svn_path_is_url(first_dst); 130251881Speter 131251881Speter if (copy_pairs->nelts == 1) 132251881Speter top_dst = apr_pstrdup(subpool, first_dst); 133251881Speter else 134251881Speter top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool) 135251881Speter : svn_dirent_dirname(first_dst, subpool); 136251881Speter 137251881Speter /* Sources can came from anywhere, so we have to actually do some 138251881Speter work for them. */ 139251881Speter first_src = first->src_abspath_or_url; 140251881Speter src_is_url = svn_path_is_url(first_src); 141251881Speter top_src = apr_pstrdup(subpool, first_src); 142251881Speter for (i = 1; i < copy_pairs->nelts; i++) 143251881Speter { 144251881Speter /* We don't need to clear the subpool here for several reasons: 145251881Speter 1) If we do, we can't use it to allocate the initial versions of 146251881Speter top_src and top_dst (above). 147251881Speter 2) We don't return any errors in the following loop, so we 148251881Speter are guanteed to destroy the subpool at the end of this function. 149251881Speter 3) The number of iterations is likely to be few, and the loop will 150251881Speter be through quickly, so memory leakage will not be significant, 151251881Speter in time or space. 152251881Speter */ 153251881Speter const svn_client__copy_pair_t *pair = 154251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 155251881Speter 156251881Speter top_src = src_is_url 157251881Speter ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url, 158251881Speter subpool) 159251881Speter : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url, 160251881Speter subpool); 161251881Speter } 162251881Speter 163251881Speter if (src_ancestor) 164251881Speter *src_ancestor = apr_pstrdup(pool, top_src); 165251881Speter 166251881Speter if (dst_ancestor) 167251881Speter *dst_ancestor = apr_pstrdup(pool, top_dst); 168251881Speter 169251881Speter if (common_ancestor) 170251881Speter *common_ancestor = 171251881Speter src_is_url 172251881Speter ? svn_uri_get_longest_ancestor(top_src, top_dst, pool) 173251881Speter : svn_dirent_get_longest_ancestor(top_src, top_dst, pool); 174251881Speter 175251881Speter svn_pool_destroy(subpool); 176251881Speter 177251881Speter return SVN_NO_ERROR; 178251881Speter} 179251881Speter 180251881Speter 181251881Speter/* The guts of do_wc_to_wc_copies */ 182251881Speterstatic svn_error_t * 183251881Speterdo_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep, 184251881Speter const apr_array_header_t *copy_pairs, 185251881Speter const char *dst_parent, 186251881Speter svn_client_ctx_t *ctx, 187251881Speter apr_pool_t *scratch_pool) 188251881Speter{ 189251881Speter int i; 190251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 191251881Speter svn_error_t *err = SVN_NO_ERROR; 192251881Speter 193251881Speter for (i = 0; i < copy_pairs->nelts; i++) 194251881Speter { 195251881Speter const char *dst_abspath; 196251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 197251881Speter svn_client__copy_pair_t *); 198251881Speter svn_pool_clear(iterpool); 199251881Speter 200251881Speter /* Check for cancellation */ 201251881Speter if (ctx->cancel_func) 202251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 203251881Speter 204251881Speter /* Perform the copy */ 205251881Speter dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name, 206251881Speter iterpool); 207251881Speter *timestamp_sleep = TRUE; 208251881Speter err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath, 209251881Speter FALSE /* metadata_only */, 210251881Speter ctx->cancel_func, ctx->cancel_baton, 211251881Speter ctx->notify_func2, ctx->notify_baton2, iterpool); 212251881Speter if (err) 213251881Speter break; 214251881Speter } 215251881Speter svn_pool_destroy(iterpool); 216251881Speter 217251881Speter SVN_ERR(err); 218251881Speter return SVN_NO_ERROR; 219251881Speter} 220251881Speter 221251881Speter/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary 222251881Speter allocations. */ 223251881Speterstatic svn_error_t * 224251881Speterdo_wc_to_wc_copies(svn_boolean_t *timestamp_sleep, 225251881Speter const apr_array_header_t *copy_pairs, 226251881Speter svn_client_ctx_t *ctx, 227251881Speter apr_pool_t *pool) 228251881Speter{ 229251881Speter const char *dst_parent, *dst_parent_abspath; 230251881Speter 231251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool)); 232251881Speter if (copy_pairs->nelts == 1) 233251881Speter dst_parent = svn_dirent_dirname(dst_parent, pool); 234251881Speter 235251881Speter SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool)); 236251881Speter 237251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 238251881Speter do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent, 239251881Speter ctx, pool), 240251881Speter ctx->wc_ctx, dst_parent_abspath, FALSE, pool); 241251881Speter 242251881Speter return SVN_NO_ERROR; 243251881Speter} 244251881Speter 245251881Speter/* The locked bit of do_wc_to_wc_moves. */ 246251881Speterstatic svn_error_t * 247251881Speterdo_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair, 248251881Speter const char *dst_parent_abspath, 249251881Speter svn_boolean_t lock_src, 250251881Speter svn_boolean_t lock_dst, 251251881Speter svn_boolean_t allow_mixed_revisions, 252251881Speter svn_boolean_t metadata_only, 253251881Speter svn_client_ctx_t *ctx, 254251881Speter apr_pool_t *scratch_pool) 255251881Speter{ 256251881Speter const char *dst_abspath; 257251881Speter 258251881Speter dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name, 259251881Speter scratch_pool); 260251881Speter 261251881Speter SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url, 262251881Speter dst_abspath, metadata_only, 263251881Speter allow_mixed_revisions, 264251881Speter ctx->cancel_func, ctx->cancel_baton, 265251881Speter ctx->notify_func2, ctx->notify_baton2, 266251881Speter scratch_pool)); 267251881Speter 268251881Speter return SVN_NO_ERROR; 269251881Speter} 270251881Speter 271251881Speter/* Wrapper to add an optional second lock */ 272251881Speterstatic svn_error_t * 273251881Speterdo_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair, 274251881Speter const char *dst_parent_abspath, 275251881Speter svn_boolean_t lock_src, 276251881Speter svn_boolean_t lock_dst, 277251881Speter svn_boolean_t allow_mixed_revisions, 278251881Speter svn_boolean_t metadata_only, 279251881Speter svn_client_ctx_t *ctx, 280251881Speter apr_pool_t *scratch_pool) 281251881Speter{ 282251881Speter if (lock_dst) 283251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 284251881Speter do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 285251881Speter lock_dst, allow_mixed_revisions, 286251881Speter metadata_only, 287251881Speter ctx, scratch_pool), 288251881Speter ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool); 289251881Speter else 290251881Speter SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src, 291251881Speter lock_dst, allow_mixed_revisions, 292251881Speter metadata_only, 293251881Speter ctx, scratch_pool)); 294251881Speter 295251881Speter return SVN_NO_ERROR; 296251881Speter} 297251881Speter 298251881Speter/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC 299251881Speter afterwards. Use POOL for temporary allocations. */ 300251881Speterstatic svn_error_t * 301251881Speterdo_wc_to_wc_moves(svn_boolean_t *timestamp_sleep, 302251881Speter const apr_array_header_t *copy_pairs, 303251881Speter const char *dst_path, 304251881Speter svn_boolean_t allow_mixed_revisions, 305251881Speter svn_boolean_t metadata_only, 306251881Speter svn_client_ctx_t *ctx, 307251881Speter apr_pool_t *pool) 308251881Speter{ 309251881Speter int i; 310251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 311251881Speter svn_error_t *err = SVN_NO_ERROR; 312251881Speter 313251881Speter for (i = 0; i < copy_pairs->nelts; i++) 314251881Speter { 315251881Speter const char *src_parent_abspath; 316251881Speter svn_boolean_t lock_src, lock_dst; 317251881Speter 318251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 319251881Speter svn_client__copy_pair_t *); 320251881Speter svn_pool_clear(iterpool); 321251881Speter 322251881Speter /* Check for cancellation */ 323251881Speter if (ctx->cancel_func) 324251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 325251881Speter 326251881Speter src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url, 327251881Speter iterpool); 328251881Speter 329251881Speter /* We now need to lock the right combination of batons. 330251881Speter Four cases: 331251881Speter 1) src_parent == dst_parent 332251881Speter 2) src_parent is parent of dst_parent 333251881Speter 3) dst_parent is parent of src_parent 334251881Speter 4) src_parent and dst_parent are disjoint 335251881Speter We can handle 1) as either 2) or 3) */ 336251881Speter if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0 337251881Speter || svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath, 338251881Speter iterpool)) 339251881Speter { 340251881Speter lock_src = TRUE; 341251881Speter lock_dst = FALSE; 342251881Speter } 343251881Speter else if (svn_dirent_is_child(pair->dst_parent_abspath, 344251881Speter src_parent_abspath, 345251881Speter iterpool)) 346251881Speter { 347251881Speter lock_src = FALSE; 348251881Speter lock_dst = TRUE; 349251881Speter } 350251881Speter else 351251881Speter { 352251881Speter lock_src = TRUE; 353251881Speter lock_dst = TRUE; 354251881Speter } 355251881Speter 356251881Speter *timestamp_sleep = TRUE; 357251881Speter 358251881Speter /* Perform the copy and then the delete. */ 359251881Speter if (lock_src) 360251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 361251881Speter do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 362251881Speter lock_src, lock_dst, 363251881Speter allow_mixed_revisions, 364251881Speter metadata_only, 365251881Speter ctx, iterpool), 366251881Speter ctx->wc_ctx, src_parent_abspath, 367251881Speter FALSE, iterpool); 368251881Speter else 369251881Speter SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath, 370251881Speter lock_src, lock_dst, 371251881Speter allow_mixed_revisions, 372251881Speter metadata_only, 373251881Speter ctx, iterpool)); 374251881Speter 375251881Speter } 376251881Speter svn_pool_destroy(iterpool); 377251881Speter 378251881Speter return svn_error_trace(err); 379251881Speter} 380251881Speter 381251881Speter/* Verify that the destinations stored in COPY_PAIRS are valid working copy 382251881Speter destinations and set pair->dst_parent_abspath and pair->base_name for each 383251881Speter item to the resulting location if they do */ 384251881Speterstatic svn_error_t * 385251881Speterverify_wc_dsts(const apr_array_header_t *copy_pairs, 386251881Speter svn_boolean_t make_parents, 387251881Speter svn_boolean_t is_move, 388253734Speter svn_boolean_t metadata_only, 389251881Speter svn_client_ctx_t *ctx, 390251881Speter apr_pool_t *result_pool, 391251881Speter apr_pool_t *scratch_pool) 392251881Speter{ 393251881Speter int i; 394251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 395251881Speter 396251881Speter /* Check that DST does not exist, but its parent does */ 397251881Speter for (i = 0; i < copy_pairs->nelts; i++) 398251881Speter { 399251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 400251881Speter svn_client__copy_pair_t *); 401251881Speter svn_node_kind_t dst_kind, dst_parent_kind; 402251881Speter 403251881Speter svn_pool_clear(iterpool); 404251881Speter 405251881Speter /* If DST_PATH does not exist, then its basename will become a new 406251881Speter file or dir added to its parent (possibly an implicit '.'). 407251881Speter Else, just error out. */ 408251881Speter SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx, 409251881Speter pair->dst_abspath_or_url, 410251881Speter FALSE /* show_deleted */, 411251881Speter TRUE /* show_hidden */, 412251881Speter iterpool)); 413251881Speter if (dst_kind != svn_node_none) 414251881Speter { 415251881Speter svn_boolean_t is_excluded; 416251881Speter svn_boolean_t is_server_excluded; 417251881Speter 418251881Speter SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded, 419251881Speter &is_server_excluded, ctx->wc_ctx, 420251881Speter pair->dst_abspath_or_url, FALSE, 421251881Speter iterpool)); 422251881Speter 423251881Speter if (is_excluded || is_server_excluded) 424251881Speter { 425251881Speter return svn_error_createf( 426251881Speter SVN_ERR_WC_OBSTRUCTED_UPDATE, 427251881Speter NULL, _("Path '%s' exists, but is excluded"), 428251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, iterpool)); 429251881Speter } 430251881Speter else 431251881Speter return svn_error_createf( 432251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 433251881Speter _("Path '%s' already exists"), 434251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, 435251881Speter scratch_pool)); 436251881Speter } 437251881Speter 438251881Speter /* Check that there is no unversioned obstruction */ 439253734Speter if (metadata_only) 440253734Speter dst_kind = svn_node_none; 441253734Speter else 442253734Speter SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 443253734Speter iterpool)); 444251881Speter 445251881Speter if (dst_kind != svn_node_none) 446251881Speter { 447251881Speter if (is_move 448251881Speter && copy_pairs->nelts == 1 449251881Speter && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool), 450251881Speter svn_dirent_dirname(pair->dst_abspath_or_url, 451251881Speter iterpool)) == 0) 452251881Speter { 453251881Speter const char *dst; 454251881Speter char *dst_apr; 455251881Speter apr_status_t apr_err; 456251881Speter /* We have a rename inside a directory, which might collide 457251881Speter just because the case insensivity of the filesystem makes 458251881Speter the source match the destination. */ 459251881Speter 460251881Speter SVN_ERR(svn_path_cstring_from_utf8(&dst, 461251881Speter pair->dst_abspath_or_url, 462251881Speter scratch_pool)); 463251881Speter 464251881Speter apr_err = apr_filepath_merge(&dst_apr, NULL, dst, 465251881Speter APR_FILEPATH_TRUENAME, iterpool); 466251881Speter 467251881Speter if (!apr_err) 468251881Speter { 469251881Speter /* And now bring it back to our canonical format */ 470251881Speter SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool)); 471251881Speter dst = svn_dirent_canonicalize(dst, iterpool); 472251881Speter } 473251881Speter /* else: Don't report this error; just report the normal error */ 474251881Speter 475251881Speter if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0) 476251881Speter { 477251881Speter /* Ok, we have a single case only rename. Get out of here */ 478251881Speter svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 479251881Speter pair->dst_abspath_or_url, result_pool); 480251881Speter 481251881Speter svn_pool_destroy(iterpool); 482251881Speter return SVN_NO_ERROR; 483251881Speter } 484251881Speter } 485251881Speter 486251881Speter return svn_error_createf( 487251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 488251881Speter _("Path '%s' already exists as unversioned node"), 489251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, 490251881Speter scratch_pool)); 491251881Speter } 492251881Speter 493251881Speter svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name, 494251881Speter pair->dst_abspath_or_url, result_pool); 495251881Speter 496251881Speter /* Make sure the destination parent is a directory and produce a clear 497251881Speter error message if it is not. */ 498251881Speter SVN_ERR(svn_wc_read_kind2(&dst_parent_kind, 499251881Speter ctx->wc_ctx, pair->dst_parent_abspath, 500251881Speter FALSE, TRUE, 501251881Speter iterpool)); 502251881Speter if (make_parents && dst_parent_kind == svn_node_none) 503251881Speter { 504251881Speter SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath, 505251881Speter TRUE, ctx, iterpool)); 506251881Speter } 507251881Speter else if (dst_parent_kind != svn_node_dir) 508251881Speter { 509251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 510251881Speter _("Path '%s' is not a directory"), 511251881Speter svn_dirent_local_style( 512251881Speter pair->dst_parent_abspath, scratch_pool)); 513251881Speter } 514251881Speter 515251881Speter SVN_ERR(svn_io_check_path(pair->dst_parent_abspath, 516251881Speter &dst_parent_kind, scratch_pool)); 517251881Speter 518251881Speter if (dst_parent_kind != svn_node_dir) 519251881Speter return svn_error_createf(SVN_ERR_WC_MISSING, NULL, 520251881Speter _("Path '%s' is not a directory"), 521251881Speter svn_dirent_local_style( 522251881Speter pair->dst_parent_abspath, scratch_pool)); 523251881Speter } 524251881Speter 525251881Speter svn_pool_destroy(iterpool); 526251881Speter 527251881Speter return SVN_NO_ERROR; 528251881Speter} 529251881Speter 530251881Speterstatic svn_error_t * 531251881Speterverify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs, 532251881Speter svn_boolean_t make_parents, 533251881Speter svn_boolean_t is_move, 534253734Speter svn_boolean_t metadata_only, 535251881Speter svn_client_ctx_t *ctx, 536251881Speter apr_pool_t *result_pool, 537251881Speter apr_pool_t *scratch_pool) 538251881Speter{ 539251881Speter int i; 540251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 541251881Speter 542251881Speter /* Check that all of our SRCs exist. */ 543251881Speter for (i = 0; i < copy_pairs->nelts; i++) 544251881Speter { 545251881Speter svn_boolean_t deleted_ok; 546251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 547251881Speter svn_client__copy_pair_t *); 548251881Speter svn_pool_clear(iterpool); 549251881Speter 550251881Speter deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base 551251881Speter || pair->src_op_revision.kind == svn_opt_revision_base); 552251881Speter 553251881Speter /* Verify that SRC_PATH exists. */ 554251881Speter SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx, 555251881Speter pair->src_abspath_or_url, 556251881Speter deleted_ok, FALSE, iterpool)); 557251881Speter if (pair->src_kind == svn_node_none) 558251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 559251881Speter _("Path '%s' does not exist"), 560251881Speter svn_dirent_local_style( 561251881Speter pair->src_abspath_or_url, 562251881Speter scratch_pool)); 563251881Speter } 564251881Speter 565253734Speter SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx, 566251881Speter result_pool, iterpool)); 567251881Speter 568251881Speter svn_pool_destroy(iterpool); 569251881Speter 570251881Speter return SVN_NO_ERROR; 571251881Speter} 572251881Speter 573251881Speter 574251881Speter/* Path-specific state used as part of path_driver_cb_baton. */ 575251881Spetertypedef struct path_driver_info_t 576251881Speter{ 577251881Speter const char *src_url; 578251881Speter const char *src_path; 579251881Speter const char *dst_path; 580251881Speter svn_node_kind_t src_kind; 581251881Speter svn_revnum_t src_revnum; 582251881Speter svn_boolean_t resurrection; 583251881Speter svn_boolean_t dir_add; 584251881Speter svn_string_t *mergeinfo; /* the new mergeinfo for the target */ 585251881Speter} path_driver_info_t; 586251881Speter 587251881Speter 588251881Speter/* The baton used with the path_driver_cb_func() callback for a copy 589251881Speter or move operation. */ 590251881Speterstruct path_driver_cb_baton 591251881Speter{ 592251881Speter /* The editor (and its state) used to perform the operation. */ 593251881Speter const svn_delta_editor_t *editor; 594251881Speter void *edit_baton; 595251881Speter 596251881Speter /* A hash of path -> path_driver_info_t *'s. */ 597251881Speter apr_hash_t *action_hash; 598251881Speter 599251881Speter /* Whether the operation is a move or copy. */ 600251881Speter svn_boolean_t is_move; 601251881Speter}; 602251881Speter 603251881Speterstatic svn_error_t * 604251881Speterpath_driver_cb_func(void **dir_baton, 605251881Speter void *parent_baton, 606251881Speter void *callback_baton, 607251881Speter const char *path, 608251881Speter apr_pool_t *pool) 609251881Speter{ 610251881Speter struct path_driver_cb_baton *cb_baton = callback_baton; 611251881Speter svn_boolean_t do_delete = FALSE, do_add = FALSE; 612251881Speter path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path); 613251881Speter 614251881Speter /* Initialize return value. */ 615251881Speter *dir_baton = NULL; 616251881Speter 617251881Speter /* This function should never get an empty PATH. We can neither 618251881Speter create nor delete the empty PATH, so if someone is calling us 619251881Speter with such, the code is just plain wrong. */ 620251881Speter SVN_ERR_ASSERT(! svn_path_is_empty(path)); 621251881Speter 622251881Speter /* Check to see if we need to add the path as a directory. */ 623251881Speter if (path_info->dir_add) 624251881Speter { 625251881Speter return cb_baton->editor->add_directory(path, parent_baton, NULL, 626251881Speter SVN_INVALID_REVNUM, pool, 627251881Speter dir_baton); 628251881Speter } 629251881Speter 630251881Speter /* If this is a resurrection, we know the source and dest paths are 631251881Speter the same, and that our driver will only be calling us once. */ 632251881Speter if (path_info->resurrection) 633251881Speter { 634251881Speter /* If this is a move, we do nothing. Otherwise, we do the copy. */ 635251881Speter if (! cb_baton->is_move) 636251881Speter do_add = TRUE; 637251881Speter } 638251881Speter /* Not a resurrection. */ 639251881Speter else 640251881Speter { 641251881Speter /* If this is a move, we check PATH to see if it is the source 642251881Speter or the destination of the move. */ 643251881Speter if (cb_baton->is_move) 644251881Speter { 645251881Speter if (strcmp(path_info->src_path, path) == 0) 646251881Speter do_delete = TRUE; 647251881Speter else 648251881Speter do_add = TRUE; 649251881Speter } 650251881Speter /* Not a move? This must just be the copy addition. */ 651251881Speter else 652251881Speter { 653251881Speter do_add = TRUE; 654251881Speter } 655251881Speter } 656251881Speter 657251881Speter if (do_delete) 658251881Speter { 659251881Speter SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM, 660251881Speter parent_baton, pool)); 661251881Speter } 662251881Speter if (do_add) 663251881Speter { 664251881Speter SVN_ERR(svn_path_check_valid(path, pool)); 665251881Speter 666251881Speter if (path_info->src_kind == svn_node_file) 667251881Speter { 668251881Speter void *file_baton; 669251881Speter SVN_ERR(cb_baton->editor->add_file(path, parent_baton, 670251881Speter path_info->src_url, 671251881Speter path_info->src_revnum, 672251881Speter pool, &file_baton)); 673251881Speter if (path_info->mergeinfo) 674251881Speter SVN_ERR(cb_baton->editor->change_file_prop(file_baton, 675251881Speter SVN_PROP_MERGEINFO, 676251881Speter path_info->mergeinfo, 677251881Speter pool)); 678251881Speter SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool)); 679251881Speter } 680251881Speter else 681251881Speter { 682251881Speter SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, 683251881Speter path_info->src_url, 684251881Speter path_info->src_revnum, 685251881Speter pool, dir_baton)); 686251881Speter if (path_info->mergeinfo) 687251881Speter SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, 688251881Speter SVN_PROP_MERGEINFO, 689251881Speter path_info->mergeinfo, 690251881Speter pool)); 691251881Speter } 692251881Speter } 693251881Speter return SVN_NO_ERROR; 694251881Speter} 695251881Speter 696251881Speter 697251881Speter/* Starting with the path DIR relative to the RA_SESSION's session 698251881Speter URL, work up through DIR's parents until an existing node is found. 699251881Speter Push each nonexistent path onto the array NEW_DIRS, allocating in 700251881Speter POOL. Raise an error if the existing node is not a directory. 701251881Speter 702251881Speter ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 703251881Speter ### implementation susceptible to race conditions. */ 704251881Speterstatic svn_error_t * 705251881Speterfind_absent_parents1(svn_ra_session_t *ra_session, 706251881Speter const char *dir, 707251881Speter apr_array_header_t *new_dirs, 708251881Speter apr_pool_t *pool) 709251881Speter{ 710251881Speter svn_node_kind_t kind; 711251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 712251881Speter 713251881Speter SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind, 714251881Speter iterpool)); 715251881Speter 716251881Speter while (kind == svn_node_none) 717251881Speter { 718251881Speter svn_pool_clear(iterpool); 719251881Speter 720251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = dir; 721251881Speter dir = svn_dirent_dirname(dir, pool); 722251881Speter 723251881Speter SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, 724251881Speter &kind, iterpool)); 725251881Speter } 726251881Speter 727251881Speter if (kind != svn_node_dir) 728251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 729251881Speter _("Path '%s' already exists, but is not a " 730251881Speter "directory"), dir); 731251881Speter 732251881Speter svn_pool_destroy(iterpool); 733251881Speter return SVN_NO_ERROR; 734251881Speter} 735251881Speter 736251881Speter/* Starting with the URL *TOP_DST_URL which is also the root of 737251881Speter RA_SESSION, work up through its parents until an existing node is 738251881Speter found. Push each nonexistent URL onto the array NEW_DIRS, 739251881Speter allocating in POOL. Raise an error if the existing node is not a 740251881Speter directory. 741251881Speter 742251881Speter Set *TOP_DST_URL and the RA session's root to the existing node's URL. 743251881Speter 744251881Speter ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this 745251881Speter ### implementation susceptible to race conditions. */ 746251881Speterstatic svn_error_t * 747251881Speterfind_absent_parents2(svn_ra_session_t *ra_session, 748251881Speter const char **top_dst_url, 749251881Speter apr_array_header_t *new_dirs, 750251881Speter apr_pool_t *pool) 751251881Speter{ 752251881Speter const char *root_url = *top_dst_url; 753251881Speter svn_node_kind_t kind; 754251881Speter 755251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 756251881Speter pool)); 757251881Speter 758251881Speter while (kind == svn_node_none) 759251881Speter { 760251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = root_url; 761251881Speter root_url = svn_uri_dirname(root_url, pool); 762251881Speter 763251881Speter SVN_ERR(svn_ra_reparent(ra_session, root_url, pool)); 764251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 765251881Speter pool)); 766251881Speter } 767251881Speter 768251881Speter if (kind != svn_node_dir) 769251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 770251881Speter _("Path '%s' already exists, but is not a directory"), 771251881Speter root_url); 772251881Speter 773251881Speter *top_dst_url = root_url; 774251881Speter return SVN_NO_ERROR; 775251881Speter} 776251881Speter 777251881Speterstatic svn_error_t * 778251881Speterrepos_to_repos_copy(const apr_array_header_t *copy_pairs, 779251881Speter svn_boolean_t make_parents, 780251881Speter const apr_hash_t *revprop_table, 781251881Speter svn_commit_callback2_t commit_callback, 782251881Speter void *commit_baton, 783251881Speter svn_client_ctx_t *ctx, 784251881Speter svn_boolean_t is_move, 785251881Speter apr_pool_t *pool) 786251881Speter{ 787251881Speter svn_error_t *err; 788251881Speter apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts, 789251881Speter sizeof(const char *)); 790251881Speter apr_hash_t *action_hash = apr_hash_make(pool); 791251881Speter apr_array_header_t *path_infos; 792251881Speter const char *top_url, *top_url_all, *top_url_dst; 793251881Speter const char *message, *repos_root; 794251881Speter svn_ra_session_t *ra_session = NULL; 795251881Speter const svn_delta_editor_t *editor; 796251881Speter void *edit_baton; 797251881Speter struct path_driver_cb_baton cb_baton; 798251881Speter apr_array_header_t *new_dirs = NULL; 799251881Speter apr_hash_t *commit_revprops; 800251881Speter int i; 801251881Speter svn_client__copy_pair_t *first_pair = 802251881Speter APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 803251881Speter 804251881Speter /* Open an RA session to the first copy pair's destination. We'll 805251881Speter be verifying that every one of our copy source and destination 806251881Speter URLs is or is beneath this sucker's repository root URL as a form 807251881Speter of a cheap(ish) sanity check. */ 808251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, 809251881Speter first_pair->src_abspath_or_url, NULL, 810251881Speter ctx, pool, pool)); 811251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 812251881Speter 813251881Speter /* Verify that sources and destinations are all at or under 814251881Speter REPOS_ROOT. While here, create a path_info struct for each 815251881Speter src/dst pair and initialize portions of it with normalized source 816251881Speter location information. */ 817251881Speter path_infos = apr_array_make(pool, copy_pairs->nelts, 818251881Speter sizeof(path_driver_info_t *)); 819251881Speter for (i = 0; i < copy_pairs->nelts; i++) 820251881Speter { 821251881Speter path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 822251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 823251881Speter svn_client__copy_pair_t *); 824251881Speter apr_hash_t *mergeinfo; 825251881Speter 826251881Speter /* Are the source and destination URLs at or under REPOS_ROOT? */ 827251881Speter if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url) 828251881Speter && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url))) 829251881Speter return svn_error_create 830251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 831251881Speter _("Source and destination URLs appear not to point to the " 832251881Speter "same repository.")); 833251881Speter 834251881Speter /* Run the history function to get the source's URL and revnum in the 835251881Speter operational revision. */ 836251881Speter SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 837251881Speter SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url, 838251881Speter &pair->src_revnum, 839251881Speter NULL, NULL, 840251881Speter ra_session, 841251881Speter pair->src_abspath_or_url, 842251881Speter &pair->src_peg_revision, 843251881Speter &pair->src_op_revision, NULL, 844251881Speter ctx, pool)); 845251881Speter 846251881Speter /* Go ahead and grab mergeinfo from the source, too. */ 847251881Speter SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool)); 848251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 849251881Speter &mergeinfo, ra_session, 850251881Speter pair->src_abspath_or_url, pair->src_revnum, 851251881Speter svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 852251881Speter if (mergeinfo) 853251881Speter SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool)); 854251881Speter 855251881Speter /* Plop an INFO structure onto our array thereof. */ 856251881Speter info->src_url = pair->src_abspath_or_url; 857251881Speter info->src_revnum = pair->src_revnum; 858251881Speter info->resurrection = FALSE; 859251881Speter APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info; 860251881Speter } 861251881Speter 862251881Speter /* If this is a move, we have to open our session to the longest 863251881Speter path common to all SRC_URLS and DST_URLS in the repository so we 864251881Speter can do existence checks on all paths, and so we can operate on 865251881Speter all paths in the case of a move. But if this is *not* a move, 866251881Speter then opening our session at the longest path common to sources 867251881Speter *and* destinations might be an optimization when the user is 868251881Speter authorized to access all that stuff, but could cause the 869251881Speter operation to fail altogether otherwise. See issue #3242. */ 870251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all, 871251881Speter pool)); 872251881Speter top_url = is_move ? top_url_all : top_url_dst; 873251881Speter 874251881Speter /* Check each src/dst pair for resurrection, and verify that TOP_URL 875251881Speter is anchored high enough to cover all the editor_t activities 876251881Speter required for this operation. */ 877251881Speter for (i = 0; i < copy_pairs->nelts; i++) 878251881Speter { 879251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 880251881Speter svn_client__copy_pair_t *); 881251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 882251881Speter path_driver_info_t *); 883251881Speter 884251881Speter /* Source and destination are the same? It's a resurrection. */ 885251881Speter if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0) 886251881Speter info->resurrection = TRUE; 887251881Speter 888251881Speter /* We need to add each dst_URL, and (in a move) we'll need to 889251881Speter delete each src_URL. Our selection of TOP_URL so far ensures 890251881Speter that all our destination URLs (and source URLs, for moves) 891251881Speter are at least as deep as TOP_URL, but we need to make sure 892251881Speter that TOP_URL is an *ancestor* of all our to-be-edited paths. 893251881Speter 894251881Speter Issue #683 is demonstrates this scenario. If you're 895251881Speter resurrecting a deleted item like this: 'svn cp -rN src_URL 896251881Speter dst_URL', then src_URL == dst_URL == top_url. In this 897251881Speter situation, we want to open an RA session to be at least the 898251881Speter *parent* of all three. */ 899251881Speter if ((strcmp(top_url, pair->dst_abspath_or_url) == 0) 900251881Speter && (strcmp(top_url, repos_root) != 0)) 901251881Speter { 902251881Speter top_url = svn_uri_dirname(top_url, pool); 903251881Speter } 904251881Speter if (is_move 905251881Speter && (strcmp(top_url, pair->src_abspath_or_url) == 0) 906251881Speter && (strcmp(top_url, repos_root) != 0)) 907251881Speter { 908251881Speter top_url = svn_uri_dirname(top_url, pool); 909251881Speter } 910251881Speter } 911251881Speter 912251881Speter /* Point the RA session to our current TOP_URL. */ 913251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 914251881Speter 915251881Speter /* If we're allowed to create nonexistent parent directories of our 916251881Speter destinations, then make a list in NEW_DIRS of the parent 917251881Speter directories of the destination that don't yet exist. */ 918251881Speter if (make_parents) 919251881Speter { 920251881Speter new_dirs = apr_array_make(pool, 0, sizeof(const char *)); 921251881Speter 922251881Speter /* If this is a move, TOP_URL is at least the common ancestor of 923251881Speter all the paths (sources and destinations) involved. Assuming 924251881Speter the sources exist (which is fair, because if they don't, this 925251881Speter whole operation will fail anyway), TOP_URL must also exist. 926251881Speter So it's the paths between TOP_URL and the destinations which 927251881Speter we have to check for existence. But here, we take advantage 928251881Speter of the knowledge of our caller. We know that if there are 929251881Speter multiple copy/move operations being requested, then the 930251881Speter destinations of the copies/moves will all be siblings of one 931251881Speter another. Therefore, we need only to check for the 932251881Speter nonexistent paths between TOP_URL and *one* of our 933251881Speter destinations to find nonexistent parents of all of them. */ 934251881Speter if (is_move) 935251881Speter { 936251881Speter /* Imagine a situation where the user tries to copy an 937251881Speter existing source directory to nonexistent directory with 938251881Speter --parents options specified: 939251881Speter 940251881Speter svn copy --parents URL/src URL/dst 941251881Speter 942251881Speter where src exists and dst does not. If the dirname of the 943251881Speter destination path is equal to TOP_URL, 944251881Speter do not try to add dst to the NEW_DIRS list since it 945251881Speter will be added to the commit items array later in this 946251881Speter function. */ 947251881Speter const char *dir = svn_uri_skip_ancestor( 948251881Speter top_url, 949251881Speter svn_uri_dirname(first_pair->dst_abspath_or_url, 950251881Speter pool), 951251881Speter pool); 952251881Speter if (dir && *dir) 953251881Speter SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool)); 954251881Speter } 955251881Speter /* If, however, this is *not* a move, TOP_URL only points to the 956251881Speter common ancestor of our destination path(s), or possibly one 957251881Speter level higher. We'll need to do an existence crawl toward the 958251881Speter root of the repository, starting with one of our destinations 959251881Speter (see "... take advantage of the knowledge of our caller ..." 960251881Speter above), and possibly adjusting TOP_URL as we go. */ 961251881Speter else 962251881Speter { 963251881Speter apr_array_header_t *new_urls = 964251881Speter apr_array_make(pool, 0, sizeof(const char *)); 965251881Speter SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool)); 966251881Speter 967251881Speter /* Convert absolute URLs into relpaths relative to TOP_URL. */ 968251881Speter for (i = 0; i < new_urls->nelts; i++) 969251881Speter { 970251881Speter const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *); 971251881Speter const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool); 972251881Speter 973251881Speter APR_ARRAY_PUSH(new_dirs, const char *) = dir; 974251881Speter } 975251881Speter } 976251881Speter } 977251881Speter 978251881Speter /* For each src/dst pair, check to see if that SRC_URL is a child of 979251881Speter the DST_URL (excepting the case where DST_URL is the repo root). 980251881Speter If it is, and the parent of DST_URL is the current TOP_URL, then we 981251881Speter need to reparent the session one directory higher, the parent of 982251881Speter the DST_URL. */ 983251881Speter for (i = 0; i < copy_pairs->nelts; i++) 984251881Speter { 985251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 986251881Speter svn_client__copy_pair_t *); 987251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 988251881Speter path_driver_info_t *); 989251881Speter const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url, 990251881Speter pair->src_abspath_or_url, 991251881Speter pool); 992251881Speter 993251881Speter if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0) 994251881Speter && (relpath != NULL && *relpath != '\0')) 995251881Speter { 996251881Speter info->resurrection = TRUE; 997251881Speter top_url = svn_uri_dirname(top_url, pool); 998251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_url, pool)); 999251881Speter } 1000251881Speter } 1001251881Speter 1002251881Speter /* Get the portions of the SRC and DST URLs that are relative to 1003251881Speter TOP_URL (URI-decoding them while we're at it), verify that the 1004251881Speter source exists and the proposed destination does not, and toss 1005251881Speter what we've learned into the INFO array. (For copies -- that is, 1006251881Speter non-moves -- the relative source URL NULL because it isn't a 1007251881Speter child of the TOP_URL at all. That's okay, we'll deal with 1008251881Speter it.) */ 1009251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1010251881Speter { 1011251881Speter svn_client__copy_pair_t *pair = 1012251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1013251881Speter path_driver_info_t *info = 1014251881Speter APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); 1015251881Speter svn_node_kind_t dst_kind; 1016251881Speter const char *src_rel, *dst_rel; 1017251881Speter 1018251881Speter src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool); 1019251881Speter if (src_rel) 1020251881Speter { 1021251881Speter SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1022251881Speter &info->src_kind, pool)); 1023251881Speter } 1024251881Speter else 1025251881Speter { 1026251881Speter const char *old_url; 1027251881Speter 1028251881Speter src_rel = NULL; 1029251881Speter SVN_ERR_ASSERT(! is_move); 1030251881Speter 1031251881Speter SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, 1032251881Speter pair->src_abspath_or_url, 1033251881Speter pool)); 1034251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum, 1035251881Speter &info->src_kind, pool)); 1036251881Speter SVN_ERR(svn_ra_reparent(ra_session, old_url, pool)); 1037251881Speter } 1038251881Speter if (info->src_kind == svn_node_none) 1039251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1040251881Speter _("Path '%s' does not exist in revision %ld"), 1041251881Speter pair->src_abspath_or_url, pair->src_revnum); 1042251881Speter 1043251881Speter /* Figure out the basename that will result from this operation, 1044251881Speter and ensure that we aren't trying to overwrite existing paths. */ 1045251881Speter dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool); 1046251881Speter SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1047251881Speter &dst_kind, pool)); 1048251881Speter if (dst_kind != svn_node_none) 1049251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1050251881Speter _("Path '%s' already exists"), dst_rel); 1051251881Speter 1052251881Speter /* More info for our INFO structure. */ 1053251881Speter info->src_path = src_rel; 1054251881Speter info->dst_path = dst_rel; 1055251881Speter 1056251881Speter svn_hash_sets(action_hash, info->dst_path, info); 1057251881Speter if (is_move && (! info->resurrection)) 1058251881Speter svn_hash_sets(action_hash, info->src_path, info); 1059251881Speter } 1060251881Speter 1061251881Speter if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1062251881Speter { 1063251881Speter /* Produce a list of new paths to add, and provide it to the 1064251881Speter mechanism used to acquire a log message. */ 1065251881Speter svn_client_commit_item3_t *item; 1066251881Speter const char *tmp_file; 1067251881Speter apr_array_header_t *commit_items 1068251881Speter = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item)); 1069251881Speter 1070251881Speter /* Add any intermediate directories to the message */ 1071251881Speter if (make_parents) 1072251881Speter { 1073251881Speter for (i = 0; i < new_dirs->nelts; i++) 1074251881Speter { 1075251881Speter const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1076251881Speter 1077251881Speter item = svn_client_commit_item3_create(pool); 1078251881Speter item->url = svn_path_url_add_component2(top_url, relpath, pool); 1079251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1080251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1081251881Speter } 1082251881Speter } 1083251881Speter 1084251881Speter for (i = 0; i < path_infos->nelts; i++) 1085251881Speter { 1086251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1087251881Speter path_driver_info_t *); 1088251881Speter 1089251881Speter item = svn_client_commit_item3_create(pool); 1090251881Speter item->url = svn_path_url_add_component2(top_url, info->dst_path, 1091251881Speter pool); 1092251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1093251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1094251881Speter 1095251881Speter if (is_move && (! info->resurrection)) 1096251881Speter { 1097251881Speter item = apr_pcalloc(pool, sizeof(*item)); 1098251881Speter item->url = svn_path_url_add_component2(top_url, info->src_path, 1099251881Speter pool); 1100251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 1101251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1102251881Speter } 1103251881Speter } 1104251881Speter 1105251881Speter SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1106251881Speter ctx, pool)); 1107251881Speter if (! message) 1108251881Speter return SVN_NO_ERROR; 1109251881Speter } 1110251881Speter else 1111251881Speter message = ""; 1112251881Speter 1113251881Speter /* Setup our PATHS for the path-based editor drive. */ 1114251881Speter /* First any intermediate directories. */ 1115251881Speter if (make_parents) 1116251881Speter { 1117251881Speter for (i = 0; i < new_dirs->nelts; i++) 1118251881Speter { 1119251881Speter const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *); 1120251881Speter path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info)); 1121251881Speter 1122251881Speter info->dst_path = relpath; 1123251881Speter info->dir_add = TRUE; 1124251881Speter 1125251881Speter APR_ARRAY_PUSH(paths, const char *) = relpath; 1126251881Speter svn_hash_sets(action_hash, relpath, info); 1127251881Speter } 1128251881Speter } 1129251881Speter 1130251881Speter /* Then our copy destinations and move sources (if any). */ 1131251881Speter for (i = 0; i < path_infos->nelts; i++) 1132251881Speter { 1133251881Speter path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i, 1134251881Speter path_driver_info_t *); 1135251881Speter 1136251881Speter APR_ARRAY_PUSH(paths, const char *) = info->dst_path; 1137251881Speter if (is_move && (! info->resurrection)) 1138251881Speter APR_ARRAY_PUSH(paths, const char *) = info->src_path; 1139251881Speter } 1140251881Speter 1141251881Speter SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1142251881Speter message, ctx, pool)); 1143251881Speter 1144251881Speter /* Fetch RA commit editor. */ 1145251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1146251881Speter svn_client__get_shim_callbacks(ctx->wc_ctx, 1147251881Speter NULL, pool))); 1148251881Speter SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1149251881Speter commit_revprops, 1150251881Speter commit_callback, 1151251881Speter commit_baton, 1152251881Speter NULL, TRUE, /* No lock tokens */ 1153251881Speter pool)); 1154251881Speter 1155251881Speter /* Setup the callback baton. */ 1156251881Speter cb_baton.editor = editor; 1157251881Speter cb_baton.edit_baton = edit_baton; 1158251881Speter cb_baton.action_hash = action_hash; 1159251881Speter cb_baton.is_move = is_move; 1160251881Speter 1161251881Speter /* Call the path-based editor driver. */ 1162251881Speter err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE, 1163251881Speter path_driver_cb_func, &cb_baton, pool); 1164251881Speter if (err) 1165251881Speter { 1166251881Speter /* At least try to abort the edit (and fs txn) before throwing err. */ 1167251881Speter return svn_error_compose_create( 1168251881Speter err, 1169251881Speter editor->abort_edit(edit_baton, pool)); 1170251881Speter } 1171251881Speter 1172251881Speter /* Close the edit. */ 1173251881Speter return svn_error_trace(editor->close_edit(edit_baton, pool)); 1174251881Speter} 1175251881Speter 1176251881Speter/* Baton for check_url_kind */ 1177251881Speterstruct check_url_kind_baton 1178251881Speter{ 1179251881Speter svn_ra_session_t *session; 1180251881Speter const char *repos_root_url; 1181251881Speter svn_boolean_t should_reparent; 1182251881Speter}; 1183251881Speter 1184251881Speter/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */ 1185251881Speterstatic svn_error_t * 1186251881Spetercheck_url_kind(void *baton, 1187251881Speter svn_node_kind_t *kind, 1188251881Speter const char *url, 1189251881Speter svn_revnum_t revision, 1190251881Speter apr_pool_t *scratch_pool) 1191251881Speter{ 1192251881Speter struct check_url_kind_baton *cukb = baton; 1193251881Speter 1194251881Speter /* If we don't have a session or can't use the session, get one */ 1195251881Speter if (!svn_uri__is_ancestor(cukb->repos_root_url, url)) 1196251881Speter *kind = svn_node_none; 1197251881Speter else 1198251881Speter { 1199251881Speter cukb->should_reparent = TRUE; 1200251881Speter 1201251881Speter SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool)); 1202251881Speter 1203251881Speter SVN_ERR(svn_ra_check_path(cukb->session, "", revision, 1204251881Speter kind, scratch_pool)); 1205251881Speter } 1206251881Speter 1207251881Speter return SVN_NO_ERROR; 1208251881Speter} 1209251881Speter 1210251881Speter/* ### Copy ... 1211251881Speter * COMMIT_INFO_P is ... 1212251881Speter * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath 1213251881Speter * and each 'dst_abspath_or_url' is a URL. 1214251881Speter * MAKE_PARENTS is ... 1215251881Speter * REVPROP_TABLE is ... 1216251881Speter * CTX is ... */ 1217251881Speterstatic svn_error_t * 1218251881Speterwc_to_repos_copy(const apr_array_header_t *copy_pairs, 1219251881Speter svn_boolean_t make_parents, 1220251881Speter const apr_hash_t *revprop_table, 1221251881Speter svn_commit_callback2_t commit_callback, 1222251881Speter void *commit_baton, 1223251881Speter svn_client_ctx_t *ctx, 1224251881Speter apr_pool_t *scratch_pool) 1225251881Speter{ 1226251881Speter const char *message; 1227251881Speter const char *top_src_path, *top_dst_url; 1228251881Speter struct check_url_kind_baton cukb; 1229251881Speter const char *top_src_abspath; 1230251881Speter svn_ra_session_t *ra_session; 1231251881Speter const svn_delta_editor_t *editor; 1232251881Speter apr_hash_t *relpath_map = NULL; 1233251881Speter void *edit_baton; 1234251881Speter svn_client__committables_t *committables; 1235251881Speter apr_array_header_t *commit_items; 1236251881Speter apr_pool_t *iterpool; 1237251881Speter apr_array_header_t *new_dirs = NULL; 1238251881Speter apr_hash_t *commit_revprops; 1239251881Speter svn_client__copy_pair_t *first_pair; 1240251881Speter apr_pool_t *session_pool = svn_pool_create(scratch_pool); 1241251881Speter int i; 1242251881Speter 1243251881Speter /* Find the common root of all the source paths */ 1244251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, 1245251881Speter scratch_pool)); 1246251881Speter 1247251881Speter /* Do we need to lock the working copy? 1.6 didn't take a write 1248251881Speter lock, but what happens if the working copy changes during the copy 1249251881Speter operation? */ 1250251881Speter 1251251881Speter iterpool = svn_pool_create(scratch_pool); 1252251881Speter 1253251881Speter /* Determine the longest common ancestor for the destinations, and open an RA 1254251881Speter session to that location. */ 1255251881Speter /* ### But why start by getting the _parent_ of the first one? */ 1256251881Speter /* --- That works because multiple destinations always point to the same 1257251881Speter * directory. I'm rather wondering why we need to find a common 1258251881Speter * destination parent here at all, instead of simply getting 1259251881Speter * top_dst_url from get_copy_pair_ancestors() above? 1260251881Speter * It looks like the entire block of code hanging off this comment 1261251881Speter * is redundant. */ 1262251881Speter first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); 1263251881Speter top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool); 1264251881Speter for (i = 1; i < copy_pairs->nelts; i++) 1265251881Speter { 1266251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1267251881Speter svn_client__copy_pair_t *); 1268251881Speter top_dst_url = svn_uri_get_longest_ancestor(top_dst_url, 1269251881Speter pair->dst_abspath_or_url, 1270251881Speter scratch_pool); 1271251881Speter } 1272251881Speter 1273251881Speter SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool)); 1274251881Speter 1275251881Speter /* Open a session to help while determining the exact targets */ 1276251881Speter SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 1277251881Speter top_src_abspath, NULL, 1278251881Speter FALSE /* write_dav_props */, 1279251881Speter TRUE /* read_dav_props */, 1280251881Speter ctx, 1281251881Speter session_pool, session_pool)); 1282251881Speter 1283251881Speter /* If requested, determine the nearest existing parent of the destination, 1284251881Speter and reparent the ra session there. */ 1285251881Speter if (make_parents) 1286251881Speter { 1287251881Speter new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *)); 1288251881Speter SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs, 1289251881Speter scratch_pool)); 1290251881Speter } 1291251881Speter 1292251881Speter /* Figure out the basename that will result from each copy and check to make 1293251881Speter sure it doesn't exist already. */ 1294251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1295251881Speter { 1296251881Speter svn_node_kind_t dst_kind; 1297251881Speter const char *dst_rel; 1298251881Speter svn_client__copy_pair_t *pair = 1299251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 1300251881Speter 1301251881Speter svn_pool_clear(iterpool); 1302251881Speter dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url, 1303251881Speter iterpool); 1304251881Speter SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM, 1305251881Speter &dst_kind, iterpool)); 1306251881Speter if (dst_kind != svn_node_none) 1307251881Speter { 1308251881Speter return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 1309251881Speter _("Path '%s' already exists"), 1310251881Speter pair->dst_abspath_or_url); 1311251881Speter } 1312251881Speter } 1313251881Speter 1314251881Speter if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1315251881Speter { 1316251881Speter /* Produce a list of new paths to add, and provide it to the 1317251881Speter mechanism used to acquire a log message. */ 1318251881Speter svn_client_commit_item3_t *item; 1319251881Speter const char *tmp_file; 1320251881Speter commit_items = apr_array_make(scratch_pool, copy_pairs->nelts, 1321251881Speter sizeof(item)); 1322251881Speter 1323251881Speter /* Add any intermediate directories to the message */ 1324251881Speter if (make_parents) 1325251881Speter { 1326251881Speter for (i = 0; i < new_dirs->nelts; i++) 1327251881Speter { 1328251881Speter const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 1329251881Speter 1330251881Speter item = svn_client_commit_item3_create(scratch_pool); 1331251881Speter item->url = url; 1332251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1333251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1334251881Speter } 1335251881Speter } 1336251881Speter 1337251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1338251881Speter { 1339251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1340251881Speter svn_client__copy_pair_t *); 1341251881Speter 1342251881Speter item = svn_client_commit_item3_create(scratch_pool); 1343251881Speter item->url = pair->dst_abspath_or_url; 1344251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1345251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1346251881Speter } 1347251881Speter 1348251881Speter SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 1349251881Speter ctx, scratch_pool)); 1350251881Speter if (! message) 1351251881Speter { 1352251881Speter svn_pool_destroy(iterpool); 1353251881Speter svn_pool_destroy(session_pool); 1354251881Speter return SVN_NO_ERROR; 1355251881Speter } 1356251881Speter } 1357251881Speter else 1358251881Speter message = ""; 1359251881Speter 1360251881Speter cukb.session = ra_session; 1361251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool)); 1362251881Speter cukb.should_reparent = FALSE; 1363251881Speter 1364251881Speter /* Crawl the working copy for commit items. */ 1365251881Speter /* ### TODO: Pass check_url_func for issue #3314 handling */ 1366251881Speter SVN_ERR(svn_client__get_copy_committables(&committables, 1367251881Speter copy_pairs, 1368251881Speter check_url_kind, &cukb, 1369251881Speter ctx, scratch_pool, iterpool)); 1370251881Speter 1371251881Speter /* The committables are keyed by the repository root */ 1372251881Speter commit_items = svn_hash_gets(committables->by_repository, 1373251881Speter cukb.repos_root_url); 1374251881Speter SVN_ERR_ASSERT(commit_items != NULL); 1375251881Speter 1376251881Speter if (cukb.should_reparent) 1377251881Speter SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool)); 1378251881Speter 1379251881Speter /* If we are creating intermediate directories, tack them onto the list 1380251881Speter of committables. */ 1381251881Speter if (make_parents) 1382251881Speter { 1383251881Speter for (i = 0; i < new_dirs->nelts; i++) 1384251881Speter { 1385251881Speter const char *url = APR_ARRAY_IDX(new_dirs, i, const char *); 1386251881Speter svn_client_commit_item3_t *item; 1387251881Speter 1388251881Speter item = svn_client_commit_item3_create(scratch_pool); 1389251881Speter item->url = url; 1390251881Speter item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1391251881Speter item->incoming_prop_changes = apr_array_make(scratch_pool, 1, 1392251881Speter sizeof(svn_prop_t *)); 1393251881Speter APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1394251881Speter } 1395251881Speter } 1396251881Speter 1397251881Speter /* ### TODO: This extra loop would be unnecessary if this code lived 1398251881Speter ### in svn_client__get_copy_committables(), which is incidentally 1399251881Speter ### only used above (so should really be in this source file). */ 1400251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1401251881Speter { 1402251881Speter apr_hash_t *mergeinfo, *wc_mergeinfo; 1403251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1404251881Speter svn_client__copy_pair_t *); 1405251881Speter svn_client_commit_item3_t *item = 1406251881Speter APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 1407251881Speter svn_client__pathrev_t *src_origin; 1408251881Speter 1409251881Speter svn_pool_clear(iterpool); 1410251881Speter 1411251881Speter SVN_ERR(svn_client__wc_node_get_origin(&src_origin, 1412251881Speter pair->src_abspath_or_url, 1413251881Speter ctx, iterpool, iterpool)); 1414251881Speter 1415251881Speter /* Set the mergeinfo for the destination to the combined merge 1416251881Speter info known to the WC and the repository. */ 1417251881Speter item->outgoing_prop_changes = apr_array_make(scratch_pool, 1, 1418251881Speter sizeof(svn_prop_t *)); 1419251881Speter /* Repository mergeinfo (or NULL if it's locally added)... */ 1420251881Speter if (src_origin) 1421251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 1422251881Speter &mergeinfo, ra_session, src_origin->url, src_origin->rev, 1423251881Speter svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool)); 1424251881Speter else 1425251881Speter mergeinfo = NULL; 1426251881Speter /* ... and WC mergeinfo. */ 1427251881Speter SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx, 1428251881Speter pair->src_abspath_or_url, 1429251881Speter iterpool, iterpool)); 1430251881Speter if (wc_mergeinfo && mergeinfo) 1431251881Speter SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool, 1432251881Speter iterpool)); 1433251881Speter else if (! mergeinfo) 1434251881Speter mergeinfo = wc_mergeinfo; 1435251881Speter if (mergeinfo) 1436251881Speter { 1437251881Speter /* Push a mergeinfo prop representing MERGEINFO onto the 1438251881Speter * OUTGOING_PROP_CHANGES array. */ 1439251881Speter 1440251881Speter svn_prop_t *mergeinfo_prop 1441251881Speter = apr_palloc(item->outgoing_prop_changes->pool, 1442251881Speter sizeof(svn_prop_t)); 1443251881Speter svn_string_t *prop_value; 1444251881Speter 1445251881Speter SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo, 1446251881Speter item->outgoing_prop_changes->pool)); 1447251881Speter 1448251881Speter mergeinfo_prop->name = SVN_PROP_MERGEINFO; 1449251881Speter mergeinfo_prop->value = prop_value; 1450251881Speter APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) 1451251881Speter = mergeinfo_prop; 1452251881Speter } 1453251881Speter } 1454251881Speter 1455251881Speter /* Sort and condense our COMMIT_ITEMS. */ 1456251881Speter SVN_ERR(svn_client__condense_commit_items(&top_dst_url, 1457251881Speter commit_items, scratch_pool)); 1458251881Speter 1459251881Speter#ifdef ENABLE_EV2_SHIMS 1460251881Speter if (commit_items) 1461251881Speter { 1462251881Speter relpath_map = apr_hash_make(pool); 1463251881Speter for (i = 0; i < commit_items->nelts; i++) 1464251881Speter { 1465251881Speter svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, 1466251881Speter svn_client_commit_item3_t *); 1467251881Speter const char *relpath; 1468251881Speter 1469251881Speter if (!item->path) 1470251881Speter continue; 1471251881Speter 1472251881Speter svn_pool_clear(iterpool); 1473251881Speter SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL, 1474251881Speter ctx->wc_ctx, item->path, FALSE, 1475251881Speter scratch_pool, iterpool)); 1476251881Speter if (relpath) 1477251881Speter svn_hash_sets(relpath_map, relpath, item->path); 1478251881Speter } 1479251881Speter } 1480251881Speter#endif 1481251881Speter 1482251881Speter /* Close the initial session, to reopen a new session with commit handling */ 1483251881Speter svn_pool_clear(session_pool); 1484251881Speter 1485251881Speter /* Open a new RA session to DST_URL. */ 1486251881Speter SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url, 1487251881Speter NULL, commit_items, 1488251881Speter FALSE, FALSE, ctx, 1489251881Speter session_pool, session_pool)); 1490251881Speter 1491251881Speter SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1492251881Speter message, ctx, session_pool)); 1493251881Speter 1494251881Speter /* Fetch RA commit editor. */ 1495251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1496251881Speter svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map, 1497251881Speter session_pool))); 1498251881Speter SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1499251881Speter commit_revprops, 1500251881Speter commit_callback, 1501251881Speter commit_baton, NULL, 1502251881Speter TRUE, /* No lock tokens */ 1503251881Speter session_pool)); 1504251881Speter 1505251881Speter /* Perform the commit. */ 1506251881Speter SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, 1507251881Speter editor, edit_baton, 1508251881Speter 0, /* ### any notify_path_offset needed? */ 1509251881Speter NULL, ctx, session_pool, session_pool), 1510251881Speter _("Commit failed (details follow):")); 1511251881Speter 1512251881Speter svn_pool_destroy(iterpool); 1513251881Speter svn_pool_destroy(session_pool); 1514251881Speter 1515251881Speter return SVN_NO_ERROR; 1516251881Speter} 1517251881Speter 1518251881Speter/* A baton for notification_adjust_func(). */ 1519251881Speterstruct notification_adjust_baton 1520251881Speter{ 1521251881Speter svn_wc_notify_func2_t inner_func; 1522251881Speter void *inner_baton; 1523251881Speter const char *checkout_abspath; 1524251881Speter const char *final_abspath; 1525251881Speter}; 1526251881Speter 1527251881Speter/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose 1528251881Speter * baton is BATON->inner_baton) and adjusts the notification paths that 1529251881Speter * start with BATON->checkout_abspath to start instead with 1530251881Speter * BATON->final_abspath. */ 1531251881Speterstatic void 1532251881Speternotification_adjust_func(void *baton, 1533251881Speter const svn_wc_notify_t *notify, 1534251881Speter apr_pool_t *pool) 1535251881Speter{ 1536251881Speter struct notification_adjust_baton *nb = baton; 1537251881Speter svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); 1538251881Speter const char *relpath; 1539251881Speter 1540251881Speter relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); 1541251881Speter inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); 1542251881Speter 1543251881Speter if (nb->inner_func) 1544251881Speter nb->inner_func(nb->inner_baton, inner_notify, pool); 1545251881Speter} 1546251881Speter 1547251881Speter/* Peform each individual copy operation for a repos -> wc copy. A 1548251881Speter helper for repos_to_wc_copy(). 1549251881Speter 1550251881Speter Resolve PAIR->src_revnum to a real revision number if it isn't already. */ 1551251881Speterstatic svn_error_t * 1552251881Speterrepos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, 1553251881Speter svn_client__copy_pair_t *pair, 1554251881Speter svn_boolean_t same_repositories, 1555251881Speter svn_boolean_t ignore_externals, 1556251881Speter svn_ra_session_t *ra_session, 1557251881Speter svn_client_ctx_t *ctx, 1558251881Speter apr_pool_t *pool) 1559251881Speter{ 1560251881Speter apr_hash_t *src_mergeinfo; 1561251881Speter const char *dst_abspath = pair->dst_abspath_or_url; 1562251881Speter 1563251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); 1564251881Speter 1565251881Speter if (!same_repositories && ctx->notify_func2) 1566251881Speter { 1567251881Speter svn_wc_notify_t *notify; 1568251881Speter notify = svn_wc_create_notify_url( 1569251881Speter pair->src_abspath_or_url, 1570251881Speter svn_wc_notify_foreign_copy_begin, 1571251881Speter pool); 1572251881Speter notify->kind = pair->src_kind; 1573251881Speter ctx->notify_func2(ctx->notify_baton2, notify, pool); 1574251881Speter 1575251881Speter /* Allow a theoretical cancel to get through. */ 1576251881Speter if (ctx->cancel_func) 1577251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1578251881Speter } 1579251881Speter 1580251881Speter if (pair->src_kind == svn_node_dir) 1581251881Speter { 1582251881Speter if (same_repositories) 1583251881Speter { 1584251881Speter svn_boolean_t sleep_needed = FALSE; 1585251881Speter const char *tmpdir_abspath, *tmp_abspath; 1586251881Speter 1587251881Speter /* Find a temporary location in which to check out the copy source. */ 1588251881Speter SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, 1589251881Speter pool, pool)); 1590251881Speter 1591251881Speter SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, 1592251881Speter svn_io_file_del_on_close, pool, pool)); 1593251881Speter 1594251881Speter /* Make a new checkout of the requested source. While doing so, 1595251881Speter * resolve pair->src_revnum to an actual revision number in case it 1596251881Speter * was until now 'invalid' meaning 'head'. Ask this function not to 1597251881Speter * sleep for timestamps, by passing a sleep_needed output param. 1598251881Speter * Send notifications for all nodes except the root node, and adjust 1599251881Speter * them to refer to the destination rather than this temporary path. */ 1600251881Speter { 1601251881Speter svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; 1602251881Speter void *old_notify_baton2 = ctx->notify_baton2; 1603251881Speter struct notification_adjust_baton nb; 1604251881Speter svn_error_t *err; 1605251881Speter 1606251881Speter nb.inner_func = ctx->notify_func2; 1607251881Speter nb.inner_baton = ctx->notify_baton2; 1608251881Speter nb.checkout_abspath = tmp_abspath; 1609251881Speter nb.final_abspath = dst_abspath; 1610251881Speter ctx->notify_func2 = notification_adjust_func; 1611251881Speter ctx->notify_baton2 = &nb; 1612251881Speter 1613251881Speter err = svn_client__checkout_internal(&pair->src_revnum, 1614251881Speter pair->src_original, 1615251881Speter tmp_abspath, 1616251881Speter &pair->src_peg_revision, 1617251881Speter &pair->src_op_revision, 1618251881Speter svn_depth_infinity, 1619251881Speter ignore_externals, FALSE, 1620251881Speter &sleep_needed, ctx, pool); 1621251881Speter 1622251881Speter ctx->notify_func2 = old_notify_func2; 1623251881Speter ctx->notify_baton2 = old_notify_baton2; 1624251881Speter 1625251881Speter SVN_ERR(err); 1626251881Speter } 1627251881Speter 1628251881Speter *timestamp_sleep = TRUE; 1629251881Speter 1630251881Speter /* Schedule dst_path for addition in parent, with copy history. 1631251881Speter Don't send any notification here. 1632251881Speter Then remove the temporary checkout's .svn dir in preparation for 1633251881Speter moving the rest of it into the final destination. */ 1634251881Speter SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, 1635251881Speter TRUE /* metadata_only */, 1636251881Speter ctx->cancel_func, ctx->cancel_baton, 1637251881Speter NULL, NULL, pool)); 1638251881Speter SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, 1639251881Speter FALSE, pool, pool)); 1640251881Speter SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, 1641251881Speter tmp_abspath, 1642251881Speter FALSE, FALSE, 1643251881Speter ctx->cancel_func, 1644251881Speter ctx->cancel_baton, 1645251881Speter pool)); 1646251881Speter 1647251881Speter /* Move the temporary disk tree into place. */ 1648251881Speter SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool)); 1649251881Speter } 1650251881Speter else 1651251881Speter { 1652251881Speter *timestamp_sleep = TRUE; 1653251881Speter 1654251881Speter SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url, 1655251881Speter dst_abspath, 1656251881Speter &pair->src_peg_revision, 1657251881Speter &pair->src_op_revision, 1658251881Speter svn_depth_infinity, 1659251881Speter FALSE /* make_parents */, 1660251881Speter TRUE /* already_locked */, 1661251881Speter ctx, pool)); 1662251881Speter 1663251881Speter return SVN_NO_ERROR; 1664251881Speter } 1665251881Speter } /* end directory case */ 1666251881Speter 1667251881Speter else if (pair->src_kind == svn_node_file) 1668251881Speter { 1669251881Speter apr_hash_t *new_props; 1670251881Speter const char *src_rel; 1671251881Speter svn_stream_t *new_base_contents = svn_stream_buffered(pool); 1672251881Speter 1673251881Speter SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 1674251881Speter pair->src_abspath_or_url, 1675251881Speter pool)); 1676251881Speter /* Fetch the file content. While doing so, resolve pair->src_revnum 1677251881Speter * to an actual revision number if it's 'invalid' meaning 'head'. */ 1678251881Speter SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, 1679251881Speter new_base_contents, 1680251881Speter &pair->src_revnum, &new_props, pool)); 1681251881Speter 1682251881Speter if (new_props && ! same_repositories) 1683251881Speter svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); 1684251881Speter 1685251881Speter *timestamp_sleep = TRUE; 1686251881Speter 1687251881Speter SVN_ERR(svn_wc_add_repos_file4( 1688251881Speter ctx->wc_ctx, dst_abspath, 1689251881Speter new_base_contents, NULL, new_props, NULL, 1690251881Speter same_repositories ? pair->src_abspath_or_url : NULL, 1691251881Speter same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, 1692251881Speter ctx->cancel_func, ctx->cancel_baton, 1693251881Speter pool)); 1694251881Speter } 1695251881Speter 1696251881Speter /* Record the implied mergeinfo (before the notification callback 1697251881Speter is invoked for the root node). */ 1698251881Speter SVN_ERR(svn_client__get_repos_mergeinfo( 1699251881Speter &src_mergeinfo, ra_session, 1700251881Speter pair->src_abspath_or_url, pair->src_revnum, 1701251881Speter svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); 1702251881Speter SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); 1703251881Speter 1704251881Speter /* Do our own notification for the root node, even if we could possibly 1705251881Speter have delegated it. See also issue #1552. 1706251881Speter 1707251881Speter ### Maybe this notification should mention the mergeinfo change. */ 1708251881Speter if (ctx->notify_func2) 1709251881Speter { 1710251881Speter svn_wc_notify_t *notify = svn_wc_create_notify( 1711251881Speter dst_abspath, svn_wc_notify_add, pool); 1712251881Speter notify->kind = pair->src_kind; 1713251881Speter (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1714251881Speter } 1715251881Speter 1716251881Speter return SVN_NO_ERROR; 1717251881Speter} 1718251881Speter 1719251881Speterstatic svn_error_t * 1720251881Speterrepos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, 1721251881Speter const apr_array_header_t *copy_pairs, 1722251881Speter const char *top_dst_path, 1723251881Speter svn_boolean_t ignore_externals, 1724251881Speter svn_ra_session_t *ra_session, 1725251881Speter svn_client_ctx_t *ctx, 1726251881Speter apr_pool_t *scratch_pool) 1727251881Speter{ 1728251881Speter int i; 1729251881Speter svn_boolean_t same_repositories; 1730251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1731251881Speter 1732251881Speter /* We've already checked for physical obstruction by a working file. 1733251881Speter But there could also be logical obstruction by an entry whose 1734251881Speter working file happens to be missing.*/ 1735253734Speter SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, 1736253734Speter ctx, scratch_pool, iterpool)); 1737251881Speter 1738251881Speter /* Decide whether the two repositories are the same or not. */ 1739251881Speter { 1740251881Speter svn_error_t *src_err, *dst_err; 1741251881Speter const char *parent; 1742251881Speter const char *parent_abspath; 1743251881Speter const char *src_uuid, *dst_uuid; 1744251881Speter 1745251881Speter /* Get the repository uuid of SRC_URL */ 1746251881Speter src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool); 1747251881Speter if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 1748251881Speter return svn_error_trace(src_err); 1749251881Speter 1750251881Speter /* Get repository uuid of dst's parent directory, since dst may 1751251881Speter not exist. ### TODO: we should probably walk up the wc here, 1752251881Speter in case the parent dir has an imaginary URL. */ 1753251881Speter if (copy_pairs->nelts == 1) 1754251881Speter parent = svn_dirent_dirname(top_dst_path, scratch_pool); 1755251881Speter else 1756251881Speter parent = top_dst_path; 1757251881Speter 1758251881Speter SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool)); 1759251881Speter dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, 1760251881Speter parent_abspath, ctx, 1761251881Speter iterpool, iterpool); 1762251881Speter if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID) 1763251881Speter return dst_err; 1764251881Speter 1765251881Speter /* If either of the UUIDs are nonexistent, then at least one of 1766251881Speter the repositories must be very old. Rather than punish the 1767251881Speter user, just assume the repositories are different, so no 1768251881Speter copy-history is attempted. */ 1769251881Speter if (src_err || dst_err || (! src_uuid) || (! dst_uuid)) 1770251881Speter same_repositories = FALSE; 1771251881Speter else 1772251881Speter same_repositories = (strcmp(src_uuid, dst_uuid) == 0); 1773251881Speter } 1774251881Speter 1775251881Speter /* Perform the move for each of the copy_pairs. */ 1776251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1777251881Speter { 1778251881Speter /* Check for cancellation */ 1779251881Speter if (ctx->cancel_func) 1780251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1781251881Speter 1782251881Speter svn_pool_clear(iterpool); 1783251881Speter 1784251881Speter SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, 1785251881Speter APR_ARRAY_IDX(copy_pairs, i, 1786251881Speter svn_client__copy_pair_t *), 1787251881Speter same_repositories, 1788251881Speter ignore_externals, 1789251881Speter ra_session, ctx, iterpool)); 1790251881Speter } 1791251881Speter svn_pool_destroy(iterpool); 1792251881Speter 1793251881Speter return SVN_NO_ERROR; 1794251881Speter} 1795251881Speter 1796251881Speterstatic svn_error_t * 1797251881Speterrepos_to_wc_copy(svn_boolean_t *timestamp_sleep, 1798251881Speter const apr_array_header_t *copy_pairs, 1799251881Speter svn_boolean_t make_parents, 1800251881Speter svn_boolean_t ignore_externals, 1801251881Speter svn_client_ctx_t *ctx, 1802251881Speter apr_pool_t *pool) 1803251881Speter{ 1804251881Speter svn_ra_session_t *ra_session; 1805251881Speter const char *top_src_url, *top_dst_path; 1806251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1807251881Speter const char *lock_abspath; 1808251881Speter int i; 1809251881Speter 1810251881Speter /* Get the real path for the source, based upon its peg revision. */ 1811251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1812251881Speter { 1813251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1814251881Speter svn_client__copy_pair_t *); 1815251881Speter const char *src; 1816251881Speter 1817251881Speter svn_pool_clear(iterpool); 1818251881Speter 1819251881Speter SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL, 1820251881Speter NULL, 1821251881Speter pair->src_abspath_or_url, 1822251881Speter &pair->src_peg_revision, 1823251881Speter &pair->src_op_revision, NULL, 1824251881Speter ctx, iterpool)); 1825251881Speter 1826251881Speter pair->src_original = pair->src_abspath_or_url; 1827251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, src); 1828251881Speter } 1829251881Speter 1830251881Speter SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, 1831251881Speter NULL, pool)); 1832251881Speter lock_abspath = top_dst_path; 1833251881Speter if (copy_pairs->nelts == 1) 1834251881Speter { 1835251881Speter top_src_url = svn_uri_dirname(top_src_url, pool); 1836251881Speter lock_abspath = svn_dirent_dirname(top_dst_path, pool); 1837251881Speter } 1838251881Speter 1839251881Speter /* Open a repository session to the longest common src ancestor. We do not 1840251881Speter (yet) have a working copy, so we don't have a corresponding path and 1841251881Speter tempfiles cannot go into the admin area. */ 1842251881Speter SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath, 1843251881Speter ctx, pool, pool)); 1844251881Speter 1845251881Speter /* Get the correct src path for the peg revision used, and verify that we 1846251881Speter aren't overwriting an existing path. */ 1847251881Speter for (i = 0; i < copy_pairs->nelts; i++) 1848251881Speter { 1849251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 1850251881Speter svn_client__copy_pair_t *); 1851251881Speter svn_node_kind_t dst_parent_kind, dst_kind; 1852251881Speter const char *dst_parent; 1853251881Speter const char *src_rel; 1854251881Speter 1855251881Speter svn_pool_clear(iterpool); 1856251881Speter 1857251881Speter /* Next, make sure that the path exists in the repository. */ 1858251881Speter SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, 1859251881Speter pair->src_abspath_or_url, 1860251881Speter iterpool)); 1861251881Speter SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum, 1862251881Speter &pair->src_kind, pool)); 1863251881Speter if (pair->src_kind == svn_node_none) 1864251881Speter { 1865251881Speter if (SVN_IS_VALID_REVNUM(pair->src_revnum)) 1866251881Speter return svn_error_createf 1867251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 1868251881Speter _("Path '%s' not found in revision %ld"), 1869251881Speter pair->src_abspath_or_url, pair->src_revnum); 1870251881Speter else 1871251881Speter return svn_error_createf 1872251881Speter (SVN_ERR_FS_NOT_FOUND, NULL, 1873251881Speter _("Path '%s' not found in head revision"), 1874251881Speter pair->src_abspath_or_url); 1875251881Speter } 1876251881Speter 1877251881Speter /* Figure out about dst. */ 1878251881Speter SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, 1879251881Speter iterpool)); 1880251881Speter if (dst_kind != svn_node_none) 1881251881Speter { 1882251881Speter return svn_error_createf( 1883251881Speter SVN_ERR_ENTRY_EXISTS, NULL, 1884251881Speter _("Path '%s' already exists"), 1885251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 1886251881Speter } 1887251881Speter 1888251881Speter /* Make sure the destination parent is a directory and produce a clear 1889251881Speter error message if it is not. */ 1890251881Speter dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); 1891251881Speter SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); 1892251881Speter if (make_parents && dst_parent_kind == svn_node_none) 1893251881Speter { 1894251881Speter SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, 1895251881Speter iterpool)); 1896251881Speter } 1897251881Speter else if (dst_parent_kind != svn_node_dir) 1898251881Speter { 1899251881Speter return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 1900251881Speter _("Path '%s' is not a directory"), 1901251881Speter svn_dirent_local_style(dst_parent, pool)); 1902251881Speter } 1903251881Speter } 1904251881Speter svn_pool_destroy(iterpool); 1905251881Speter 1906251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 1907251881Speter repos_to_wc_copy_locked(timestamp_sleep, 1908251881Speter copy_pairs, top_dst_path, ignore_externals, 1909251881Speter ra_session, ctx, pool), 1910251881Speter ctx->wc_ctx, lock_abspath, FALSE, pool); 1911251881Speter return SVN_NO_ERROR; 1912251881Speter} 1913251881Speter 1914251881Speter#define NEED_REPOS_REVNUM(revision) \ 1915251881Speter ((revision.kind != svn_opt_revision_unspecified) \ 1916251881Speter && (revision.kind != svn_opt_revision_working)) 1917251881Speter 1918251881Speter/* ... 1919251881Speter * 1920251881Speter * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not 1921251881Speter * change *TIMESTAMP_SLEEP. This output will be valid even if the 1922251881Speter * function returns an error. 1923251881Speter * 1924251881Speter * Perform all allocations in POOL. 1925251881Speter */ 1926251881Speterstatic svn_error_t * 1927251881Spetertry_copy(svn_boolean_t *timestamp_sleep, 1928251881Speter const apr_array_header_t *sources, 1929251881Speter const char *dst_path_in, 1930251881Speter svn_boolean_t is_move, 1931251881Speter svn_boolean_t allow_mixed_revisions, 1932251881Speter svn_boolean_t metadata_only, 1933251881Speter svn_boolean_t make_parents, 1934251881Speter svn_boolean_t ignore_externals, 1935251881Speter const apr_hash_t *revprop_table, 1936251881Speter svn_commit_callback2_t commit_callback, 1937251881Speter void *commit_baton, 1938251881Speter svn_client_ctx_t *ctx, 1939251881Speter apr_pool_t *pool) 1940251881Speter{ 1941251881Speter apr_array_header_t *copy_pairs = 1942251881Speter apr_array_make(pool, sources->nelts, 1943251881Speter sizeof(svn_client__copy_pair_t *)); 1944251881Speter svn_boolean_t srcs_are_urls, dst_is_url; 1945251881Speter int i; 1946251881Speter 1947251881Speter /* Are either of our paths URLs? Just check the first src_path. If 1948251881Speter there are more than one, we'll check for homogeneity among them 1949251881Speter down below. */ 1950251881Speter srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0, 1951251881Speter svn_client_copy_source_t *)->path); 1952251881Speter dst_is_url = svn_path_is_url(dst_path_in); 1953251881Speter if (!dst_is_url) 1954251881Speter SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool)); 1955251881Speter 1956251881Speter /* If we have multiple source paths, it implies the dst_path is a 1957251881Speter directory we are moving or copying into. Populate the COPY_PAIRS 1958251881Speter array to contain a destination path for each of the source paths. */ 1959251881Speter if (sources->nelts > 1) 1960251881Speter { 1961251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1962251881Speter 1963251881Speter for (i = 0; i < sources->nelts; i++) 1964251881Speter { 1965251881Speter svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i, 1966251881Speter svn_client_copy_source_t *); 1967251881Speter svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair)); 1968251881Speter const char *src_basename; 1969251881Speter svn_boolean_t src_is_url = svn_path_is_url(source->path); 1970251881Speter 1971251881Speter svn_pool_clear(iterpool); 1972251881Speter 1973251881Speter if (src_is_url) 1974251881Speter { 1975251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 1976251881Speter src_basename = svn_uri_basename(pair->src_abspath_or_url, 1977251881Speter iterpool); 1978251881Speter } 1979251881Speter else 1980251881Speter { 1981251881Speter SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 1982251881Speter source->path, pool)); 1983251881Speter src_basename = svn_dirent_basename(pair->src_abspath_or_url, 1984251881Speter iterpool); 1985251881Speter } 1986251881Speter 1987251881Speter pair->src_op_revision = *source->revision; 1988251881Speter pair->src_peg_revision = *source->peg_revision; 1989251881Speter 1990251881Speter SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 1991251881Speter &pair->src_op_revision, 1992251881Speter src_is_url, 1993251881Speter TRUE, 1994251881Speter iterpool)); 1995251881Speter 1996251881Speter /* Check to see if all the sources are urls or all working copy 1997251881Speter * paths. */ 1998251881Speter if (src_is_url != srcs_are_urls) 1999251881Speter return svn_error_create 2000251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2001251881Speter _("Cannot mix repository and working copy sources")); 2002251881Speter 2003251881Speter if (dst_is_url) 2004251881Speter pair->dst_abspath_or_url = 2005251881Speter svn_path_url_add_component2(dst_path_in, src_basename, pool); 2006251881Speter else 2007251881Speter pair->dst_abspath_or_url = svn_dirent_join(dst_path_in, 2008251881Speter src_basename, pool); 2009251881Speter APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2010251881Speter } 2011251881Speter 2012251881Speter svn_pool_destroy(iterpool); 2013251881Speter } 2014251881Speter else 2015251881Speter { 2016251881Speter /* Only one source path. */ 2017251881Speter svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair)); 2018251881Speter svn_client_copy_source_t *source = 2019251881Speter APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *); 2020251881Speter svn_boolean_t src_is_url = svn_path_is_url(source->path); 2021251881Speter 2022251881Speter if (src_is_url) 2023251881Speter pair->src_abspath_or_url = apr_pstrdup(pool, source->path); 2024251881Speter else 2025251881Speter SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url, 2026251881Speter source->path, pool)); 2027251881Speter pair->src_op_revision = *source->revision; 2028251881Speter pair->src_peg_revision = *source->peg_revision; 2029251881Speter 2030251881Speter SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision, 2031251881Speter &pair->src_op_revision, 2032251881Speter src_is_url, TRUE, pool)); 2033251881Speter 2034251881Speter pair->dst_abspath_or_url = dst_path_in; 2035251881Speter APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair; 2036251881Speter } 2037251881Speter 2038251881Speter if (!srcs_are_urls && !dst_is_url) 2039251881Speter { 2040251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2041251881Speter 2042251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2043251881Speter { 2044251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2045251881Speter svn_client__copy_pair_t *); 2046251881Speter 2047251881Speter svn_pool_clear(iterpool); 2048251881Speter 2049251881Speter if (svn_dirent_is_child(pair->src_abspath_or_url, 2050251881Speter pair->dst_abspath_or_url, iterpool)) 2051251881Speter return svn_error_createf 2052251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2053251881Speter _("Cannot copy path '%s' into its own child '%s'"), 2054251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool), 2055251881Speter svn_dirent_local_style(pair->dst_abspath_or_url, pool)); 2056251881Speter } 2057251881Speter 2058251881Speter svn_pool_destroy(iterpool); 2059251881Speter } 2060251881Speter 2061251881Speter /* A file external should not be moved since the file external is 2062251881Speter implemented as a switched file and it would delete the file the 2063251881Speter file external is switched to, which is not the behavior the user 2064251881Speter would probably want. */ 2065251881Speter if (is_move && !srcs_are_urls) 2066251881Speter { 2067251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2068251881Speter 2069251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2070251881Speter { 2071251881Speter svn_client__copy_pair_t *pair = 2072251881Speter APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); 2073251881Speter svn_node_kind_t external_kind; 2074251881Speter const char *defining_abspath; 2075251881Speter 2076251881Speter svn_pool_clear(iterpool); 2077251881Speter 2078251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2079251881Speter SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, 2080251881Speter NULL, NULL, NULL, ctx->wc_ctx, 2081251881Speter pair->src_abspath_or_url, 2082251881Speter pair->src_abspath_or_url, TRUE, 2083251881Speter iterpool, iterpool)); 2084251881Speter 2085251881Speter if (external_kind != svn_node_none) 2086251881Speter return svn_error_createf( 2087251881Speter SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL, 2088251881Speter NULL, 2089251881Speter _("Cannot move the external at '%s'; please " 2090251881Speter "edit the svn:externals property on '%s'."), 2091251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool), 2092251881Speter svn_dirent_local_style(defining_abspath, pool)); 2093251881Speter } 2094251881Speter svn_pool_destroy(iterpool); 2095251881Speter } 2096251881Speter 2097251881Speter if (is_move) 2098251881Speter { 2099251881Speter /* Disallow moves between the working copy and the repository. */ 2100251881Speter if (srcs_are_urls != dst_is_url) 2101251881Speter { 2102251881Speter return svn_error_create 2103251881Speter (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2104251881Speter _("Moves between the working copy and the repository are not " 2105251881Speter "supported")); 2106251881Speter } 2107251881Speter 2108251881Speter /* Disallow moving any path/URL onto or into itself. */ 2109251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2110251881Speter { 2111251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2112251881Speter svn_client__copy_pair_t *); 2113251881Speter 2114251881Speter if (strcmp(pair->src_abspath_or_url, 2115251881Speter pair->dst_abspath_or_url) == 0) 2116251881Speter return svn_error_createf( 2117251881Speter SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2118251881Speter srcs_are_urls ? 2119251881Speter _("Cannot move URL '%s' into itself") : 2120251881Speter _("Cannot move path '%s' into itself"), 2121251881Speter srcs_are_urls ? 2122251881Speter pair->src_abspath_or_url : 2123251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2124251881Speter } 2125251881Speter } 2126251881Speter else 2127251881Speter { 2128251881Speter if (!srcs_are_urls) 2129251881Speter { 2130251881Speter /* If we are doing a wc->* copy, but with an operational revision 2131251881Speter other than the working copy revision, we are really doing a 2132251881Speter repo->* copy, because we're going to need to get the rev from the 2133251881Speter repo. */ 2134251881Speter 2135251881Speter svn_boolean_t need_repos_op_rev = FALSE; 2136251881Speter svn_boolean_t need_repos_peg_rev = FALSE; 2137251881Speter 2138251881Speter /* Check to see if any revision is something other than 2139251881Speter svn_opt_revision_unspecified or svn_opt_revision_working. */ 2140251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2141251881Speter { 2142251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2143251881Speter svn_client__copy_pair_t *); 2144251881Speter 2145251881Speter if (NEED_REPOS_REVNUM(pair->src_op_revision)) 2146251881Speter need_repos_op_rev = TRUE; 2147251881Speter 2148251881Speter if (NEED_REPOS_REVNUM(pair->src_peg_revision)) 2149251881Speter need_repos_peg_rev = TRUE; 2150251881Speter 2151251881Speter if (need_repos_op_rev || need_repos_peg_rev) 2152251881Speter break; 2153251881Speter } 2154251881Speter 2155251881Speter if (need_repos_op_rev || need_repos_peg_rev) 2156251881Speter { 2157251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2158251881Speter 2159251881Speter for (i = 0; i < copy_pairs->nelts; i++) 2160251881Speter { 2161251881Speter const char *copyfrom_repos_root_url; 2162251881Speter const char *copyfrom_repos_relpath; 2163251881Speter const char *url; 2164251881Speter svn_revnum_t copyfrom_rev; 2165251881Speter svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, 2166251881Speter svn_client__copy_pair_t *); 2167251881Speter 2168251881Speter svn_pool_clear(iterpool); 2169251881Speter 2170251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); 2171251881Speter 2172251881Speter SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev, 2173251881Speter ©from_repos_relpath, 2174251881Speter ©from_repos_root_url, 2175251881Speter NULL, NULL, 2176251881Speter ctx->wc_ctx, 2177251881Speter pair->src_abspath_or_url, 2178251881Speter TRUE, iterpool, iterpool)); 2179251881Speter 2180251881Speter if (copyfrom_repos_relpath) 2181251881Speter url = svn_path_url_add_component2(copyfrom_repos_root_url, 2182251881Speter copyfrom_repos_relpath, 2183251881Speter pool); 2184251881Speter else 2185251881Speter return svn_error_createf 2186251881Speter (SVN_ERR_ENTRY_MISSING_URL, NULL, 2187251881Speter _("'%s' does not have a URL associated with it"), 2188251881Speter svn_dirent_local_style(pair->src_abspath_or_url, pool)); 2189251881Speter 2190251881Speter pair->src_abspath_or_url = url; 2191251881Speter 2192251881Speter if (!need_repos_peg_rev 2193251881Speter || pair->src_peg_revision.kind == svn_opt_revision_base) 2194251881Speter { 2195251881Speter /* Default the peg revision to that of the WC entry. */ 2196251881Speter pair->src_peg_revision.kind = svn_opt_revision_number; 2197251881Speter pair->src_peg_revision.value.number = copyfrom_rev; 2198251881Speter } 2199251881Speter 2200251881Speter if (pair->src_op_revision.kind == svn_opt_revision_base) 2201251881Speter { 2202251881Speter /* Use the entry's revision as the operational rev. */ 2203251881Speter pair->src_op_revision.kind = svn_opt_revision_number; 2204251881Speter pair->src_op_revision.value.number = copyfrom_rev; 2205251881Speter } 2206251881Speter } 2207251881Speter 2208251881Speter svn_pool_destroy(iterpool); 2209251881Speter srcs_are_urls = TRUE; 2210251881Speter } 2211251881Speter } 2212251881Speter } 2213251881Speter 2214251881Speter /* Now, call the right handler for the operation. */ 2215251881Speter if ((! srcs_are_urls) && (! dst_is_url)) 2216251881Speter { 2217251881Speter SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, 2218253734Speter metadata_only, ctx, pool, pool)); 2219251881Speter 2220251881Speter /* Copy or move all targets. */ 2221251881Speter if (is_move) 2222251881Speter return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep, 2223251881Speter copy_pairs, dst_path_in, 2224251881Speter allow_mixed_revisions, 2225251881Speter metadata_only, 2226251881Speter ctx, pool)); 2227251881Speter else 2228251881Speter { 2229251881Speter /* We ignore these values, so assert the default value */ 2230251881Speter SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only); 2231251881Speter return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep, 2232251881Speter copy_pairs, ctx, pool)); 2233251881Speter } 2234251881Speter } 2235251881Speter else if ((! srcs_are_urls) && (dst_is_url)) 2236251881Speter { 2237251881Speter return svn_error_trace( 2238251881Speter wc_to_repos_copy(copy_pairs, make_parents, revprop_table, 2239251881Speter commit_callback, commit_baton, ctx, pool)); 2240251881Speter } 2241251881Speter else if ((srcs_are_urls) && (! dst_is_url)) 2242251881Speter { 2243251881Speter return svn_error_trace( 2244251881Speter repos_to_wc_copy(timestamp_sleep, 2245251881Speter copy_pairs, make_parents, ignore_externals, 2246251881Speter ctx, pool)); 2247251881Speter } 2248251881Speter else 2249251881Speter { 2250251881Speter return svn_error_trace( 2251251881Speter repos_to_repos_copy(copy_pairs, make_parents, revprop_table, 2252251881Speter commit_callback, commit_baton, ctx, is_move, 2253251881Speter pool)); 2254251881Speter } 2255251881Speter} 2256251881Speter 2257251881Speter 2258251881Speter 2259251881Speter/* Public Interfaces */ 2260251881Spetersvn_error_t * 2261251881Spetersvn_client_copy6(const apr_array_header_t *sources, 2262251881Speter const char *dst_path, 2263251881Speter svn_boolean_t copy_as_child, 2264251881Speter svn_boolean_t make_parents, 2265251881Speter svn_boolean_t ignore_externals, 2266251881Speter const apr_hash_t *revprop_table, 2267251881Speter svn_commit_callback2_t commit_callback, 2268251881Speter void *commit_baton, 2269251881Speter svn_client_ctx_t *ctx, 2270251881Speter apr_pool_t *pool) 2271251881Speter{ 2272251881Speter svn_error_t *err; 2273251881Speter svn_boolean_t timestamp_sleep = FALSE; 2274251881Speter apr_pool_t *subpool = svn_pool_create(pool); 2275251881Speter 2276251881Speter if (sources->nelts > 1 && !copy_as_child) 2277251881Speter return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 2278251881Speter NULL, NULL); 2279251881Speter 2280251881Speter err = try_copy(×tamp_sleep, 2281251881Speter sources, dst_path, 2282251881Speter FALSE /* is_move */, 2283251881Speter TRUE /* allow_mixed_revisions */, 2284251881Speter FALSE /* metadata_only */, 2285251881Speter make_parents, 2286251881Speter ignore_externals, 2287251881Speter revprop_table, 2288251881Speter commit_callback, commit_baton, 2289251881Speter ctx, 2290251881Speter subpool); 2291251881Speter 2292251881Speter /* If the destination exists, try to copy the sources as children of the 2293251881Speter destination. */ 2294251881Speter if (copy_as_child && err && (sources->nelts == 1) 2295251881Speter && (err->apr_err == SVN_ERR_ENTRY_EXISTS 2296251881Speter || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 2297251881Speter { 2298251881Speter const char *src_path = APR_ARRAY_IDX(sources, 0, 2299251881Speter svn_client_copy_source_t *)->path; 2300251881Speter const char *src_basename; 2301251881Speter svn_boolean_t src_is_url = svn_path_is_url(src_path); 2302251881Speter svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 2303251881Speter 2304251881Speter svn_error_clear(err); 2305251881Speter svn_pool_clear(subpool); 2306251881Speter 2307251881Speter src_basename = src_is_url ? svn_uri_basename(src_path, subpool) 2308251881Speter : svn_dirent_basename(src_path, subpool); 2309251881Speter dst_path 2310251881Speter = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 2311251881Speter subpool) 2312251881Speter : svn_dirent_join(dst_path, src_basename, subpool); 2313251881Speter 2314251881Speter err = try_copy(×tamp_sleep, 2315251881Speter sources, dst_path, 2316251881Speter FALSE /* is_move */, 2317251881Speter TRUE /* allow_mixed_revisions */, 2318251881Speter FALSE /* metadata_only */, 2319251881Speter make_parents, 2320251881Speter ignore_externals, 2321251881Speter revprop_table, 2322251881Speter commit_callback, commit_baton, 2323251881Speter ctx, 2324251881Speter subpool); 2325251881Speter } 2326251881Speter 2327251881Speter /* Sleep if required. DST_PATH is not a URL in these cases. */ 2328251881Speter if (timestamp_sleep) 2329251881Speter svn_io_sleep_for_timestamps(dst_path, subpool); 2330251881Speter 2331251881Speter svn_pool_destroy(subpool); 2332251881Speter return svn_error_trace(err); 2333251881Speter} 2334251881Speter 2335251881Speter 2336251881Spetersvn_error_t * 2337251881Spetersvn_client_move7(const apr_array_header_t *src_paths, 2338251881Speter const char *dst_path, 2339251881Speter svn_boolean_t move_as_child, 2340251881Speter svn_boolean_t make_parents, 2341251881Speter svn_boolean_t allow_mixed_revisions, 2342251881Speter svn_boolean_t metadata_only, 2343251881Speter const apr_hash_t *revprop_table, 2344251881Speter svn_commit_callback2_t commit_callback, 2345251881Speter void *commit_baton, 2346251881Speter svn_client_ctx_t *ctx, 2347251881Speter apr_pool_t *pool) 2348251881Speter{ 2349251881Speter const svn_opt_revision_t head_revision 2350251881Speter = { svn_opt_revision_head, { 0 } }; 2351251881Speter svn_error_t *err; 2352251881Speter svn_boolean_t timestamp_sleep = FALSE; 2353251881Speter int i; 2354251881Speter apr_pool_t *subpool = svn_pool_create(pool); 2355251881Speter apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts, 2356251881Speter sizeof(const svn_client_copy_source_t *)); 2357251881Speter 2358251881Speter if (src_paths->nelts > 1 && !move_as_child) 2359251881Speter return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED, 2360251881Speter NULL, NULL); 2361251881Speter 2362251881Speter for (i = 0; i < src_paths->nelts; i++) 2363251881Speter { 2364251881Speter const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *); 2365251881Speter svn_client_copy_source_t *copy_source = apr_palloc(pool, 2366251881Speter sizeof(*copy_source)); 2367251881Speter 2368251881Speter copy_source->path = src_path; 2369251881Speter copy_source->revision = &head_revision; 2370251881Speter copy_source->peg_revision = &head_revision; 2371251881Speter 2372251881Speter APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source; 2373251881Speter } 2374251881Speter 2375251881Speter err = try_copy(×tamp_sleep, 2376251881Speter sources, dst_path, 2377251881Speter TRUE /* is_move */, 2378251881Speter allow_mixed_revisions, 2379251881Speter metadata_only, 2380251881Speter make_parents, 2381251881Speter FALSE /* ignore_externals */, 2382251881Speter revprop_table, 2383251881Speter commit_callback, commit_baton, 2384251881Speter ctx, 2385251881Speter subpool); 2386251881Speter 2387251881Speter /* If the destination exists, try to move the sources as children of the 2388251881Speter destination. */ 2389251881Speter if (move_as_child && err && (src_paths->nelts == 1) 2390251881Speter && (err->apr_err == SVN_ERR_ENTRY_EXISTS 2391251881Speter || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS)) 2392251881Speter { 2393251881Speter const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *); 2394251881Speter const char *src_basename; 2395251881Speter svn_boolean_t src_is_url = svn_path_is_url(src_path); 2396251881Speter svn_boolean_t dst_is_url = svn_path_is_url(dst_path); 2397251881Speter 2398251881Speter svn_error_clear(err); 2399251881Speter svn_pool_clear(subpool); 2400251881Speter 2401251881Speter src_basename = src_is_url ? svn_uri_basename(src_path, pool) 2402251881Speter : svn_dirent_basename(src_path, pool); 2403251881Speter dst_path 2404251881Speter = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename, 2405251881Speter subpool) 2406251881Speter : svn_dirent_join(dst_path, src_basename, subpool); 2407251881Speter 2408251881Speter err = try_copy(×tamp_sleep, 2409251881Speter sources, dst_path, 2410251881Speter TRUE /* is_move */, 2411251881Speter allow_mixed_revisions, 2412251881Speter metadata_only, 2413251881Speter make_parents, 2414251881Speter FALSE /* ignore_externals */, 2415251881Speter revprop_table, 2416251881Speter commit_callback, commit_baton, 2417251881Speter ctx, 2418251881Speter subpool); 2419251881Speter } 2420251881Speter 2421251881Speter /* Sleep if required. DST_PATH is not a URL in these cases. */ 2422251881Speter if (timestamp_sleep) 2423251881Speter svn_io_sleep_for_timestamps(dst_path, subpool); 2424251881Speter 2425251881Speter svn_pool_destroy(subpool); 2426251881Speter return svn_error_trace(err); 2427251881Speter} 2428