1/* 2 * update.c: wrappers around wc update functionality 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_hash.h" 31#include "svn_wc.h" 32#include "svn_client.h" 33#include "svn_error.h" 34#include "svn_config.h" 35#include "svn_time.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_pools.h" 39#include "svn_io.h" 40#include "client.h" 41 42#include "svn_private_config.h" 43#include "private/svn_wc_private.h" 44 45/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes 46 a struct svn_client__dirent_fetcher_baton_t * baton */ 47svn_error_t * 48svn_client__dirent_fetcher(void *baton, 49 apr_hash_t **dirents, 50 const char *repos_root_url, 51 const char *repos_relpath, 52 apr_pool_t *result_pool, 53 apr_pool_t *scratch_pool) 54{ 55 struct svn_client__dirent_fetcher_baton_t *dfb = baton; 56 const char *old_url = NULL; 57 const char *session_relpath; 58 svn_node_kind_t kind; 59 const char *url; 60 61 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 62 scratch_pool); 63 64 if (!svn_uri__is_ancestor(dfb->anchor_url, url)) 65 { 66 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session, 67 url, scratch_pool)); 68 session_relpath = ""; 69 } 70 else 71 SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session, 72 &session_relpath, url, 73 scratch_pool)); 74 75 /* Is session_relpath still a directory? */ 76 SVN_ERR(svn_ra_check_path(dfb->ra_session, session_relpath, 77 dfb->target_revision, &kind, scratch_pool)); 78 79 if (kind == svn_node_dir) 80 SVN_ERR(svn_ra_get_dir2(dfb->ra_session, dirents, NULL, NULL, 81 session_relpath, dfb->target_revision, 82 SVN_DIRENT_KIND, result_pool)); 83 else 84 *dirents = NULL; 85 86 if (old_url) 87 SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool)); 88 89 return SVN_NO_ERROR; 90} 91 92 93/*** Code. ***/ 94 95/* Set *CLEAN_CHECKOUT to FALSE only if LOCAL_ABSPATH is a non-empty 96 folder. ANCHOR_ABSPATH is the w/c root and LOCAL_ABSPATH will still 97 be considered empty, if it is equal to ANCHOR_ABSPATH and only 98 contains the admin sub-folder. 99 If the w/c folder already exists but cannot be openend, we return 100 "unclean" - just in case. Most likely, the caller will have to bail 101 out later due to the same error we got here. 102 */ 103static svn_error_t * 104is_empty_wc(svn_boolean_t *clean_checkout, 105 const char *local_abspath, 106 const char *anchor_abspath, 107 apr_pool_t *pool) 108{ 109 apr_dir_t *dir; 110 apr_finfo_t finfo; 111 svn_error_t *err; 112 113 /* "clean" until found dirty */ 114 *clean_checkout = TRUE; 115 116 /* open directory. If it does not exist, yet, a clean one will 117 be created by the caller. */ 118 err = svn_io_dir_open(&dir, local_abspath, pool); 119 if (err) 120 { 121 if (! APR_STATUS_IS_ENOENT(err->apr_err)) 122 *clean_checkout = FALSE; 123 124 svn_error_clear(err); 125 return SVN_NO_ERROR; 126 } 127 128 for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool); 129 err == SVN_NO_ERROR; 130 err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool)) 131 { 132 /* Ignore entries for this dir and its parent, robustly. 133 (APR promises that they'll come first, so technically 134 this guard could be moved outside the loop. But Ryan Bloom 135 says he doesn't believe it, and I believe him. */ 136 if (! (finfo.name[0] == '.' 137 && (finfo.name[1] == '\0' 138 || (finfo.name[1] == '.' && finfo.name[2] == '\0')))) 139 { 140 if ( ! svn_wc_is_adm_dir(finfo.name, pool) 141 || strcmp(local_abspath, anchor_abspath) != 0) 142 { 143 *clean_checkout = FALSE; 144 break; 145 } 146 } 147 } 148 149 if (err) 150 { 151 if (! APR_STATUS_IS_ENOENT(err->apr_err)) 152 { 153 /* There was some issue reading the folder content. 154 * We better disable optimizations in that case. */ 155 *clean_checkout = FALSE; 156 } 157 158 svn_error_clear(err); 159 } 160 161 return svn_io_dir_close(dir); 162} 163 164/* A conflict callback that simply records the conflicted path in BATON. 165 166 Implements svn_wc_conflict_resolver_func2_t. 167*/ 168static svn_error_t * 169record_conflict(svn_wc_conflict_result_t **result, 170 const svn_wc_conflict_description2_t *description, 171 void *baton, 172 apr_pool_t *result_pool, 173 apr_pool_t *scratch_pool) 174{ 175 apr_hash_t *conflicted_paths = baton; 176 177 svn_hash_sets(conflicted_paths, 178 apr_pstrdup(apr_hash_pool_get(conflicted_paths), 179 description->local_abspath), ""); 180 *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, 181 NULL, result_pool); 182 return SVN_NO_ERROR; 183} 184 185/* Perform post-update processing of externals defined below LOCAL_ABSPATH. */ 186static svn_error_t * 187handle_externals(svn_boolean_t *timestamp_sleep, 188 const char *local_abspath, 189 svn_depth_t depth, 190 const char *repos_root_url, 191 svn_ra_session_t *ra_session, 192 svn_client_ctx_t *ctx, 193 apr_pool_t *scratch_pool) 194{ 195 apr_hash_t *new_externals; 196 apr_hash_t *new_depths; 197 198 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, 199 &new_depths, 200 ctx->wc_ctx, local_abspath, 201 depth, 202 scratch_pool, scratch_pool)); 203 204 SVN_ERR(svn_client__handle_externals(new_externals, 205 new_depths, 206 repos_root_url, local_abspath, 207 depth, timestamp_sleep, ra_session, 208 ctx, scratch_pool)); 209 return SVN_NO_ERROR; 210} 211 212/* Try to reuse the RA session by reparenting it to the anchor_url. 213 * This code is probably overly cautious since we only use this 214 * currently when parents are missing and so all the anchor_urls 215 * have to be in the same repo. 216 * Note that ra_session_p is an (optional) input parameter as well 217 * as an output parameter. */ 218static svn_error_t * 219reuse_ra_session(svn_ra_session_t **ra_session_p, 220 const char **corrected_url, 221 const char *anchor_url, 222 const char *anchor_abspath, 223 svn_client_ctx_t *ctx, 224 apr_pool_t *result_pool, 225 apr_pool_t *scratch_pool) 226{ 227 svn_ra_session_t *ra_session = *ra_session_p; 228 229 if (ra_session) 230 { 231 svn_error_t *err = svn_ra_reparent(ra_session, anchor_url, scratch_pool); 232 if (err) 233 { 234 if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL) 235 { 236 /* session changed repos, can't reuse it */ 237 svn_error_clear(err); 238 ra_session = NULL; 239 } 240 else 241 { 242 return svn_error_trace(err); 243 } 244 } 245 else 246 { 247 *corrected_url = NULL; 248 } 249 } 250 251 /* Open an RA session for the URL if one isn't already available */ 252 if (!ra_session) 253 { 254 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, corrected_url, 255 anchor_url, 256 anchor_abspath, NULL, 257 TRUE /* write_dav_props */, 258 TRUE /* read_dav_props */, 259 ctx, 260 result_pool, scratch_pool)); 261 *ra_session_p = ra_session; 262 } 263 264 return SVN_NO_ERROR; 265} 266 267/* This is a helper for svn_client__update_internal(), which see for 268 an explanation of most of these parameters. Some stuff that's 269 unique is as follows: 270 271 ANCHOR_ABSPATH is the local absolute path of the update anchor. 272 This is typically either the same as LOCAL_ABSPATH, or the 273 immediate parent of LOCAL_ABSPATH. 274 275 If NOTIFY_SUMMARY is set (and there's a notification handler in 276 CTX), transmit the final update summary upon successful 277 completion of the update. 278 279 Add the paths of any conflict victims to CONFLICTED_PATHS, if that 280 is not null. 281 282 Use RA_SESSION_P to run the update if it is not NULL. If it is then 283 open a new ra session and place it in RA_SESSION_P. This allows 284 repeated calls to update_internal to reuse the same session. 285*/ 286static svn_error_t * 287update_internal(svn_revnum_t *result_rev, 288 svn_boolean_t *timestamp_sleep, 289 apr_hash_t *conflicted_paths, 290 svn_ra_session_t **ra_session_p, 291 const char *local_abspath, 292 const char *anchor_abspath, 293 const svn_opt_revision_t *revision, 294 svn_depth_t depth, 295 svn_boolean_t depth_is_sticky, 296 svn_boolean_t ignore_externals, 297 svn_boolean_t allow_unver_obstructions, 298 svn_boolean_t adds_as_modification, 299 svn_boolean_t notify_summary, 300 svn_client_ctx_t *ctx, 301 apr_pool_t *result_pool, 302 apr_pool_t *scratch_pool) 303{ 304 const svn_delta_editor_t *update_editor; 305 void *update_edit_baton; 306 const svn_ra_reporter3_t *reporter; 307 void *report_baton; 308 const char *corrected_url; 309 const char *target; 310 const char *repos_root_url; 311 const char *repos_relpath; 312 const char *repos_uuid; 313 const char *anchor_url; 314 svn_revnum_t revnum; 315 svn_boolean_t use_commit_times; 316 svn_boolean_t clean_checkout = FALSE; 317 const char *diff3_cmd; 318 apr_hash_t *wcroot_iprops; 319 svn_opt_revision_t opt_rev; 320 svn_ra_session_t *ra_session = *ra_session_p; 321 const char *preserved_exts_str; 322 apr_array_header_t *preserved_exts; 323 struct svn_client__dirent_fetcher_baton_t dfb; 324 svn_boolean_t server_supports_depth; 325 svn_boolean_t cropping_target; 326 svn_boolean_t target_conflicted = FALSE; 327 svn_config_t *cfg = ctx->config 328 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) 329 : NULL; 330 331 if (result_rev) 332 *result_rev = SVN_INVALID_REVNUM; 333 334 /* An unknown depth can't be sticky. */ 335 if (depth == svn_depth_unknown) 336 depth_is_sticky = FALSE; 337 338 if (strcmp(local_abspath, anchor_abspath)) 339 target = svn_dirent_basename(local_abspath, scratch_pool); 340 else 341 target = ""; 342 343 /* Check if our anchor exists in BASE. If it doesn't we can't update. */ 344 SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url, 345 &repos_uuid, NULL, 346 ctx->wc_ctx, anchor_abspath, 347 TRUE /* ignore_enoent */, 348 scratch_pool, scratch_pool)); 349 350 /* It does not make sense to update conflict victims. */ 351 if (repos_relpath) 352 { 353 svn_error_t *err; 354 svn_boolean_t text_conflicted, prop_conflicted; 355 356 anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath, 357 scratch_pool); 358 359 err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, 360 NULL, 361 ctx->wc_ctx, local_abspath, scratch_pool); 362 363 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 364 return svn_error_trace(err); 365 svn_error_clear(err); 366 367 /* tree-conflicts are handled by the update editor */ 368 if (!err && (text_conflicted || prop_conflicted)) 369 target_conflicted = TRUE; 370 } 371 else 372 anchor_url = NULL; 373 374 if (! anchor_url || target_conflicted) 375 { 376 if (ctx->notify_func2) 377 { 378 svn_wc_notify_t *nt; 379 380 nt = svn_wc_create_notify(local_abspath, 381 target_conflicted 382 ? svn_wc_notify_skip_conflicted 383 : svn_wc_notify_update_skip_working_only, 384 scratch_pool); 385 386 ctx->notify_func2(ctx->notify_baton2, nt, scratch_pool); 387 } 388 return SVN_NO_ERROR; 389 } 390 391 /* We may need to crop the tree if the depth is sticky */ 392 cropping_target = (depth_is_sticky && depth < svn_depth_infinity); 393 if (cropping_target) 394 { 395 svn_node_kind_t target_kind; 396 397 if (depth == svn_depth_exclude) 398 { 399 SVN_ERR(svn_wc_exclude(ctx->wc_ctx, 400 local_abspath, 401 ctx->cancel_func, ctx->cancel_baton, 402 ctx->notify_func2, ctx->notify_baton2, 403 scratch_pool)); 404 405 if (!ignore_externals) 406 { 407 /* We may now be able to remove externals below LOCAL_ABSPATH. */ 408 SVN_ERR(reuse_ra_session(ra_session_p, &corrected_url, 409 anchor_url, anchor_abspath, 410 ctx, result_pool, scratch_pool)); 411 ra_session = *ra_session_p; 412 SVN_ERR(handle_externals(timestamp_sleep, local_abspath, depth, 413 repos_root_url, ra_session, ctx, 414 scratch_pool)); 415 } 416 417 /* Target excluded, we are done now */ 418 return SVN_NO_ERROR; 419 } 420 421 SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, 422 TRUE, TRUE, scratch_pool)); 423 if (target_kind == svn_node_dir) 424 { 425 SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, 426 ctx->cancel_func, ctx->cancel_baton, 427 ctx->notify_func2, ctx->notify_baton2, 428 scratch_pool)); 429 } 430 } 431 432 /* check whether the "clean c/o" optimization is applicable */ 433 SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, 434 scratch_pool)); 435 436 /* Get the external diff3, if any. */ 437 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, 438 SVN_CONFIG_OPTION_DIFF3_CMD, NULL); 439 440 if (diff3_cmd != NULL) 441 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); 442 443 /* See if the user wants last-commit timestamps instead of current ones. */ 444 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, 445 SVN_CONFIG_SECTION_MISCELLANY, 446 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); 447 448 /* See which files the user wants to preserve the extension of when 449 conflict files are made. */ 450 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, 451 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); 452 preserved_exts = *preserved_exts_str 453 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) 454 : NULL; 455 456 /* Let everyone know we're starting a real update (unless we're 457 asked not to). */ 458 if (ctx->notify_func2 && notify_summary) 459 { 460 svn_wc_notify_t *notify 461 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, 462 scratch_pool); 463 notify->kind = svn_node_none; 464 notify->content_state = notify->prop_state 465 = svn_wc_notify_state_inapplicable; 466 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 467 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 468 } 469 470 SVN_ERR(reuse_ra_session(ra_session_p, &corrected_url, anchor_url, 471 anchor_abspath, ctx, result_pool, scratch_pool)); 472 ra_session = *ra_session_p; 473 474 /* If we got a corrected URL from the RA subsystem, we'll need to 475 relocate our working copy first. */ 476 if (corrected_url) 477 { 478 const char *new_repos_root_url; 479 480 /* To relocate everything inside our repository we need the old and new 481 repos root. */ 482 SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, 483 scratch_pool)); 484 485 /* svn_client_relocate2() will check the uuid */ 486 SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url, 487 new_repos_root_url, ignore_externals, 488 ctx, scratch_pool)); 489 490 /* Store updated repository root for externals */ 491 repos_root_url = new_repos_root_url; 492 /* ### We should update anchor_loc->repos_uuid too, although currently 493 * we don't use it. */ 494 anchor_url = corrected_url; 495 } 496 497 /* Resolve unspecified REVISION now, because we need to retrieve the 498 correct inherited props prior to the editor drive and we need to 499 use the same value of HEAD for both. */ 500 opt_rev.kind = revision->kind; 501 opt_rev.value = revision->value; 502 if (opt_rev.kind == svn_opt_revision_unspecified) 503 opt_rev.kind = svn_opt_revision_head; 504 505 /* ### todo: shouldn't svn_client__get_revision_number be able 506 to take a URL as easily as a local path? */ 507 SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, 508 local_abspath, ra_session, &opt_rev, 509 scratch_pool)); 510 511 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, 512 SVN_RA_CAPABILITY_DEPTH, scratch_pool)); 513 514 dfb.ra_session = ra_session; 515 dfb.target_revision = revnum; 516 dfb.anchor_url = anchor_url; 517 518 SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath, 519 revnum, depth, ra_session, 520 ctx, scratch_pool, scratch_pool)); 521 522 /* Fetch the update editor. If REVISION is invalid, that's okay; 523 the RA driver will call editor->set_target_revision later on. */ 524 SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton, 525 &revnum, ctx->wc_ctx, anchor_abspath, 526 target, wcroot_iprops, use_commit_times, 527 depth, depth_is_sticky, 528 allow_unver_obstructions, 529 adds_as_modification, 530 server_supports_depth, 531 clean_checkout, 532 diff3_cmd, preserved_exts, 533 svn_client__dirent_fetcher, &dfb, 534 conflicted_paths ? record_conflict : NULL, 535 conflicted_paths, 536 NULL, NULL, 537 ctx->cancel_func, ctx->cancel_baton, 538 ctx->notify_func2, ctx->notify_baton2, 539 scratch_pool, scratch_pool)); 540 541 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an 542 invalid revnum, that means RA will use the latest revision. */ 543 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton, 544 revnum, target, 545 (!server_supports_depth || depth_is_sticky 546 ? depth 547 : svn_depth_unknown), 548 FALSE /* send_copyfrom_args */, 549 FALSE /* ignore_ancestry */, 550 update_editor, update_edit_baton, 551 scratch_pool, scratch_pool)); 552 553 /* Past this point, we assume the WC is going to be modified so we will 554 * need to sleep for timestamps. */ 555 *timestamp_sleep = TRUE; 556 557 /* Drive the reporter structure, describing the revisions within 558 LOCAL_ABSPATH. When this calls reporter->finish_report, the 559 reporter will drive the update_editor. */ 560 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, 561 report_baton, TRUE, 562 depth, (! depth_is_sticky), 563 (! server_supports_depth), 564 use_commit_times, 565 ctx->cancel_func, ctx->cancel_baton, 566 ctx->notify_func2, ctx->notify_baton2, 567 scratch_pool)); 568 569 /* We handle externals after the update is complete, so that 570 handling external items (and any errors therefrom) doesn't delay 571 the primary operation. */ 572 if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target) 573 && (! ignore_externals)) 574 { 575 SVN_ERR(handle_externals(timestamp_sleep, local_abspath, depth, 576 repos_root_url, ra_session, ctx, scratch_pool)); 577 } 578 579 /* Let everyone know we're finished here (unless we're asked not to). */ 580 if (ctx->notify_func2 && notify_summary) 581 { 582 svn_wc_notify_t *notify 583 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 584 scratch_pool); 585 notify->kind = svn_node_none; 586 notify->content_state = notify->prop_state 587 = svn_wc_notify_state_inapplicable; 588 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 589 notify->revision = revnum; 590 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 591 } 592 593 /* If the caller wants the result revision, give it to them. */ 594 if (result_rev) 595 *result_rev = revnum; 596 597 return SVN_NO_ERROR; 598} 599 600svn_error_t * 601svn_client__update_internal(svn_revnum_t *result_rev, 602 svn_boolean_t *timestamp_sleep, 603 const char *local_abspath, 604 const svn_opt_revision_t *revision, 605 svn_depth_t depth, 606 svn_boolean_t depth_is_sticky, 607 svn_boolean_t ignore_externals, 608 svn_boolean_t allow_unver_obstructions, 609 svn_boolean_t adds_as_modification, 610 svn_boolean_t make_parents, 611 svn_boolean_t innerupdate, 612 svn_ra_session_t *ra_session, 613 svn_client_ctx_t *ctx, 614 apr_pool_t *pool) 615{ 616 const char *anchor_abspath, *lockroot_abspath; 617 svn_error_t *err; 618 svn_opt_revision_t opt_rev = *revision; /* operative revision */ 619 apr_hash_t *conflicted_paths 620 = ctx->conflict_func2 ? apr_hash_make(pool) : NULL; 621 622 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 623 SVN_ERR_ASSERT(! (innerupdate && make_parents)); 624 625 if (make_parents) 626 { 627 int i; 628 const char *parent_abspath = local_abspath; 629 apr_array_header_t *missing_parents = 630 apr_array_make(pool, 4, sizeof(const char *)); 631 apr_pool_t *iterpool; 632 633 iterpool = svn_pool_create(pool); 634 635 while (1) 636 { 637 svn_pool_clear(iterpool); 638 639 /* Try to lock. If we can't lock because our target (or its 640 parent) isn't a working copy, we'll try to walk up the 641 tree to find a working copy, remembering this path's 642 parent as one we need to flesh out. */ 643 err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, 644 parent_abspath, !innerupdate, 645 pool, iterpool); 646 if (!err) 647 break; 648 if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 649 || svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) 650 return err; 651 svn_error_clear(err); 652 653 /* Remember the parent of our update target as a missing 654 parent. */ 655 parent_abspath = svn_dirent_dirname(parent_abspath, pool); 656 APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath; 657 } 658 659 /* Run 'svn up --depth=empty' (effectively) on the missing 660 parents, if any. */ 661 anchor_abspath = lockroot_abspath; 662 for (i = missing_parents->nelts - 1; i >= 0; i--) 663 { 664 const char *missing_parent = 665 APR_ARRAY_IDX(missing_parents, i, const char *); 666 667 svn_pool_clear(iterpool); 668 669 err = update_internal(result_rev, timestamp_sleep, conflicted_paths, 670 &ra_session, missing_parent, 671 anchor_abspath, &opt_rev, svn_depth_empty, 672 FALSE, ignore_externals, 673 allow_unver_obstructions, adds_as_modification, 674 FALSE, ctx, pool, iterpool); 675 if (err) 676 goto cleanup; 677 anchor_abspath = missing_parent; 678 679 /* If we successfully updated a missing parent, let's re-use 680 the returned revision number for future updates for the 681 sake of consistency. */ 682 opt_rev.kind = svn_opt_revision_number; 683 opt_rev.value.number = *result_rev; 684 } 685 686 svn_pool_destroy(iterpool); 687 } 688 else 689 { 690 SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, 691 local_abspath, !innerupdate, 692 pool, pool)); 693 anchor_abspath = lockroot_abspath; 694 } 695 696 err = update_internal(result_rev, timestamp_sleep, conflicted_paths, 697 &ra_session, 698 local_abspath, anchor_abspath, 699 &opt_rev, depth, depth_is_sticky, 700 ignore_externals, allow_unver_obstructions, 701 adds_as_modification, 702 TRUE, ctx, pool, pool); 703 704 /* Give the conflict resolver callback the opportunity to 705 * resolve any conflicts that were raised. */ 706 if (! err && ctx->conflict_func2 && apr_hash_count(conflicted_paths)) 707 { 708 err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool); 709 } 710 711 cleanup: 712 err = svn_error_compose_create( 713 err, 714 svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool)); 715 716 return svn_error_trace(err); 717} 718 719 720svn_error_t * 721svn_client_update4(apr_array_header_t **result_revs, 722 const apr_array_header_t *paths, 723 const svn_opt_revision_t *revision, 724 svn_depth_t depth, 725 svn_boolean_t depth_is_sticky, 726 svn_boolean_t ignore_externals, 727 svn_boolean_t allow_unver_obstructions, 728 svn_boolean_t adds_as_modification, 729 svn_boolean_t make_parents, 730 svn_client_ctx_t *ctx, 731 apr_pool_t *pool) 732{ 733 int i; 734 apr_pool_t *iterpool = svn_pool_create(pool); 735 const char *path = NULL; 736 svn_boolean_t sleep = FALSE; 737 svn_error_t *err = SVN_NO_ERROR; 738 svn_boolean_t found_valid_target = FALSE; 739 740 if (result_revs) 741 *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t)); 742 743 for (i = 0; i < paths->nelts; ++i) 744 { 745 path = APR_ARRAY_IDX(paths, i, const char *); 746 747 if (svn_path_is_url(path)) 748 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 749 _("'%s' is not a local path"), path); 750 } 751 752 for (i = 0; i < paths->nelts; ++i) 753 { 754 svn_revnum_t result_rev; 755 const char *local_abspath; 756 path = APR_ARRAY_IDX(paths, i, const char *); 757 758 svn_pool_clear(iterpool); 759 760 if (ctx->cancel_func) 761 { 762 err = ctx->cancel_func(ctx->cancel_baton); 763 if (err) 764 goto cleanup; 765 } 766 767 err = svn_dirent_get_absolute(&local_abspath, path, iterpool); 768 if (err) 769 goto cleanup; 770 err = svn_client__update_internal(&result_rev, &sleep, local_abspath, 771 revision, depth, depth_is_sticky, 772 ignore_externals, 773 allow_unver_obstructions, 774 adds_as_modification, 775 make_parents, 776 FALSE, NULL, ctx, 777 iterpool); 778 779 if (err) 780 { 781 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 782 goto cleanup; 783 784 svn_error_clear(err); 785 err = SVN_NO_ERROR; 786 787 /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */ 788 789 result_rev = SVN_INVALID_REVNUM; 790 if (ctx->notify_func2) 791 { 792 svn_wc_notify_t *notify; 793 notify = svn_wc_create_notify(path, 794 svn_wc_notify_skip, 795 iterpool); 796 ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 797 } 798 } 799 else 800 found_valid_target = TRUE; 801 802 if (result_revs) 803 APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev; 804 } 805 svn_pool_destroy(iterpool); 806 807 cleanup: 808 if (!err && !found_valid_target) 809 return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 810 _("None of the targets are working copies")); 811 if (sleep) 812 { 813 const char *wcroot_abspath; 814 815 if (paths->nelts == 1) 816 { 817 const char *abspath; 818 819 /* PATH iteslf may have been removed by the update. */ 820 SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool)); 821 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath, 822 pool, pool)); 823 } 824 else 825 wcroot_abspath = NULL; 826 827 svn_io_sleep_for_timestamps(wcroot_abspath, pool); 828 } 829 830 return svn_error_trace(err); 831} 832