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