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