externals.c revision 289166
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 char *url_from_externals_definition, 150 const svn_opt_revision_t *peg_revision, 151 const svn_opt_revision_t *revision, 152 const char *defining_abspath, 153 svn_boolean_t *timestamp_sleep, 154 svn_client_ctx_t *ctx, 155 apr_pool_t *pool) 156{ 157 svn_node_kind_t kind; 158 svn_error_t *err; 159 svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM; 160 svn_revnum_t external_rev = SVN_INVALID_REVNUM; 161 apr_pool_t *subpool = svn_pool_create(pool); 162 const char *repos_root_url; 163 const char *repos_uuid; 164 165 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 166 167 if (peg_revision->kind == svn_opt_revision_number) 168 external_peg_rev = peg_revision->value.number; 169 170 if (revision->kind == svn_opt_revision_number) 171 external_rev = revision->value.number; 172 173 /* 174 * The code below assumes existing versioned paths are *not* part of 175 * the external's defining working copy. 176 * The working copy library does not support registering externals 177 * on top of existing BASE nodes and will error out if we try. 178 * So if the external target is part of the defining working copy's 179 * BASE tree, don't attempt to create the external. Doing so would 180 * leave behind a switched path instead of an external (since the 181 * switch succeeds but registration of the external in the DB fails). 182 * The working copy then cannot be updated until the path is switched back. 183 * See issue #4085. 184 */ 185 SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL, 186 &repos_root_url, &repos_uuid, 187 NULL, ctx->wc_ctx, local_abspath, 188 TRUE, /* ignore_enoent */ 189 TRUE, /* show hidden */ 190 pool, pool)); 191 if (kind != svn_node_unknown) 192 { 193 const char *wcroot_abspath; 194 const char *defining_wcroot_abspath; 195 196 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 197 local_abspath, pool, pool)); 198 SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx, 199 defining_abspath, pool, pool)); 200 if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0) 201 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 202 _("The external '%s' defined in %s at '%s' " 203 "cannot be checked out because '%s' is " 204 "already a versioned path."), 205 url_from_externals_definition, 206 SVN_PROP_EXTERNALS, 207 svn_dirent_local_style(defining_abspath, 208 pool), 209 svn_dirent_local_style(local_abspath, 210 pool)); 211 } 212 213 /* If path is a directory, try to update/switch to the correct URL 214 and revision. */ 215 SVN_ERR(svn_io_check_path(local_abspath, &kind, pool)); 216 if (kind == svn_node_dir) 217 { 218 const char *node_url; 219 220 /* Doubles as an "is versioned" check. */ 221 err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath, 222 pool, subpool); 223 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 224 { 225 svn_error_clear(err); 226 err = SVN_NO_ERROR; 227 goto relegate; 228 } 229 else if (err) 230 return svn_error_trace(err); 231 232 if (node_url) 233 { 234 /* If we have what appears to be a version controlled 235 subdir, and its top-level URL matches that of our 236 externals definition, perform an update. */ 237 if (strcmp(node_url, url) == 0) 238 { 239 SVN_ERR(svn_client__update_internal(NULL, local_abspath, 240 revision, svn_depth_unknown, 241 FALSE, FALSE, FALSE, TRUE, 242 FALSE, TRUE, 243 timestamp_sleep, 244 ctx, subpool)); 245 246 /* We just decided that this existing directory is an external, 247 so update the external registry with this information, like 248 when checking out an external */ 249 SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 250 defining_abspath, 251 local_abspath, svn_node_dir, 252 repos_root_url, repos_uuid, 253 svn_uri_skip_ancestor(repos_root_url, 254 url, pool), 255 external_peg_rev, 256 external_rev, 257 pool)); 258 259 svn_pool_destroy(subpool); 260 goto cleanup; 261 } 262 263 /* We'd really prefer not to have to do a brute-force 264 relegation -- blowing away the current external working 265 copy and checking it out anew -- so we'll first see if we 266 can get away with a generally cheaper relocation (if 267 required) and switch-style update. 268 269 To do so, we need to know the repository root URL of the 270 external working copy as it currently sits. */ 271 err = svn_wc__node_get_repos_info(NULL, NULL, 272 &repos_root_url, &repos_uuid, 273 ctx->wc_ctx, local_abspath, 274 pool, subpool); 275 if (err) 276 { 277 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 278 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 279 return svn_error_trace(err); 280 281 svn_error_clear(err); 282 repos_root_url = NULL; 283 repos_uuid = NULL; 284 } 285 286 if (repos_root_url) 287 { 288 /* If the new external target URL is not obviously a 289 child of the external working copy's current 290 repository root URL... */ 291 if (! svn_uri__is_ancestor(repos_root_url, url)) 292 { 293 const char *repos_root; 294 295 /* ... then figure out precisely which repository 296 root URL that target URL *is* a child of ... */ 297 SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url, 298 ctx, subpool, subpool)); 299 300 /* ... and use that to try to relocate the external 301 working copy to the target location. */ 302 err = svn_client_relocate2(local_abspath, repos_root_url, 303 repos_root, FALSE, ctx, subpool); 304 305 /* If the relocation failed because the new URL 306 points to a totally different repository, we've 307 no choice but to relegate and check out a new WC. */ 308 if (err 309 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION 310 || (err->apr_err 311 == SVN_ERR_CLIENT_INVALID_RELOCATION))) 312 { 313 svn_error_clear(err); 314 goto relegate; 315 } 316 else if (err) 317 return svn_error_trace(err); 318 319 /* If the relocation went without a hitch, we should 320 have a new repository root URL. */ 321 repos_root_url = repos_root; 322 } 323 324 SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url, 325 peg_revision, revision, 326 svn_depth_infinity, 327 TRUE, FALSE, FALSE, 328 TRUE /* ignore_ancestry */, 329 timestamp_sleep, 330 ctx, subpool)); 331 332 SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 333 defining_abspath, 334 local_abspath, svn_node_dir, 335 repos_root_url, repos_uuid, 336 svn_uri_skip_ancestor( 337 repos_root_url, 338 url, subpool), 339 external_peg_rev, 340 external_rev, 341 subpool)); 342 343 svn_pool_destroy(subpool); 344 goto cleanup; 345 } 346 } 347 } 348 349 relegate: 350 351 /* Fall back on removing the WC and checking out a new one. */ 352 353 /* Ensure that we don't have any RA sessions or WC locks from failed 354 operations above. */ 355 svn_pool_destroy(subpool); 356 357 if (kind == svn_node_dir) 358 { 359 /* Buh-bye, old and busted ... */ 360 SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath, 361 local_abspath, 362 ctx->cancel_func, ctx->cancel_baton, 363 ctx->notify_func2, ctx->notify_baton2, 364 pool)); 365 } 366 else 367 { 368 /* The target dir might have multiple components. Guarantee 369 the path leading down to the last component. */ 370 const char *parent = svn_dirent_dirname(local_abspath, pool); 371 SVN_ERR(svn_io_make_dir_recursively(parent, pool)); 372 } 373 374 /* ... Hello, new hotness. */ 375 SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision, 376 revision, svn_depth_infinity, 377 FALSE, FALSE, timestamp_sleep, 378 ctx, pool)); 379 380 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, 381 &repos_root_url, 382 &repos_uuid, 383 ctx->wc_ctx, local_abspath, 384 pool, pool)); 385 386 SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 387 defining_abspath, 388 local_abspath, svn_node_dir, 389 repos_root_url, repos_uuid, 390 svn_uri_skip_ancestor(repos_root_url, 391 url, pool), 392 external_peg_rev, 393 external_rev, 394 pool)); 395 396 cleanup: 397 /* Issues #4123 and #4130: We don't need to keep the newly checked 398 out external's DB open. */ 399 SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool)); 400 401 return SVN_NO_ERROR; 402} 403 404/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a 405 access baton that has a write lock. Use SCRATCH_POOL for temporary 406 allocations, and use the client context CTX. */ 407static svn_error_t * 408switch_file_external(const char *local_abspath, 409 const char *url, 410 const svn_opt_revision_t *peg_revision, 411 const svn_opt_revision_t *revision, 412 const char *def_dir_abspath, 413 svn_ra_session_t *ra_session, 414 svn_client_ctx_t *ctx, 415 apr_pool_t *scratch_pool) 416{ 417 svn_config_t *cfg = ctx->config 418 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) 419 : NULL; 420 svn_boolean_t use_commit_times; 421 const char *diff3_cmd; 422 const char *preserved_exts_str; 423 const apr_array_header_t *preserved_exts; 424 svn_node_kind_t kind, external_kind; 425 426 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 427 428 /* See if the user wants last-commit timestamps instead of current ones. */ 429 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, 430 SVN_CONFIG_SECTION_MISCELLANY, 431 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); 432 433 /* Get the external diff3, if any. */ 434 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, 435 SVN_CONFIG_OPTION_DIFF3_CMD, NULL); 436 437 if (diff3_cmd != NULL) 438 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); 439 440 /* See which files the user wants to preserve the extension of when 441 conflict files are made. */ 442 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, 443 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); 444 preserved_exts = *preserved_exts_str 445 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) 446 : NULL; 447 448 { 449 const char *wcroot_abspath; 450 451 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, 452 scratch_pool, scratch_pool)); 453 454 /* File externals can only be installed inside the current working copy. 455 So verify if the working copy that contains/will contain the target 456 is the defining abspath, or one of its ancestors */ 457 458 if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) 459 return svn_error_createf( 460 SVN_ERR_WC_BAD_PATH, NULL, 461 _("Cannot insert a file external defined on '%s' " 462 "into the working copy '%s'."), 463 svn_dirent_local_style(def_dir_abspath, 464 scratch_pool), 465 svn_dirent_local_style(wcroot_abspath, 466 scratch_pool)); 467 } 468 469 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, 470 TRUE, FALSE, scratch_pool)); 471 472 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 473 ctx->wc_ctx, local_abspath, local_abspath, 474 TRUE, scratch_pool, scratch_pool)); 475 476 /* If there is a versioned item with this name, ensure it's a file 477 external before working with it. If there is no entry in the 478 working copy, then create an empty file and add it to the working 479 copy. */ 480 if (kind != svn_node_none && kind != svn_node_unknown) 481 { 482 if (external_kind != svn_node_file) 483 { 484 return svn_error_createf( 485 SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, 486 _("The file external from '%s' cannot overwrite the existing " 487 "versioned item at '%s'"), 488 url, svn_dirent_local_style(local_abspath, scratch_pool)); 489 } 490 } 491 else 492 { 493 svn_node_kind_t disk_kind; 494 495 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 496 497 if (kind == svn_node_file || kind == svn_node_dir) 498 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, 499 _("The file external '%s' can not be " 500 "created because the node exists."), 501 svn_dirent_local_style(local_abspath, 502 scratch_pool)); 503 } 504 505 { 506 const svn_ra_reporter3_t *reporter; 507 void *report_baton; 508 const svn_delta_editor_t *switch_editor; 509 void *switch_baton; 510 svn_client__pathrev_t *switch_loc; 511 svn_revnum_t revnum; 512 apr_array_header_t *inherited_props; 513 const char *dir_abspath; 514 const char *target; 515 516 svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool); 517 518 /* ### Why do we open a new session? RA_SESSION is a valid 519 ### session -- the caller used it to call svn_ra_check_path on 520 ### this very URL, the caller also did the resolving and 521 ### reparenting that is repeated here. */ 522 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, 523 url, dir_abspath, 524 peg_revision, revision, 525 ctx, scratch_pool)); 526 /* Get the external file's iprops. */ 527 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", 528 switch_loc->rev, 529 scratch_pool, scratch_pool)); 530 531 SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool), 532 scratch_pool)); 533 534 SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, 535 &revnum, ctx->wc_ctx, 536 local_abspath, 537 def_dir_abspath, 538 switch_loc->url, 539 switch_loc->repos_root_url, 540 switch_loc->repos_uuid, 541 inherited_props, 542 use_commit_times, 543 diff3_cmd, preserved_exts, 544 def_dir_abspath, 545 url, peg_revision, revision, 546 ctx->conflict_func2, 547 ctx->conflict_baton2, 548 ctx->cancel_func, 549 ctx->cancel_baton, 550 ctx->notify_func2, 551 ctx->notify_baton2, 552 scratch_pool, scratch_pool)); 553 554 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an 555 invalid revnum, that means RA will use the latest revision. */ 556 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, 557 switch_loc->rev, 558 target, svn_depth_unknown, switch_loc->url, 559 FALSE /* send_copyfrom */, 560 TRUE /* ignore_ancestry */, 561 switch_editor, switch_baton, 562 scratch_pool, scratch_pool)); 563 564 SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, 565 reporter, report_baton, 566 TRUE, use_commit_times, 567 ctx->cancel_func, ctx->cancel_baton, 568 ctx->notify_func2, ctx->notify_baton2, 569 scratch_pool)); 570 571 if (ctx->notify_func2) 572 { 573 svn_wc_notify_t *notify 574 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 575 scratch_pool); 576 notify->kind = svn_node_none; 577 notify->content_state = notify->prop_state 578 = svn_wc_notify_state_inapplicable; 579 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 580 notify->revision = revnum; 581 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 582 } 583 } 584 585 return SVN_NO_ERROR; 586} 587 588/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for 589 directory externals */ 590static svn_error_t * 591remove_external2(svn_boolean_t *removed, 592 svn_wc_context_t *wc_ctx, 593 const char *wri_abspath, 594 const char *local_abspath, 595 svn_node_kind_t external_kind, 596 svn_cancel_func_t cancel_func, 597 void *cancel_baton, 598 apr_pool_t *scratch_pool) 599{ 600 SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath, 601 local_abspath, 602 (external_kind == svn_node_none), 603 cancel_func, cancel_baton, 604 scratch_pool)); 605 606 *removed = TRUE; 607 return SVN_NO_ERROR; 608} 609 610 611static svn_error_t * 612remove_external(svn_boolean_t *removed, 613 svn_wc_context_t *wc_ctx, 614 const char *wri_abspath, 615 const char *local_abspath, 616 svn_node_kind_t external_kind, 617 svn_cancel_func_t cancel_func, 618 void *cancel_baton, 619 apr_pool_t *scratch_pool) 620{ 621 *removed = FALSE; 622 switch (external_kind) 623 { 624 case svn_node_dir: 625 SVN_WC__CALL_WITH_WRITE_LOCK( 626 remove_external2(removed, 627 wc_ctx, wri_abspath, 628 local_abspath, external_kind, 629 cancel_func, cancel_baton, 630 scratch_pool), 631 wc_ctx, local_abspath, FALSE, scratch_pool); 632 break; 633 case svn_node_file: 634 default: 635 SVN_ERR(remove_external2(removed, 636 wc_ctx, wri_abspath, 637 local_abspath, external_kind, 638 cancel_func, cancel_baton, 639 scratch_pool)); 640 break; 641 } 642 643 return SVN_NO_ERROR; 644} 645 646/* Called when an external that is in the EXTERNALS table is no longer 647 referenced from an svn:externals property */ 648static svn_error_t * 649handle_external_item_removal(const svn_client_ctx_t *ctx, 650 const char *defining_abspath, 651 const char *local_abspath, 652 apr_pool_t *scratch_pool) 653{ 654 svn_error_t *err; 655 svn_node_kind_t external_kind; 656 svn_node_kind_t kind; 657 svn_boolean_t removed = FALSE; 658 659 /* local_abspath should be a wcroot or a file external */ 660 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 661 ctx->wc_ctx, defining_abspath, 662 local_abspath, FALSE, 663 scratch_pool, scratch_pool)); 664 665 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, 666 scratch_pool)); 667 668 if (external_kind != kind) 669 external_kind = svn_node_none; /* Only remove the registration */ 670 671 err = remove_external(&removed, 672 ctx->wc_ctx, defining_abspath, local_abspath, 673 external_kind, 674 ctx->cancel_func, ctx->cancel_baton, 675 scratch_pool); 676 677 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed) 678 { 679 svn_error_clear(err); 680 err = NULL; /* We removed the working copy, so we can't release the 681 lock that was stored inside */ 682 } 683 684 if (ctx->notify_func2) 685 { 686 svn_wc_notify_t *notify = 687 svn_wc_create_notify(local_abspath, 688 svn_wc_notify_update_external_removed, 689 scratch_pool); 690 691 notify->kind = kind; 692 notify->err = err; 693 694 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 695 696 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 697 { 698 notify = svn_wc_create_notify(local_abspath, 699 svn_wc_notify_left_local_modifications, 700 scratch_pool); 701 notify->kind = svn_node_dir; 702 notify->err = err; 703 704 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); 705 } 706 } 707 708 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 709 { 710 svn_error_clear(err); 711 err = NULL; 712 } 713 714 return svn_error_trace(err); 715} 716 717static svn_error_t * 718handle_external_item_change(svn_client_ctx_t *ctx, 719 const char *repos_root_url, 720 const char *parent_dir_abspath, 721 const char *parent_dir_url, 722 const char *local_abspath, 723 const char *old_defining_abspath, 724 const svn_wc_external_item2_t *new_item, 725 svn_boolean_t *timestamp_sleep, 726 apr_pool_t *scratch_pool) 727{ 728 svn_ra_session_t *ra_session; 729 svn_client__pathrev_t *new_loc; 730 const char *new_url; 731 svn_node_kind_t ext_kind; 732 733 SVN_ERR_ASSERT(repos_root_url && parent_dir_url); 734 SVN_ERR_ASSERT(new_item != NULL); 735 736 /* Don't bother to check status, since we'll get that for free by 737 attempting to retrieve the hash values anyway. */ 738 739 /* When creating the absolute URL, use the pool and not the 740 iterpool, since the hash table values outlive the iterpool and 741 any pointers they have should also outlive the iterpool. */ 742 743 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, 744 new_item, repos_root_url, 745 parent_dir_url, 746 scratch_pool, scratch_pool)); 747 748 /* Determine if the external is a file or directory. */ 749 /* Get the RA connection. */ 750 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 751 new_url, NULL, 752 &(new_item->peg_revision), 753 &(new_item->revision), ctx, 754 scratch_pool)); 755 756 SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind, 757 scratch_pool)); 758 759 if (svn_node_none == ext_kind) 760 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 761 _("URL '%s' at revision %ld doesn't exist"), 762 new_loc->url, new_loc->rev); 763 764 if (svn_node_dir != ext_kind && svn_node_file != ext_kind) 765 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 766 _("URL '%s' at revision %ld is not a file " 767 "or a directory"), 768 new_loc->url, new_loc->rev); 769 770 771 /* Not protecting against recursive externals. Detecting them in 772 the global case is hard, and it should be pretty obvious to a 773 user when it happens. Worst case: your disk fills up :-). */ 774 775 /* First notify that we're about to handle an external. */ 776 if (ctx->notify_func2) 777 { 778 (*ctx->notify_func2)( 779 ctx->notify_baton2, 780 svn_wc_create_notify(local_abspath, 781 svn_wc_notify_update_external, 782 scratch_pool), 783 scratch_pool); 784 } 785 786 if (! old_defining_abspath) 787 { 788 /* The target dir might have multiple components. Guarantee the path 789 leading down to the last component. */ 790 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, 791 scratch_pool), 792 scratch_pool)); 793 } 794 795 switch (ext_kind) 796 { 797 case svn_node_dir: 798 SVN_ERR(switch_dir_external(local_abspath, new_loc->url, 799 new_item->url, 800 &(new_item->peg_revision), 801 &(new_item->revision), 802 parent_dir_abspath, 803 timestamp_sleep, ctx, 804 scratch_pool)); 805 break; 806 case svn_node_file: 807 if (strcmp(repos_root_url, new_loc->repos_root_url)) 808 { 809 const char *local_repos_root_url; 810 const char *local_repos_uuid; 811 const char *ext_repos_relpath; 812 svn_error_t *err; 813 814 /* 815 * The working copy library currently requires that all files 816 * in the working copy have the same repository root URL. 817 * The URL from the file external's definition differs from the 818 * one used by the working copy. As a workaround, replace the 819 * root URL portion of the file external's URL, after making 820 * sure both URLs point to the same repository. See issue #4087. 821 */ 822 823 err = svn_wc__node_get_repos_info(NULL, NULL, 824 &local_repos_root_url, 825 &local_repos_uuid, 826 ctx->wc_ctx, parent_dir_abspath, 827 scratch_pool, scratch_pool); 828 if (err) 829 { 830 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 831 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 832 return svn_error_trace(err); 833 834 svn_error_clear(err); 835 local_repos_root_url = NULL; 836 local_repos_uuid = NULL; 837 } 838 839 ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url, 840 new_url, scratch_pool); 841 if (local_repos_uuid == NULL || local_repos_root_url == NULL || 842 ext_repos_relpath == NULL || 843 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0) 844 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 845 _("Unsupported external: URL of file external '%s' " 846 "is not in repository '%s'"), 847 new_url, repos_root_url); 848 849 new_url = svn_path_url_add_component2(local_repos_root_url, 850 ext_repos_relpath, 851 scratch_pool); 852 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 853 new_url, 854 NULL, 855 &(new_item->peg_revision), 856 &(new_item->revision), 857 ctx, scratch_pool)); 858 } 859 860 SVN_ERR(switch_file_external(local_abspath, 861 new_url, 862 &new_item->peg_revision, 863 &new_item->revision, 864 parent_dir_abspath, 865 ra_session, 866 ctx, 867 scratch_pool)); 868 break; 869 870 default: 871 SVN_ERR_MALFUNCTION(); 872 break; 873 } 874 875 return SVN_NO_ERROR; 876} 877 878static svn_error_t * 879wrap_external_error(const svn_client_ctx_t *ctx, 880 const char *target_abspath, 881 svn_error_t *err, 882 apr_pool_t *scratch_pool) 883{ 884 if (err && err->apr_err != SVN_ERR_CANCELLED) 885 { 886 if (ctx->notify_func2) 887 { 888 svn_wc_notify_t *notifier = svn_wc_create_notify( 889 target_abspath, 890 svn_wc_notify_failed_external, 891 scratch_pool); 892 notifier->err = err; 893 ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool); 894 } 895 svn_error_clear(err); 896 return SVN_NO_ERROR; 897 } 898 899 return err; 900} 901 902static svn_error_t * 903handle_externals_change(svn_client_ctx_t *ctx, 904 const char *repos_root_url, 905 svn_boolean_t *timestamp_sleep, 906 const char *local_abspath, 907 const char *new_desc_text, 908 apr_hash_t *old_externals, 909 svn_depth_t ambient_depth, 910 svn_depth_t requested_depth, 911 apr_pool_t *scratch_pool) 912{ 913 apr_array_header_t *new_desc; 914 int i; 915 apr_pool_t *iterpool; 916 const char *url; 917 918 iterpool = svn_pool_create(scratch_pool); 919 920 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 921 922 /* Bag out if the depth here is too shallow for externals action. */ 923 if ((requested_depth < svn_depth_infinity 924 && requested_depth != svn_depth_unknown) 925 || (ambient_depth < svn_depth_infinity 926 && requested_depth < svn_depth_infinity)) 927 return SVN_NO_ERROR; 928 929 if (new_desc_text) 930 SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath, 931 new_desc_text, 932 FALSE, scratch_pool)); 933 else 934 new_desc = NULL; 935 936 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath, 937 scratch_pool, iterpool)); 938 939 SVN_ERR_ASSERT(url); 940 941 for (i = 0; new_desc && (i < new_desc->nelts); i++) 942 { 943 const char *old_defining_abspath; 944 svn_wc_external_item2_t *new_item; 945 const char *target_abspath; 946 svn_boolean_t under_root; 947 948 new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); 949 950 svn_pool_clear(iterpool); 951 952 if (ctx->cancel_func) 953 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 954 955 SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath, 956 local_abspath, new_item->target_dir, 957 iterpool)); 958 959 if (! under_root) 960 { 961 return svn_error_createf( 962 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 963 _("Path '%s' is not in the working copy"), 964 svn_dirent_local_style( 965 svn_dirent_join(local_abspath, new_item->target_dir, 966 iterpool), 967 iterpool)); 968 } 969 970 old_defining_abspath = svn_hash_gets(old_externals, target_abspath); 971 972 SVN_ERR(wrap_external_error( 973 ctx, target_abspath, 974 handle_external_item_change(ctx, 975 repos_root_url, 976 local_abspath, url, 977 target_abspath, 978 old_defining_abspath, 979 new_item, 980 timestamp_sleep, 981 iterpool), 982 iterpool)); 983 984 /* And remove already processed items from the to-remove hash */ 985 if (old_defining_abspath) 986 svn_hash_sets(old_externals, target_abspath, NULL); 987 } 988 989 svn_pool_destroy(iterpool); 990 991 return SVN_NO_ERROR; 992} 993 994 995svn_error_t * 996svn_client__handle_externals(apr_hash_t *externals_new, 997 apr_hash_t *ambient_depths, 998 const char *repos_root_url, 999 const char *target_abspath, 1000 svn_depth_t requested_depth, 1001 svn_boolean_t *timestamp_sleep, 1002 svn_client_ctx_t *ctx, 1003 apr_pool_t *scratch_pool) 1004{ 1005 apr_hash_t *old_external_defs; 1006 apr_hash_index_t *hi; 1007 apr_pool_t *iterpool; 1008 1009 SVN_ERR_ASSERT(repos_root_url); 1010 1011 iterpool = svn_pool_create(scratch_pool); 1012 1013 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs, 1014 ctx->wc_ctx, target_abspath, 1015 scratch_pool, iterpool)); 1016 1017 for (hi = apr_hash_first(scratch_pool, externals_new); 1018 hi; 1019 hi = apr_hash_next(hi)) 1020 { 1021 const char *local_abspath = svn__apr_hash_index_key(hi); 1022 const char *desc_text = svn__apr_hash_index_val(hi); 1023 svn_depth_t ambient_depth = svn_depth_infinity; 1024 1025 svn_pool_clear(iterpool); 1026 1027 if (ambient_depths) 1028 { 1029 const char *ambient_depth_w; 1030 1031 ambient_depth_w = apr_hash_get(ambient_depths, local_abspath, 1032 svn__apr_hash_index_klen(hi)); 1033 1034 if (ambient_depth_w == NULL) 1035 { 1036 return svn_error_createf( 1037 SVN_ERR_WC_CORRUPT, NULL, 1038 _("Traversal of '%s' found no ambient depth"), 1039 svn_dirent_local_style(local_abspath, scratch_pool)); 1040 } 1041 else 1042 { 1043 ambient_depth = svn_depth_from_word(ambient_depth_w); 1044 } 1045 } 1046 1047 SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep, 1048 local_abspath, 1049 desc_text, old_external_defs, 1050 ambient_depth, requested_depth, 1051 iterpool)); 1052 } 1053 1054 /* Remove the remaining externals */ 1055 for (hi = apr_hash_first(scratch_pool, old_external_defs); 1056 hi; 1057 hi = apr_hash_next(hi)) 1058 { 1059 const char *item_abspath = svn__apr_hash_index_key(hi); 1060 const char *defining_abspath = svn__apr_hash_index_val(hi); 1061 const char *parent_abspath; 1062 1063 svn_pool_clear(iterpool); 1064 1065 SVN_ERR(wrap_external_error( 1066 ctx, item_abspath, 1067 handle_external_item_removal(ctx, defining_abspath, 1068 item_abspath, iterpool), 1069 iterpool)); 1070 1071 /* Are there any unversioned directories between the removed 1072 * external and the DEFINING_ABSPATH which we can remove? */ 1073 parent_abspath = item_abspath; 1074 do { 1075 svn_node_kind_t kind; 1076 1077 parent_abspath = svn_dirent_dirname(parent_abspath, iterpool); 1078 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath, 1079 FALSE /* show_deleted*/, 1080 FALSE /* show_hidden */, 1081 iterpool)); 1082 if (kind == svn_node_none) 1083 { 1084 svn_error_t *err; 1085 1086 err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool); 1087 if (err) 1088 { 1089 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 1090 { 1091 svn_error_clear(err); 1092 break; /* No parents to delete */ 1093 } 1094 else if (APR_STATUS_IS_ENOENT(err->apr_err) 1095 || APR_STATUS_IS_ENOTDIR(err->apr_err)) 1096 { 1097 svn_error_clear(err); 1098 /* Fall through; parent dir might be unversioned */ 1099 } 1100 else 1101 return svn_error_trace(err); 1102 } 1103 } 1104 } while (strcmp(parent_abspath, defining_abspath) != 0); 1105 } 1106 1107 1108 svn_pool_destroy(iterpool); 1109 return SVN_NO_ERROR; 1110} 1111 1112 1113svn_error_t * 1114svn_client__export_externals(apr_hash_t *externals, 1115 const char *from_url, 1116 const char *to_abspath, 1117 const char *repos_root_url, 1118 svn_depth_t requested_depth, 1119 const char *native_eol, 1120 svn_boolean_t ignore_keywords, 1121 svn_client_ctx_t *ctx, 1122 apr_pool_t *scratch_pool) 1123{ 1124 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1125 apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool); 1126 apr_hash_index_t *hi; 1127 1128 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); 1129 1130 for (hi = apr_hash_first(scratch_pool, externals); 1131 hi; 1132 hi = apr_hash_next(hi)) 1133 { 1134 const char *local_abspath = svn__apr_hash_index_key(hi); 1135 const char *desc_text = svn__apr_hash_index_val(hi); 1136 const char *local_relpath; 1137 const char *dir_url; 1138 apr_array_header_t *items; 1139 int i; 1140 1141 svn_pool_clear(iterpool); 1142 1143 SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath, 1144 desc_text, FALSE, 1145 iterpool)); 1146 1147 if (! items->nelts) 1148 continue; 1149 1150 local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath); 1151 1152 dir_url = svn_path_url_add_component2(from_url, local_relpath, 1153 scratch_pool); 1154 1155 for (i = 0; i < items->nelts; i++) 1156 { 1157 const char *item_abspath; 1158 const char *new_url; 1159 svn_boolean_t under_root; 1160 svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i, 1161 svn_wc_external_item2_t *); 1162 1163 svn_pool_clear(sub_iterpool); 1164 1165 SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath, 1166 local_abspath, item->target_dir, 1167 sub_iterpool)); 1168 1169 if (! under_root) 1170 { 1171 return svn_error_createf( 1172 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 1173 _("Path '%s' is not in the working copy"), 1174 svn_dirent_local_style( 1175 svn_dirent_join(local_abspath, item->target_dir, 1176 sub_iterpool), 1177 sub_iterpool)); 1178 } 1179 1180 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item, 1181 repos_root_url, 1182 dir_url, sub_iterpool, 1183 sub_iterpool)); 1184 1185 /* The target dir might have multiple components. Guarantee 1186 the path leading down to the last component. */ 1187 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath, 1188 sub_iterpool), 1189 sub_iterpool)); 1190 1191 SVN_ERR(wrap_external_error( 1192 ctx, item_abspath, 1193 svn_client_export5(NULL, new_url, item_abspath, 1194 &item->peg_revision, 1195 &item->revision, 1196 TRUE, FALSE, ignore_keywords, 1197 svn_depth_infinity, 1198 native_eol, 1199 ctx, sub_iterpool), 1200 sub_iterpool)); 1201 } 1202 } 1203 1204 svn_pool_destroy(sub_iterpool); 1205 svn_pool_destroy(iterpool); 1206 1207 return SVN_NO_ERROR; 1208} 1209 1210