externals.c revision 269847
1/* 2 * externals.c: handle the svn:externals property 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <apr_uri.h> 31#include "svn_hash.h" 32#include "svn_wc.h" 33#include "svn_pools.h" 34#include "svn_client.h" 35#include "svn_types.h" 36#include "svn_error.h" 37#include "svn_dirent_uri.h" 38#include "svn_path.h" 39#include "svn_props.h" 40#include "svn_config.h" 41#include "client.h" 42 43#include "svn_private_config.h" 44#include "private/svn_wc_private.h" 45 46 47/* Remove the directory at LOCAL_ABSPATH from revision control, and do the 48 * same to any revision controlled directories underneath LOCAL_ABSPATH 49 * (including directories not referred to by parent svn administrative areas); 50 * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a 51 * unique name in the same parent directory. 52 * 53 * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. 54 * 55 * Use SCRATCH_POOL for all temporary allocation. 56 */ 57static svn_error_t * 58relegate_dir_external(svn_wc_context_t *wc_ctx, 59 const char *wri_abspath, 60 const char *local_abspath, 61 svn_cancel_func_t cancel_func, 62 void *cancel_baton, 63 svn_wc_notify_func2_t notify_func, 64 void *notify_baton, 65 apr_pool_t *scratch_pool) 66{ 67 svn_error_t *err = SVN_NO_ERROR; 68 69 SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath, 70 FALSE, scratch_pool, scratch_pool)); 71 72 err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE, 73 cancel_func, cancel_baton, scratch_pool); 74 if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) 75 { 76 const char *parent_dir; 77 const char *dirname; 78 const char *new_path; 79 80 svn_error_clear(err); 81 err = SVN_NO_ERROR; 82 83 svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool); 84 85 /* Reserve the new dir name. */ 86 SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, 87 parent_dir, dirname, ".OLD", 88 svn_io_file_del_none, 89 scratch_pool, scratch_pool)); 90 91 /* Sigh... We must fall ever so slightly from grace. 92 93 Ideally, there would be no window, however brief, when we 94 don't have a reservation on the new name. Unfortunately, 95 at least in the Unix (Linux?) version of apr_file_rename(), 96 you can't rename a directory over a file, because it's just 97 calling stdio rename(), which says: 98 99 ENOTDIR 100 A component used as a directory in oldpath or newpath 101 path is not, in fact, a directory. Or, oldpath is 102 a directory, and newpath exists but is not a directory 103 104 So instead, we get the name, then remove the file (ugh), then 105 rename the directory, hoping that nobody has gotten that name 106 in the meantime -- which would never happen in real life, so 107 no big deal. 108 */ 109 /* Do our best, but no biggy if it fails. The rename will fail. */ 110 svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool)); 111 112 /* Rename. If this is still a working copy we should use the working 113 copy rename function (to release open handles) */ 114 err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path, 115 scratch_pool); 116 117 if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 118 { 119 svn_error_clear(err); 120 121 /* And if it is no longer a working copy, we should just rename 122 it */ 123 err = svn_io_file_rename(local_abspath, new_path, scratch_pool); 124 } 125 126 /* ### TODO: We should notify the user about the rename */ 127 if (notify_func) 128 { 129 svn_wc_notify_t *notify; 130 131 notify = svn_wc_create_notify(err ? local_abspath : new_path, 132 svn_wc_notify_left_local_modifications, 133 scratch_pool); 134 notify->kind = svn_node_dir; 135 notify->err = err; 136 137 notify_func(notify_baton, notify, scratch_pool); 138 } 139 } 140 141 return svn_error_trace(err); 142} 143 144/* Try to update a directory external at PATH to URL at REVISION. 145 Use POOL for temporary allocations, and use the client context CTX. */ 146static svn_error_t * 147switch_dir_external(const char *local_abspath, 148 const char *url, 149 const svn_opt_revision_t *peg_revision, 150 const svn_opt_revision_t *revision, 151 const char *defining_abspath, 152 svn_boolean_t *timestamp_sleep, 153 svn_client_ctx_t *ctx, 154 apr_pool_t *pool) 155{ 156 svn_node_kind_t kind; 157 svn_error_t *err; 158 svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM; 159 svn_revnum_t external_rev = SVN_INVALID_REVNUM; 160 apr_pool_t *subpool = svn_pool_create(pool); 161 const char *repos_root_url; 162 const char *repos_uuid; 163 164 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 165 166 if (peg_revision->kind == svn_opt_revision_number) 167 external_peg_rev = peg_revision->value.number; 168 169 if (revision->kind == svn_opt_revision_number) 170 external_rev = revision->value.number; 171 172 /* If path is a directory, try to update/switch to the correct URL 173 and revision. */ 174 SVN_ERR(svn_io_check_path(local_abspath, &kind, pool)); 175 if (kind == svn_node_dir) 176 { 177 const char *node_url; 178 179 /* Doubles as an "is versioned" check. */ 180 err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath, 181 pool, subpool); 182 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 183 { 184 svn_error_clear(err); 185 err = SVN_NO_ERROR; 186 goto relegate; 187 } 188 else if (err) 189 return svn_error_trace(err); 190 191 if (node_url) 192 { 193 /* If we have what appears to be a version controlled 194 subdir, and its top-level URL matches that of our 195 externals definition, perform an update. */ 196 if (strcmp(node_url, url) == 0) 197 { 198 SVN_ERR(svn_client__update_internal(NULL, local_abspath, 199 revision, svn_depth_unknown, 200 FALSE, FALSE, FALSE, TRUE, 201 FALSE, TRUE, 202 timestamp_sleep, 203 ctx, subpool)); 204 svn_pool_destroy(subpool); 205 goto cleanup; 206 } 207 208 /* We'd really prefer not to have to do a brute-force 209 relegation -- blowing away the current external working 210 copy and checking it out anew -- so we'll first see if we 211 can get away with a generally cheaper relocation (if 212 required) and switch-style update. 213 214 To do so, we need to know the repository root URL of the 215 external working copy as it currently sits. */ 216 err = svn_wc__node_get_repos_info(NULL, NULL, 217 &repos_root_url, &repos_uuid, 218 ctx->wc_ctx, local_abspath, 219 pool, subpool); 220 if (err) 221 { 222 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 223 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 224 return svn_error_trace(err); 225 226 svn_error_clear(err); 227 repos_root_url = NULL; 228 repos_uuid = NULL; 229 } 230 231 if (repos_root_url) 232 { 233 /* If the new external target URL is not obviously a 234 child of the external working copy's current 235 repository root URL... */ 236 if (! svn_uri__is_ancestor(repos_root_url, url)) 237 { 238 const char *repos_root; 239 240 /* ... then figure out precisely which repository 241 root URL that target URL *is* a child of ... */ 242 SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url, 243 ctx, subpool, subpool)); 244 245 /* ... and use that to try to relocate the external 246 working copy to the target location. */ 247 err = svn_client_relocate2(local_abspath, repos_root_url, 248 repos_root, FALSE, ctx, subpool); 249 250 /* If the relocation failed because the new URL 251 points to a totally different repository, we've 252 no choice but to relegate and check out a new WC. */ 253 if (err 254 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION 255 || (err->apr_err 256 == SVN_ERR_CLIENT_INVALID_RELOCATION))) 257 { 258 svn_error_clear(err); 259 goto relegate; 260 } 261 else if (err) 262 return svn_error_trace(err); 263 264 /* If the relocation went without a hitch, we should 265 have a new repository root URL. */ 266 repos_root_url = repos_root; 267 } 268 269 SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url, 270 peg_revision, revision, 271 svn_depth_infinity, 272 TRUE, FALSE, FALSE, 273 TRUE /* ignore_ancestry */, 274 timestamp_sleep, 275 ctx, subpool)); 276 277 SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 278 defining_abspath, 279 local_abspath, svn_node_dir, 280 repos_root_url, repos_uuid, 281 svn_uri_skip_ancestor( 282 repos_root_url, 283 url, subpool), 284 external_peg_rev, 285 external_rev, 286 subpool)); 287 288 svn_pool_destroy(subpool); 289 goto cleanup; 290 } 291 } 292 } 293 294 relegate: 295 296 /* Fall back on removing the WC and checking out a new one. */ 297 298 /* Ensure that we don't have any RA sessions or WC locks from failed 299 operations above. */ 300 svn_pool_destroy(subpool); 301 302 if (kind == svn_node_dir) 303 { 304 /* Buh-bye, old and busted ... */ 305 SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath, 306 local_abspath, 307 ctx->cancel_func, ctx->cancel_baton, 308 ctx->notify_func2, ctx->notify_baton2, 309 pool)); 310 } 311 else 312 { 313 /* The target dir might have multiple components. Guarantee 314 the path leading down to the last component. */ 315 const char *parent = svn_dirent_dirname(local_abspath, pool); 316 SVN_ERR(svn_io_make_dir_recursively(parent, pool)); 317 } 318 319 /* ... Hello, new hotness. */ 320 SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision, 321 revision, svn_depth_infinity, 322 FALSE, FALSE, timestamp_sleep, 323 ctx, pool)); 324 325 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, 326 &repos_root_url, 327 &repos_uuid, 328 ctx->wc_ctx, local_abspath, 329 pool, pool)); 330 331 SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 332 defining_abspath, 333 local_abspath, svn_node_dir, 334 repos_root_url, repos_uuid, 335 svn_uri_skip_ancestor(repos_root_url, 336 url, pool), 337 external_peg_rev, 338 external_rev, 339 pool)); 340 341 cleanup: 342 /* Issues #4123 and #4130: We don't need to keep the newly checked 343 out external's DB open. */ 344 SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool)); 345 346 return SVN_NO_ERROR; 347} 348 349/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a 350 access baton that has a write lock. Use SCRATCH_POOL for temporary 351 allocations, and use the client context CTX. */ 352static svn_error_t * 353switch_file_external(const char *local_abspath, 354 const char *url, 355 const svn_opt_revision_t *peg_revision, 356 const svn_opt_revision_t *revision, 357 const char *def_dir_abspath, 358 svn_ra_session_t *ra_session, 359 svn_client_ctx_t *ctx, 360 apr_pool_t *scratch_pool) 361{ 362 svn_config_t *cfg = ctx->config 363 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) 364 : NULL; 365 svn_boolean_t use_commit_times; 366 const char *diff3_cmd; 367 const char *preserved_exts_str; 368 const apr_array_header_t *preserved_exts; 369 svn_node_kind_t kind, external_kind; 370 371 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 372 373 /* See if the user wants last-commit timestamps instead of current ones. */ 374 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, 375 SVN_CONFIG_SECTION_MISCELLANY, 376 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); 377 378 /* Get the external diff3, if any. */ 379 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, 380 SVN_CONFIG_OPTION_DIFF3_CMD, NULL); 381 382 if (diff3_cmd != NULL) 383 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); 384 385 /* See which files the user wants to preserve the extension of when 386 conflict files are made. */ 387 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, 388 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); 389 preserved_exts = *preserved_exts_str 390 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) 391 : NULL; 392 393 { 394 const char *wcroot_abspath; 395 396 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, 397 scratch_pool, scratch_pool)); 398 399 /* File externals can only be installed inside the current working copy. 400 So verify if the working copy that contains/will contain the target 401 is the defining abspath, or one of its ancestors */ 402 403 if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) 404 return svn_error_createf( 405 SVN_ERR_WC_BAD_PATH, NULL, 406 _("Cannot insert a file external defined on '%s' " 407 "into the working copy '%s'."), 408 svn_dirent_local_style(def_dir_abspath, 409 scratch_pool), 410 svn_dirent_local_style(wcroot_abspath, 411 scratch_pool)); 412 } 413 414 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, 415 TRUE, FALSE, scratch_pool)); 416 417 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 418 ctx->wc_ctx, local_abspath, local_abspath, 419 TRUE, scratch_pool, scratch_pool)); 420 421 /* If there is a versioned item with this name, ensure it's a file 422 external before working with it. If there is no entry in the 423 working copy, then create an empty file and add it to the working 424 copy. */ 425 if (kind != svn_node_none && kind != svn_node_unknown) 426 { 427 if (external_kind != svn_node_file) 428 { 429 return svn_error_createf( 430 SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, 431 _("The file external from '%s' cannot overwrite the existing " 432 "versioned item at '%s'"), 433 url, svn_dirent_local_style(local_abspath, scratch_pool)); 434 } 435 } 436 else 437 { 438 svn_node_kind_t disk_kind; 439 440 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 441 442 if (kind == svn_node_file || kind == svn_node_dir) 443 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, 444 _("The file external '%s' can not be " 445 "created because the node exists."), 446 svn_dirent_local_style(local_abspath, 447 scratch_pool)); 448 } 449 450 { 451 const svn_ra_reporter3_t *reporter; 452 void *report_baton; 453 const svn_delta_editor_t *switch_editor; 454 void *switch_baton; 455 svn_client__pathrev_t *switch_loc; 456 svn_revnum_t revnum; 457 apr_array_header_t *inherited_props; 458 const char *dir_abspath; 459 const char *target; 460 461 svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool); 462 463 /* Open an RA session to 'source' URL */ 464 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, 465 url, dir_abspath, 466 peg_revision, revision, 467 ctx, scratch_pool)); 468 /* Get the external file's iprops. */ 469 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", 470 switch_loc->rev, 471 scratch_pool, scratch_pool)); 472 473 SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool), 474 scratch_pool)); 475 476 SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, 477 &revnum, ctx->wc_ctx, 478 local_abspath, 479 def_dir_abspath, 480 switch_loc->url, 481 switch_loc->repos_root_url, 482 switch_loc->repos_uuid, 483 inherited_props, 484 use_commit_times, 485 diff3_cmd, preserved_exts, 486 def_dir_abspath, 487 url, peg_revision, revision, 488 ctx->conflict_func2, 489 ctx->conflict_baton2, 490 ctx->cancel_func, 491 ctx->cancel_baton, 492 ctx->notify_func2, 493 ctx->notify_baton2, 494 scratch_pool, scratch_pool)); 495 496 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an 497 invalid revnum, that means RA will use the latest revision. */ 498 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, 499 switch_loc->rev, 500 target, svn_depth_unknown, url, 501 FALSE /* send_copyfrom */, 502 TRUE /* ignore_ancestry */, 503 switch_editor, switch_baton, 504 scratch_pool, scratch_pool)); 505 506 SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, 507 reporter, report_baton, 508 TRUE, use_commit_times, 509 ctx->cancel_func, ctx->cancel_baton, 510 ctx->notify_func2, ctx->notify_baton2, 511 scratch_pool)); 512 513 if (ctx->notify_func2) 514 { 515 svn_wc_notify_t *notify 516 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 517 scratch_pool); 518 notify->kind = svn_node_none; 519 notify->content_state = notify->prop_state 520 = svn_wc_notify_state_inapplicable; 521 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 522 notify->revision = revnum; 523 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 524 } 525 } 526 527 return SVN_NO_ERROR; 528} 529 530/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for 531 directory externals */ 532static svn_error_t * 533remove_external2(svn_boolean_t *removed, 534 svn_wc_context_t *wc_ctx, 535 const char *wri_abspath, 536 const char *local_abspath, 537 svn_node_kind_t external_kind, 538 svn_cancel_func_t cancel_func, 539 void *cancel_baton, 540 apr_pool_t *scratch_pool) 541{ 542 SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath, 543 local_abspath, 544 (external_kind == svn_node_none), 545 cancel_func, cancel_baton, 546 scratch_pool)); 547 548 *removed = TRUE; 549 return SVN_NO_ERROR; 550} 551 552 553static svn_error_t * 554remove_external(svn_boolean_t *removed, 555 svn_wc_context_t *wc_ctx, 556 const char *wri_abspath, 557 const char *local_abspath, 558 svn_node_kind_t external_kind, 559 svn_cancel_func_t cancel_func, 560 void *cancel_baton, 561 apr_pool_t *scratch_pool) 562{ 563 *removed = FALSE; 564 switch (external_kind) 565 { 566 case svn_node_dir: 567 SVN_WC__CALL_WITH_WRITE_LOCK( 568 remove_external2(removed, 569 wc_ctx, wri_abspath, 570 local_abspath, external_kind, 571 cancel_func, cancel_baton, 572 scratch_pool), 573 wc_ctx, local_abspath, FALSE, scratch_pool); 574 break; 575 case svn_node_file: 576 default: 577 SVN_ERR(remove_external2(removed, 578 wc_ctx, wri_abspath, 579 local_abspath, external_kind, 580 cancel_func, cancel_baton, 581 scratch_pool)); 582 break; 583 } 584 585 return SVN_NO_ERROR; 586} 587 588/* Called when an external that is in the EXTERNALS table is no longer 589 referenced from an svn:externals property */ 590static svn_error_t * 591handle_external_item_removal(const svn_client_ctx_t *ctx, 592 const char *defining_abspath, 593 const char *local_abspath, 594 apr_pool_t *scratch_pool) 595{ 596 svn_error_t *err; 597 svn_node_kind_t external_kind; 598 svn_node_kind_t kind; 599 svn_boolean_t removed = FALSE; 600 601 /* local_abspath should be a wcroot or a file external */ 602 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 603 ctx->wc_ctx, defining_abspath, 604 local_abspath, FALSE, 605 scratch_pool, scratch_pool)); 606 607 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, 608 scratch_pool)); 609 610 if (external_kind != kind) 611 external_kind = svn_node_none; /* Only remove the registration */ 612 613 err = remove_external(&removed, 614 ctx->wc_ctx, defining_abspath, local_abspath, 615 external_kind, 616 ctx->cancel_func, ctx->cancel_baton, 617 scratch_pool); 618 619 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed) 620 { 621 svn_error_clear(err); 622 err = NULL; /* We removed the working copy, so we can't release the 623 lock that was stored inside */ 624 } 625 626 if (ctx->notify_func2) 627 { 628 svn_wc_notify_t *notify = 629 svn_wc_create_notify(local_abspath, 630 svn_wc_notify_update_external_removed, 631 scratch_pool); 632 633 notify->kind = kind; 634 notify->err = err; 635 636 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 637 638 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 639 { 640 notify = svn_wc_create_notify(local_abspath, 641 svn_wc_notify_left_local_modifications, 642 scratch_pool); 643 notify->kind = svn_node_dir; 644 notify->err = err; 645 646 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 647 } 648 } 649 650 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 651 { 652 svn_error_clear(err); 653 err = NULL; 654 } 655 656 return svn_error_trace(err); 657} 658 659static svn_error_t * 660handle_external_item_change(svn_client_ctx_t *ctx, 661 const char *repos_root_url, 662 const char *parent_dir_abspath, 663 const char *parent_dir_url, 664 const char *local_abspath, 665 const char *old_defining_abspath, 666 const svn_wc_external_item2_t *new_item, 667 svn_boolean_t *timestamp_sleep, 668 apr_pool_t *scratch_pool) 669{ 670 svn_ra_session_t *ra_session; 671 svn_client__pathrev_t *new_loc; 672 const char *new_url; 673 svn_node_kind_t ext_kind; 674 675 SVN_ERR_ASSERT(repos_root_url && parent_dir_url); 676 SVN_ERR_ASSERT(new_item != NULL); 677 678 /* Don't bother to check status, since we'll get that for free by 679 attempting to retrieve the hash values anyway. */ 680 681 /* When creating the absolute URL, use the pool and not the 682 iterpool, since the hash table values outlive the iterpool and 683 any pointers they have should also outlive the iterpool. */ 684 685 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, 686 new_item, repos_root_url, 687 parent_dir_url, 688 scratch_pool, scratch_pool)); 689 690 /* Determine if the external is a file or directory. */ 691 /* Get the RA connection. */ 692 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 693 new_url, NULL, 694 &(new_item->peg_revision), 695 &(new_item->revision), ctx, 696 scratch_pool)); 697 698 SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind, 699 scratch_pool)); 700 701 if (svn_node_none == ext_kind) 702 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 703 _("URL '%s' at revision %ld doesn't exist"), 704 new_loc->url, new_loc->rev); 705 706 if (svn_node_dir != ext_kind && svn_node_file != ext_kind) 707 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 708 _("URL '%s' at revision %ld is not a file " 709 "or a directory"), 710 new_loc->url, new_loc->rev); 711 712 713 /* Not protecting against recursive externals. Detecting them in 714 the global case is hard, and it should be pretty obvious to a 715 user when it happens. Worst case: your disk fills up :-). */ 716 717 /* First notify that we're about to handle an external. */ 718 if (ctx->notify_func2) 719 { 720 (*ctx->notify_func2)( 721 ctx->notify_baton2, 722 svn_wc_create_notify(local_abspath, 723 svn_wc_notify_update_external, 724 scratch_pool), 725 scratch_pool); 726 } 727 728 if (! old_defining_abspath) 729 { 730 /* The target dir might have multiple components. Guarantee the path 731 leading down to the last component. */ 732 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, 733 scratch_pool), 734 scratch_pool)); 735 } 736 737 switch (ext_kind) 738 { 739 case svn_node_dir: 740 SVN_ERR(switch_dir_external(local_abspath, new_loc->url, 741 &(new_item->peg_revision), 742 &(new_item->revision), 743 parent_dir_abspath, 744 timestamp_sleep, ctx, 745 scratch_pool)); 746 break; 747 case svn_node_file: 748 if (strcmp(repos_root_url, new_loc->repos_root_url)) 749 { 750 const char *local_repos_root_url; 751 const char *local_repos_uuid; 752 const char *ext_repos_relpath; 753 svn_error_t *err; 754 755 /* 756 * The working copy library currently requires that all files 757 * in the working copy have the same repository root URL. 758 * The URL from the file external's definition differs from the 759 * one used by the working copy. As a workaround, replace the 760 * root URL portion of the file external's URL, after making 761 * sure both URLs point to the same repository. See issue #4087. 762 */ 763 764 err = svn_wc__node_get_repos_info(NULL, NULL, 765 &local_repos_root_url, 766 &local_repos_uuid, 767 ctx->wc_ctx, parent_dir_abspath, 768 scratch_pool, scratch_pool); 769 if (err) 770 { 771 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 772 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 773 return svn_error_trace(err); 774 775 svn_error_clear(err); 776 local_repos_root_url = NULL; 777 local_repos_uuid = NULL; 778 } 779 780 ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url, 781 new_url, scratch_pool); 782 if (local_repos_uuid == NULL || local_repos_root_url == NULL || 783 ext_repos_relpath == NULL || 784 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0) 785 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 786 _("Unsupported external: URL of file external '%s' " 787 "is not in repository '%s'"), 788 new_url, repos_root_url); 789 790 new_url = svn_path_url_add_component2(local_repos_root_url, 791 ext_repos_relpath, 792 scratch_pool); 793 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 794 new_url, 795 NULL, 796 &(new_item->peg_revision), 797 &(new_item->revision), 798 ctx, scratch_pool)); 799 } 800 801 SVN_ERR(switch_file_external(local_abspath, 802 new_url, 803 &new_item->peg_revision, 804 &new_item->revision, 805 parent_dir_abspath, 806 ra_session, 807 ctx, 808 scratch_pool)); 809 break; 810 811 default: 812 SVN_ERR_MALFUNCTION(); 813 break; 814 } 815 816 return SVN_NO_ERROR; 817} 818 819static svn_error_t * 820wrap_external_error(const svn_client_ctx_t *ctx, 821 const char *target_abspath, 822 svn_error_t *err, 823 apr_pool_t *scratch_pool) 824{ 825 if (err && err->apr_err != SVN_ERR_CANCELLED) 826 { 827 if (ctx->notify_func2) 828 { 829 svn_wc_notify_t *notifier = svn_wc_create_notify( 830 target_abspath, 831 svn_wc_notify_failed_external, 832 scratch_pool); 833 notifier->err = err; 834 ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool); 835 } 836 svn_error_clear(err); 837 return SVN_NO_ERROR; 838 } 839 840 return err; 841} 842 843static svn_error_t * 844handle_externals_change(svn_client_ctx_t *ctx, 845 const char *repos_root_url, 846 svn_boolean_t *timestamp_sleep, 847 const char *local_abspath, 848 const char *new_desc_text, 849 apr_hash_t *old_externals, 850 svn_depth_t ambient_depth, 851 svn_depth_t requested_depth, 852 apr_pool_t *scratch_pool) 853{ 854 apr_array_header_t *new_desc; 855 int i; 856 apr_pool_t *iterpool; 857 const char *url; 858 859 iterpool = svn_pool_create(scratch_pool); 860 861 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 862 863 /* Bag out if the depth here is too shallow for externals action. */ 864 if ((requested_depth < svn_depth_infinity 865 && requested_depth != svn_depth_unknown) 866 || (ambient_depth < svn_depth_infinity 867 && requested_depth < svn_depth_infinity)) 868 return SVN_NO_ERROR; 869 870 if (new_desc_text) 871 SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath, 872 new_desc_text, 873 FALSE, scratch_pool)); 874 else 875 new_desc = NULL; 876 877 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath, 878 scratch_pool, iterpool)); 879 880 SVN_ERR_ASSERT(url); 881 882 for (i = 0; new_desc && (i < new_desc->nelts); i++) 883 { 884 const char *old_defining_abspath; 885 svn_wc_external_item2_t *new_item; 886 const char *target_abspath; 887 svn_boolean_t under_root; 888 889 new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); 890 891 svn_pool_clear(iterpool); 892 893 if (ctx->cancel_func) 894 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 895 896 SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath, 897 local_abspath, new_item->target_dir, 898 iterpool)); 899 900 if (! under_root) 901 { 902 return svn_error_createf( 903 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 904 _("Path '%s' is not in the working copy"), 905 svn_dirent_local_style( 906 svn_dirent_join(local_abspath, new_item->target_dir, 907 iterpool), 908 iterpool)); 909 } 910 911 old_defining_abspath = svn_hash_gets(old_externals, target_abspath); 912 913 SVN_ERR(wrap_external_error( 914 ctx, target_abspath, 915 handle_external_item_change(ctx, 916 repos_root_url, 917 local_abspath, url, 918 target_abspath, 919 old_defining_abspath, 920 new_item, 921 timestamp_sleep, 922 iterpool), 923 iterpool)); 924 925 /* And remove already processed items from the to-remove hash */ 926 if (old_defining_abspath) 927 svn_hash_sets(old_externals, target_abspath, NULL); 928 } 929 930 svn_pool_destroy(iterpool); 931 932 return SVN_NO_ERROR; 933} 934 935 936svn_error_t * 937svn_client__handle_externals(apr_hash_t *externals_new, 938 apr_hash_t *ambient_depths, 939 const char *repos_root_url, 940 const char *target_abspath, 941 svn_depth_t requested_depth, 942 svn_boolean_t *timestamp_sleep, 943 svn_client_ctx_t *ctx, 944 apr_pool_t *scratch_pool) 945{ 946 apr_hash_t *old_external_defs; 947 apr_hash_index_t *hi; 948 apr_pool_t *iterpool; 949 950 SVN_ERR_ASSERT(repos_root_url); 951 952 iterpool = svn_pool_create(scratch_pool); 953 954 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs, 955 ctx->wc_ctx, target_abspath, 956 scratch_pool, iterpool)); 957 958 for (hi = apr_hash_first(scratch_pool, externals_new); 959 hi; 960 hi = apr_hash_next(hi)) 961 { 962 const char *local_abspath = svn__apr_hash_index_key(hi); 963 const char *desc_text = svn__apr_hash_index_val(hi); 964 svn_depth_t ambient_depth = svn_depth_infinity; 965 966 svn_pool_clear(iterpool); 967 968 if (ambient_depths) 969 { 970 const char *ambient_depth_w; 971 972 ambient_depth_w = apr_hash_get(ambient_depths, local_abspath, 973 svn__apr_hash_index_klen(hi)); 974 975 if (ambient_depth_w == NULL) 976 { 977 return svn_error_createf( 978 SVN_ERR_WC_CORRUPT, NULL, 979 _("Traversal of '%s' found no ambient depth"), 980 svn_dirent_local_style(local_abspath, scratch_pool)); 981 } 982 else 983 { 984 ambient_depth = svn_depth_from_word(ambient_depth_w); 985 } 986 } 987 988 SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep, 989 local_abspath, 990 desc_text, old_external_defs, 991 ambient_depth, requested_depth, 992 iterpool)); 993 } 994 995 /* Remove the remaining externals */ 996 for (hi = apr_hash_first(scratch_pool, old_external_defs); 997 hi; 998 hi = apr_hash_next(hi)) 999 { 1000 const char *item_abspath = svn__apr_hash_index_key(hi); 1001 const char *defining_abspath = svn__apr_hash_index_val(hi); 1002 const char *parent_abspath; 1003 1004 svn_pool_clear(iterpool); 1005 1006 SVN_ERR(wrap_external_error( 1007 ctx, item_abspath, 1008 handle_external_item_removal(ctx, defining_abspath, 1009 item_abspath, iterpool), 1010 iterpool)); 1011 1012 /* Are there any unversioned directories between the removed 1013 * external and the DEFINING_ABSPATH which we can remove? */ 1014 parent_abspath = item_abspath; 1015 do { 1016 svn_node_kind_t kind; 1017 1018 parent_abspath = svn_dirent_dirname(parent_abspath, iterpool); 1019 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath, 1020 FALSE /* show_deleted*/, 1021 FALSE /* show_hidden */, 1022 iterpool)); 1023 if (kind == svn_node_none) 1024 { 1025 svn_error_t *err; 1026 1027 err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool); 1028 if (err) 1029 { 1030 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 1031 { 1032 svn_error_clear(err); 1033 break; /* No parents to delete */ 1034 } 1035 else if (APR_STATUS_IS_ENOENT(err->apr_err) 1036 || APR_STATUS_IS_ENOTDIR(err->apr_err)) 1037 { 1038 svn_error_clear(err); 1039 /* Fall through; parent dir might be unversioned */ 1040 } 1041 else 1042 return svn_error_trace(err); 1043 } 1044 } 1045 } while (strcmp(parent_abspath, defining_abspath) != 0); 1046 } 1047 1048 1049 svn_pool_destroy(iterpool); 1050 return SVN_NO_ERROR; 1051} 1052 1053 1054svn_error_t * 1055svn_client__export_externals(apr_hash_t *externals, 1056 const char *from_url, 1057 const char *to_abspath, 1058 const char *repos_root_url, 1059 svn_depth_t requested_depth, 1060 const char *native_eol, 1061 svn_boolean_t ignore_keywords, 1062 svn_client_ctx_t *ctx, 1063 apr_pool_t *scratch_pool) 1064{ 1065 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1066 apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool); 1067 apr_hash_index_t *hi; 1068 1069 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); 1070 1071 for (hi = apr_hash_first(scratch_pool, externals); 1072 hi; 1073 hi = apr_hash_next(hi)) 1074 { 1075 const char *local_abspath = svn__apr_hash_index_key(hi); 1076 const char *desc_text = svn__apr_hash_index_val(hi); 1077 const char *local_relpath; 1078 const char *dir_url; 1079 apr_array_header_t *items; 1080 int i; 1081 1082 svn_pool_clear(iterpool); 1083 1084 SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath, 1085 desc_text, FALSE, 1086 iterpool)); 1087 1088 if (! items->nelts) 1089 continue; 1090 1091 local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath); 1092 1093 dir_url = svn_path_url_add_component2(from_url, local_relpath, 1094 scratch_pool); 1095 1096 for (i = 0; i < items->nelts; i++) 1097 { 1098 const char *item_abspath; 1099 const char *new_url; 1100 svn_boolean_t under_root; 1101 svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i, 1102 svn_wc_external_item2_t *); 1103 1104 svn_pool_clear(sub_iterpool); 1105 1106 SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath, 1107 local_abspath, item->target_dir, 1108 sub_iterpool)); 1109 1110 if (! under_root) 1111 { 1112 return svn_error_createf( 1113 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 1114 _("Path '%s' is not in the working copy"), 1115 svn_dirent_local_style( 1116 svn_dirent_join(local_abspath, item->target_dir, 1117 sub_iterpool), 1118 sub_iterpool)); 1119 } 1120 1121 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item, 1122 repos_root_url, 1123 dir_url, sub_iterpool, 1124 sub_iterpool)); 1125 1126 /* The target dir might have multiple components. Guarantee 1127 the path leading down to the last component. */ 1128 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath, 1129 sub_iterpool), 1130 sub_iterpool)); 1131 1132 SVN_ERR(wrap_external_error( 1133 ctx, item_abspath, 1134 svn_client_export5(NULL, new_url, item_abspath, 1135 &item->peg_revision, 1136 &item->revision, 1137 TRUE, FALSE, ignore_keywords, 1138 svn_depth_infinity, 1139 native_eol, 1140 ctx, sub_iterpool), 1141 sub_iterpool)); 1142 } 1143 } 1144 1145 svn_pool_destroy(sub_iterpool); 1146 svn_pool_destroy(iterpool); 1147 1148 return SVN_NO_ERROR; 1149} 1150 1151