update.c revision 262253
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/* This is a helper for svn_client__update_internal(), which see for 186 an explanation of most of these parameters. Some stuff that's 187 unique is as follows: 188 189 ANCHOR_ABSPATH is the local absolute path of the update anchor. 190 This is typically either the same as LOCAL_ABSPATH, or the 191 immediate parent of LOCAL_ABSPATH. 192 193 If NOTIFY_SUMMARY is set (and there's a notification handler in 194 CTX), transmit the final update summary upon successful 195 completion of the update. 196 197 Add the paths of any conflict victims to CONFLICTED_PATHS, if that 198 is not null. 199*/ 200static svn_error_t * 201update_internal(svn_revnum_t *result_rev, 202 apr_hash_t *conflicted_paths, 203 const char *local_abspath, 204 const char *anchor_abspath, 205 const svn_opt_revision_t *revision, 206 svn_depth_t depth, 207 svn_boolean_t depth_is_sticky, 208 svn_boolean_t ignore_externals, 209 svn_boolean_t allow_unver_obstructions, 210 svn_boolean_t adds_as_modification, 211 svn_boolean_t *timestamp_sleep, 212 svn_boolean_t notify_summary, 213 svn_client_ctx_t *ctx, 214 apr_pool_t *pool) 215{ 216 const svn_delta_editor_t *update_editor; 217 void *update_edit_baton; 218 const svn_ra_reporter3_t *reporter; 219 void *report_baton; 220 const char *corrected_url; 221 const char *target; 222 const char *repos_root_url; 223 const char *repos_relpath; 224 const char *repos_uuid; 225 const char *anchor_url; 226 svn_revnum_t revnum; 227 svn_boolean_t use_commit_times; 228 svn_boolean_t clean_checkout = FALSE; 229 const char *diff3_cmd; 230 apr_hash_t *wcroot_iprops; 231 svn_opt_revision_t opt_rev; 232 svn_ra_session_t *ra_session; 233 const char *preserved_exts_str; 234 apr_array_header_t *preserved_exts; 235 struct svn_client__dirent_fetcher_baton_t dfb; 236 svn_boolean_t server_supports_depth; 237 svn_boolean_t cropping_target; 238 svn_boolean_t target_conflicted = FALSE; 239 svn_config_t *cfg = ctx->config 240 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) 241 : NULL; 242 243 if (result_rev) 244 *result_rev = SVN_INVALID_REVNUM; 245 246 /* An unknown depth can't be sticky. */ 247 if (depth == svn_depth_unknown) 248 depth_is_sticky = FALSE; 249 250 if (strcmp(local_abspath, anchor_abspath)) 251 target = svn_dirent_basename(local_abspath, pool); 252 else 253 target = ""; 254 255 /* Check if our anchor exists in BASE. If it doesn't we can't update. */ 256 SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url, 257 &repos_uuid, NULL, 258 ctx->wc_ctx, anchor_abspath, 259 TRUE, FALSE, 260 pool, pool)); 261 262 /* It does not make sense to update conflict victims. */ 263 if (repos_relpath) 264 { 265 svn_error_t *err; 266 svn_boolean_t text_conflicted, prop_conflicted; 267 268 anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath, 269 pool); 270 271 err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, 272 NULL, 273 ctx->wc_ctx, local_abspath, pool); 274 275 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 276 return svn_error_trace(err); 277 svn_error_clear(err); 278 279 /* tree-conflicts are handled by the update editor */ 280 if (!err && (text_conflicted || prop_conflicted)) 281 target_conflicted = TRUE; 282 } 283 else 284 anchor_url = NULL; 285 286 if (! anchor_url || target_conflicted) 287 { 288 if (ctx->notify_func2) 289 { 290 svn_wc_notify_t *nt; 291 292 nt = svn_wc_create_notify(local_abspath, 293 target_conflicted 294 ? svn_wc_notify_skip_conflicted 295 : svn_wc_notify_update_skip_working_only, 296 pool); 297 298 ctx->notify_func2(ctx->notify_baton2, nt, pool); 299 } 300 return SVN_NO_ERROR; 301 } 302 303 /* We may need to crop the tree if the depth is sticky */ 304 cropping_target = (depth_is_sticky && depth < svn_depth_infinity); 305 if (cropping_target) 306 { 307 svn_node_kind_t target_kind; 308 309 if (depth == svn_depth_exclude) 310 { 311 SVN_ERR(svn_wc_exclude(ctx->wc_ctx, 312 local_abspath, 313 ctx->cancel_func, ctx->cancel_baton, 314 ctx->notify_func2, ctx->notify_baton2, 315 pool)); 316 317 /* Target excluded, we are done now */ 318 return SVN_NO_ERROR; 319 } 320 321 SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, 322 TRUE, TRUE, pool)); 323 if (target_kind == svn_node_dir) 324 { 325 SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, 326 ctx->cancel_func, ctx->cancel_baton, 327 ctx->notify_func2, ctx->notify_baton2, 328 pool)); 329 } 330 } 331 332 /* check whether the "clean c/o" optimization is applicable */ 333 SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); 334 335 /* Get the external diff3, if any. */ 336 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, 337 SVN_CONFIG_OPTION_DIFF3_CMD, NULL); 338 339 if (diff3_cmd != NULL) 340 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); 341 342 /* See if the user wants last-commit timestamps instead of current ones. */ 343 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, 344 SVN_CONFIG_SECTION_MISCELLANY, 345 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); 346 347 /* See which files the user wants to preserve the extension of when 348 conflict files are made. */ 349 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, 350 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); 351 preserved_exts = *preserved_exts_str 352 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) 353 : NULL; 354 355 /* Let everyone know we're starting a real update (unless we're 356 asked not to). */ 357 if (ctx->notify_func2 && notify_summary) 358 { 359 svn_wc_notify_t *notify 360 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, 361 pool); 362 notify->kind = svn_node_none; 363 notify->content_state = notify->prop_state 364 = svn_wc_notify_state_inapplicable; 365 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 366 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 367 } 368 369 /* Open an RA session for the URL */ 370 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 371 anchor_url, 372 anchor_abspath, NULL, TRUE, 373 TRUE, ctx, pool, pool)); 374 375 /* If we got a corrected URL from the RA subsystem, we'll need to 376 relocate our working copy first. */ 377 if (corrected_url) 378 { 379 const char *new_repos_root_url; 380 381 /* To relocate everything inside our repository we need the old and new 382 repos root. */ 383 SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool)); 384 385 /* svn_client_relocate2() will check the uuid */ 386 SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url, 387 new_repos_root_url, ignore_externals, 388 ctx, pool)); 389 390 /* Store updated repository root for externals */ 391 repos_root_url = new_repos_root_url; 392 /* ### We should update anchor_loc->repos_uuid too, although currently 393 * we don't use it. */ 394 anchor_url = corrected_url; 395 } 396 397 /* Resolve unspecified REVISION now, because we need to retrieve the 398 correct inherited props prior to the editor drive and we need to 399 use the same value of HEAD for both. */ 400 opt_rev.kind = revision->kind; 401 opt_rev.value = revision->value; 402 if (opt_rev.kind == svn_opt_revision_unspecified) 403 opt_rev.kind = svn_opt_revision_head; 404 405 /* ### todo: shouldn't svn_client__get_revision_number be able 406 to take a URL as easily as a local path? */ 407 SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, 408 local_abspath, ra_session, &opt_rev, 409 pool)); 410 411 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, 412 SVN_RA_CAPABILITY_DEPTH, pool)); 413 414 dfb.ra_session = ra_session; 415 dfb.target_revision = revnum; 416 dfb.anchor_url = anchor_url; 417 418 SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath, 419 revnum, depth, ra_session, 420 ctx, pool, pool)); 421 422 /* Fetch the update editor. If REVISION is invalid, that's okay; 423 the RA driver will call editor->set_target_revision later on. */ 424 SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton, 425 &revnum, ctx->wc_ctx, anchor_abspath, 426 target, wcroot_iprops, use_commit_times, 427 depth, depth_is_sticky, 428 allow_unver_obstructions, 429 adds_as_modification, 430 server_supports_depth, 431 clean_checkout, 432 diff3_cmd, preserved_exts, 433 svn_client__dirent_fetcher, &dfb, 434 conflicted_paths ? record_conflict : NULL, 435 conflicted_paths, 436 NULL, NULL, 437 ctx->cancel_func, ctx->cancel_baton, 438 ctx->notify_func2, ctx->notify_baton2, 439 pool, pool)); 440 441 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an 442 invalid revnum, that means RA will use the latest revision. */ 443 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton, 444 revnum, target, 445 (!server_supports_depth || depth_is_sticky 446 ? depth 447 : svn_depth_unknown), 448 FALSE /* send_copyfrom_args */, 449 FALSE /* ignore_ancestry */, 450 update_editor, update_edit_baton, pool, pool)); 451 452 /* Past this point, we assume the WC is going to be modified so we will 453 * need to sleep for timestamps. */ 454 *timestamp_sleep = TRUE; 455 456 /* Drive the reporter structure, describing the revisions within 457 PATH. When we call reporter->finish_report, the 458 update_editor will be driven by svn_repos_dir_delta2. */ 459 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, 460 report_baton, TRUE, 461 depth, (! depth_is_sticky), 462 (! server_supports_depth), 463 use_commit_times, 464 ctx->cancel_func, ctx->cancel_baton, 465 ctx->notify_func2, ctx->notify_baton2, 466 pool)); 467 468 /* We handle externals after the update is complete, so that 469 handling external items (and any errors therefrom) doesn't delay 470 the primary operation. */ 471 if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target) 472 && (! ignore_externals)) 473 { 474 apr_hash_t *new_externals; 475 apr_hash_t *new_depths; 476 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, 477 &new_depths, 478 ctx->wc_ctx, local_abspath, 479 depth, pool, pool)); 480 481 SVN_ERR(svn_client__handle_externals(new_externals, 482 new_depths, 483 repos_root_url, local_abspath, 484 depth, timestamp_sleep, 485 ctx, pool)); 486 } 487 488 /* Let everyone know we're finished here (unless we're asked not to). */ 489 if (ctx->notify_func2 && notify_summary) 490 { 491 svn_wc_notify_t *notify 492 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 493 pool); 494 notify->kind = svn_node_none; 495 notify->content_state = notify->prop_state 496 = svn_wc_notify_state_inapplicable; 497 notify->lock_state = svn_wc_notify_lock_state_inapplicable; 498 notify->revision = revnum; 499 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 500 } 501 502 /* If the caller wants the result revision, give it to them. */ 503 if (result_rev) 504 *result_rev = revnum; 505 506 return SVN_NO_ERROR; 507} 508 509svn_error_t * 510svn_client__update_internal(svn_revnum_t *result_rev, 511 const char *local_abspath, 512 const svn_opt_revision_t *revision, 513 svn_depth_t depth, 514 svn_boolean_t depth_is_sticky, 515 svn_boolean_t ignore_externals, 516 svn_boolean_t allow_unver_obstructions, 517 svn_boolean_t adds_as_modification, 518 svn_boolean_t make_parents, 519 svn_boolean_t innerupdate, 520 svn_boolean_t *timestamp_sleep, 521 svn_client_ctx_t *ctx, 522 apr_pool_t *pool) 523{ 524 const char *anchor_abspath, *lockroot_abspath; 525 svn_error_t *err; 526 svn_opt_revision_t peg_revision = *revision; 527 apr_hash_t *conflicted_paths 528 = ctx->conflict_func2 ? apr_hash_make(pool) : NULL; 529 530 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 531 SVN_ERR_ASSERT(! (innerupdate && make_parents)); 532 533 if (make_parents) 534 { 535 int i; 536 const char *parent_abspath = local_abspath; 537 apr_array_header_t *missing_parents = 538 apr_array_make(pool, 4, sizeof(const char *)); 539 540 while (1) 541 { 542 /* Try to lock. If we can't lock because our target (or its 543 parent) isn't a working copy, we'll try to walk up the 544 tree to find a working copy, remembering this path's 545 parent as one we need to flesh out. */ 546 err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, 547 parent_abspath, !innerupdate, 548 pool, pool); 549 if (!err) 550 break; 551 if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 552 || svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) 553 return err; 554 svn_error_clear(err); 555 556 /* Remember the parent of our update target as a missing 557 parent. */ 558 parent_abspath = svn_dirent_dirname(parent_abspath, pool); 559 APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath; 560 } 561 562 /* Run 'svn up --depth=empty' (effectively) on the missing 563 parents, if any. */ 564 anchor_abspath = lockroot_abspath; 565 for (i = missing_parents->nelts - 1; i >= 0; i--) 566 { 567 const char *missing_parent = 568 APR_ARRAY_IDX(missing_parents, i, const char *); 569 570 err = update_internal(result_rev, conflicted_paths, 571 missing_parent, anchor_abspath, 572 &peg_revision, svn_depth_empty, FALSE, 573 ignore_externals, allow_unver_obstructions, 574 adds_as_modification, timestamp_sleep, 575 FALSE, ctx, pool); 576 if (err) 577 goto cleanup; 578 anchor_abspath = missing_parent; 579 580 /* If we successfully updated a missing parent, let's re-use 581 the returned revision number for future updates for the 582 sake of consistency. */ 583 peg_revision.kind = svn_opt_revision_number; 584 peg_revision.value.number = *result_rev; 585 } 586 } 587 else 588 { 589 SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, 590 local_abspath, !innerupdate, 591 pool, pool)); 592 anchor_abspath = lockroot_abspath; 593 } 594 595 err = update_internal(result_rev, conflicted_paths, 596 local_abspath, anchor_abspath, 597 &peg_revision, depth, depth_is_sticky, 598 ignore_externals, allow_unver_obstructions, 599 adds_as_modification, timestamp_sleep, 600 TRUE, ctx, pool); 601 602 /* Give the conflict resolver callback the opportunity to 603 * resolve any conflicts that were raised. */ 604 if (! err && ctx->conflict_func2) 605 { 606 err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool); 607 } 608 609 cleanup: 610 err = svn_error_compose_create( 611 err, 612 svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool)); 613 614 return svn_error_trace(err); 615} 616 617 618svn_error_t * 619svn_client_update4(apr_array_header_t **result_revs, 620 const apr_array_header_t *paths, 621 const svn_opt_revision_t *revision, 622 svn_depth_t depth, 623 svn_boolean_t depth_is_sticky, 624 svn_boolean_t ignore_externals, 625 svn_boolean_t allow_unver_obstructions, 626 svn_boolean_t adds_as_modification, 627 svn_boolean_t make_parents, 628 svn_client_ctx_t *ctx, 629 apr_pool_t *pool) 630{ 631 int i; 632 apr_pool_t *iterpool = svn_pool_create(pool); 633 const char *path = NULL; 634 svn_boolean_t sleep = FALSE; 635 svn_error_t *err = SVN_NO_ERROR; 636 637 if (result_revs) 638 *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t)); 639 640 for (i = 0; i < paths->nelts; ++i) 641 { 642 path = APR_ARRAY_IDX(paths, i, const char *); 643 644 if (svn_path_is_url(path)) 645 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 646 _("'%s' is not a local path"), path); 647 } 648 649 for (i = 0; i < paths->nelts; ++i) 650 { 651 svn_revnum_t result_rev; 652 const char *local_abspath; 653 path = APR_ARRAY_IDX(paths, i, const char *); 654 655 svn_pool_clear(iterpool); 656 657 if (ctx->cancel_func) 658 { 659 err = ctx->cancel_func(ctx->cancel_baton); 660 if (err) 661 goto cleanup; 662 } 663 664 err = svn_dirent_get_absolute(&local_abspath, path, iterpool); 665 if (err) 666 goto cleanup; 667 err = svn_client__update_internal(&result_rev, local_abspath, 668 revision, depth, depth_is_sticky, 669 ignore_externals, 670 allow_unver_obstructions, 671 adds_as_modification, 672 make_parents, 673 FALSE, &sleep, 674 ctx, 675 iterpool); 676 677 if (err) 678 { 679 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 680 goto cleanup; 681 682 svn_error_clear(err); 683 err = SVN_NO_ERROR; 684 685 /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */ 686 687 result_rev = SVN_INVALID_REVNUM; 688 if (ctx->notify_func2) 689 { 690 svn_wc_notify_t *notify; 691 notify = svn_wc_create_notify(path, 692 svn_wc_notify_skip, 693 iterpool); 694 (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool); 695 } 696 } 697 if (result_revs) 698 APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev; 699 } 700 svn_pool_destroy(iterpool); 701 702 cleanup: 703 if (sleep) 704 { 705 const char *wcroot_abspath; 706 707 if (paths->nelts == 1) 708 { 709 const char *abspath; 710 711 /* PATH iteslf may have been removed by the update. */ 712 SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool)); 713 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath, 714 pool, pool)); 715 } 716 else 717 wcroot_abspath = NULL; 718 719 svn_io_sleep_for_timestamps(wcroot_abspath, pool); 720 } 721 722 return svn_error_trace(err); 723} 724