1/* 2 * adm_crawler.c: report local WC mods to an Editor. 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#include <string.h> 28 29#include <apr_pools.h> 30#include <apr_file_io.h> 31#include <apr_hash.h> 32 33#include "svn_hash.h" 34#include "svn_types.h" 35#include "svn_pools.h" 36#include "svn_wc.h" 37#include "svn_io.h" 38#include "svn_delta.h" 39#include "svn_dirent_uri.h" 40#include "svn_path.h" 41 42#include "private/svn_wc_private.h" 43 44#include "wc.h" 45#include "adm_files.h" 46#include "translate.h" 47#include "workqueue.h" 48#include "conflicts.h" 49 50#include "svn_private_config.h" 51 52 53/* Helper for report_revisions_and_depths(). 54 55 Perform an atomic restoration of the file LOCAL_ABSPATH; that is, copy 56 the file's text-base to the administrative tmp area, and then move 57 that file to LOCAL_ABSPATH with possible translations/expansions. If 58 USE_COMMIT_TIMES is set, then set working file's timestamp to 59 last-commit-time. Either way, set entry-timestamp to match that of 60 the working file when all is finished. 61 62 If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing 63 text conflict on LOCAL_ABSPATH. 64 65 Not that a valid access baton with a write lock to the directory of 66 LOCAL_ABSPATH must be available in DB.*/ 67static svn_error_t * 68restore_file(svn_wc__db_t *db, 69 const char *local_abspath, 70 svn_boolean_t use_commit_times, 71 svn_boolean_t mark_resolved_text_conflict, 72 svn_cancel_func_t cancel_func, 73 void *cancel_baton, 74 apr_pool_t *scratch_pool) 75{ 76 svn_skel_t *work_item; 77 78 SVN_ERR(svn_wc__wq_build_file_install(&work_item, 79 db, local_abspath, 80 NULL /* source_abspath */, 81 use_commit_times, 82 TRUE /* record_fileinfo */, 83 scratch_pool, scratch_pool)); 84 /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */ 85 SVN_ERR(svn_wc__db_wq_add(db, 86 svn_dirent_dirname(local_abspath, scratch_pool), 87 work_item, scratch_pool)); 88 89 /* Run the work item immediately. */ 90 SVN_ERR(svn_wc__wq_run(db, local_abspath, 91 cancel_func, cancel_baton, 92 scratch_pool)); 93 94 /* Remove any text conflict */ 95 if (mark_resolved_text_conflict) 96 SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, 97 cancel_func, cancel_baton, 98 scratch_pool)); 99 100 return SVN_NO_ERROR; 101} 102 103svn_error_t * 104svn_wc_restore(svn_wc_context_t *wc_ctx, 105 const char *local_abspath, 106 svn_boolean_t use_commit_times, 107 apr_pool_t *scratch_pool) 108{ 109 /* ### If ever revved: Add cancel func. */ 110 svn_wc__db_status_t status; 111 svn_node_kind_t kind; 112 svn_node_kind_t disk_kind; 113 const svn_checksum_t *checksum; 114 115 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 116 117 if (disk_kind != svn_node_none) 118 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, 119 _("The existing node '%s' can not be restored."), 120 svn_dirent_local_style(local_abspath, 121 scratch_pool)); 122 123 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, 124 NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL, 125 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 126 NULL, NULL, NULL, NULL, 127 wc_ctx->db, local_abspath, 128 scratch_pool, scratch_pool)); 129 130 if (status != svn_wc__db_status_normal 131 && !((status == svn_wc__db_status_added 132 || status == svn_wc__db_status_incomplete) 133 && (kind == svn_node_dir 134 || (kind == svn_node_file && checksum != NULL) 135 /* || (kind == svn_node_symlink && target)*/))) 136 { 137 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 138 _("The node '%s' can not be restored."), 139 svn_dirent_local_style(local_abspath, 140 scratch_pool)); 141 } 142 143 if (kind == svn_node_file || kind == svn_node_symlink) 144 SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times, 145 FALSE /*mark_resolved_text_conflict*/, 146 NULL, NULL /* cancel func, baton */, 147 scratch_pool)); 148 else 149 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); 150 151 return SVN_NO_ERROR; 152} 153 154/* Try to restore LOCAL_ABSPATH of node type KIND and if successful, 155 notify that the node is restored. Use DB for accessing the working copy. 156 If USE_COMMIT_TIMES is set, then set working file's timestamp to 157 last-commit-time. 158 159 This function does all temporary allocations in SCRATCH_POOL 160 */ 161static svn_error_t * 162restore_node(svn_wc__db_t *db, 163 const char *local_abspath, 164 svn_node_kind_t kind, 165 svn_boolean_t mark_resolved_text_conflict, 166 svn_boolean_t use_commit_times, 167 svn_cancel_func_t cancel_func, 168 void *cancel_baton, 169 svn_wc_notify_func2_t notify_func, 170 void *notify_baton, 171 apr_pool_t *scratch_pool) 172{ 173 if (kind == svn_node_file || kind == svn_node_symlink) 174 { 175 /* Recreate file from text-base; mark any text conflict as resolved */ 176 SVN_ERR(restore_file(db, local_abspath, use_commit_times, 177 mark_resolved_text_conflict, 178 cancel_func, cancel_baton, 179 scratch_pool)); 180 } 181 else if (kind == svn_node_dir) 182 { 183 /* Recreating a directory is just a mkdir */ 184 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); 185 } 186 187 /* ... report the restoration to the caller. */ 188 if (notify_func != NULL) 189 { 190 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, 191 svn_wc_notify_restore, 192 scratch_pool); 193 notify->kind = svn_node_file; 194 (*notify_func)(notify_baton, notify, scratch_pool); 195 } 196 197 return SVN_NO_ERROR; 198} 199 200/* The recursive crawler that describes a mixed-revision working 201 copy to an RA layer. Used to initiate updates. 202 203 This is a depth-first recursive walk of the children of DIR_ABSPATH 204 (not including DIR_ABSPATH itself) using DB. Look at each node and 205 check if its revision is different than DIR_REV. If so, report this 206 fact to REPORTER. If a node has a different URL than expected, or 207 a different depth than its parent, report that to REPORTER. 208 209 Report DIR_ABSPATH to the reporter as REPORT_RELPATH. 210 211 Alternatively, if REPORT_EVERYTHING is set, then report all 212 children unconditionally. 213 214 DEPTH is actually the *requested* depth for the update-like 215 operation for which we are reporting working copy state. However, 216 certain requested depths affect the depth of the report crawl. For 217 example, if the requested depth is svn_depth_empty, there's no 218 point descending into subdirs, no matter what their depths. So: 219 220 If DEPTH is svn_depth_empty, don't report any files and don't 221 descend into any subdirs. If svn_depth_files, report files but 222 still don't descend into subdirs. If svn_depth_immediates, report 223 files, and report subdirs themselves but not their entries. If 224 svn_depth_infinity or svn_depth_unknown, report everything all the 225 way down. (That last sentence might sound counterintuitive, but 226 since you can't go deeper than the local ambient depth anyway, 227 requesting svn_depth_infinity really means "as deep as the various 228 parts of this working copy go". Of course, the information that 229 comes back from the server will be different for svn_depth_unknown 230 than for svn_depth_infinity.) 231 232 DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository 233 relative path, the repository root and depth stored on the directory, 234 passed here to avoid another database query. 235 236 DEPTH_COMPATIBILITY_TRICK means the same thing here as it does 237 in svn_wc_crawl_revisions5(). 238 239 If RESTORE_FILES is set, then unexpectedly missing working files 240 will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON 241 will be called to report the restoration. USE_COMMIT_TIMES is 242 passed to restore_file() helper. */ 243static svn_error_t * 244report_revisions_and_depths(svn_wc__db_t *db, 245 const char *dir_abspath, 246 const char *report_relpath, 247 svn_revnum_t dir_rev, 248 const char *dir_repos_relpath, 249 const char *dir_repos_root, 250 svn_depth_t dir_depth, 251 const svn_ra_reporter3_t *reporter, 252 void *report_baton, 253 svn_boolean_t restore_files, 254 svn_depth_t depth, 255 svn_boolean_t honor_depth_exclude, 256 svn_boolean_t depth_compatibility_trick, 257 svn_boolean_t report_everything, 258 svn_boolean_t use_commit_times, 259 svn_cancel_func_t cancel_func, 260 void *cancel_baton, 261 svn_wc_notify_func2_t notify_func, 262 void *notify_baton, 263 apr_pool_t *scratch_pool) 264{ 265 apr_hash_t *base_children; 266 apr_hash_t *dirents; 267 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 268 apr_hash_index_t *hi; 269 svn_error_t *err; 270 271 272 /* Get both the SVN Entries and the actual on-disk entries. Also 273 notice that we're picking up hidden entries too (read_children never 274 hides children). */ 275 SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath, 276 scratch_pool, iterpool)); 277 278 if (restore_files) 279 { 280 err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE, 281 scratch_pool, scratch_pool); 282 283 if (err && (APR_STATUS_IS_ENOENT(err->apr_err) 284 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 285 { 286 svn_error_clear(err); 287 /* There is no directory, and if we could create the directory 288 we would have already created it when walking the parent 289 directory */ 290 restore_files = FALSE; 291 dirents = NULL; 292 } 293 else 294 SVN_ERR(err); 295 } 296 else 297 dirents = NULL; 298 299 /*** Do the real reporting and recursing. ***/ 300 301 /* Looping over current directory's BASE children: */ 302 for (hi = apr_hash_first(scratch_pool, base_children); 303 hi != NULL; 304 hi = apr_hash_next(hi)) 305 { 306 const char *child = apr_hash_this_key(hi); 307 const char *this_report_relpath; 308 const char *this_abspath; 309 svn_boolean_t this_switched = FALSE; 310 struct svn_wc__db_base_info_t *ths = apr_hash_this_val(hi); 311 312 if (cancel_func) 313 SVN_ERR(cancel_func(cancel_baton)); 314 315 /* Clear the iteration subpool here because the loop has a bunch 316 of 'continue' jump statements. */ 317 svn_pool_clear(iterpool); 318 319 /* Compute the paths and URLs we need. */ 320 this_report_relpath = svn_relpath_join(report_relpath, child, iterpool); 321 this_abspath = svn_dirent_join(dir_abspath, child, iterpool); 322 323 /*** File Externals **/ 324 if (ths->update_root) 325 { 326 /* File externals are ... special. We ignore them. */; 327 continue; 328 } 329 330 /* First check for exclusion */ 331 if (ths->status == svn_wc__db_status_excluded) 332 { 333 if (honor_depth_exclude) 334 { 335 /* Report the excluded path, no matter whether report_everything 336 flag is set. Because the report_everything flag indicates 337 that the server will treat the wc as empty and thus push 338 full content of the files/subdirs. But we want to prevent the 339 server from pushing the full content of this_path at us. */ 340 341 /* The server does not support link_path report on excluded 342 path. We explicitly prohibit this situation in 343 svn_wc_crop_tree(). */ 344 SVN_ERR(reporter->set_path(report_baton, 345 this_report_relpath, 346 dir_rev, 347 svn_depth_exclude, 348 FALSE, 349 NULL, 350 iterpool)); 351 } 352 else 353 { 354 /* We want to pull in the excluded target. So, report it as 355 deleted, and server will respond properly. */ 356 if (! report_everything) 357 SVN_ERR(reporter->delete_path(report_baton, 358 this_report_relpath, iterpool)); 359 } 360 continue; 361 } 362 363 /*** The Big Tests: ***/ 364 if (ths->status == svn_wc__db_status_server_excluded 365 || ths->status == svn_wc__db_status_not_present) 366 { 367 /* If the entry is 'absent' or 'not-present', make sure the server 368 knows it's gone... 369 ...unless we're reporting everything, in which case we're 370 going to report it missing later anyway. 371 372 This instructs the server to send it back to us, if it is 373 now available (an addition after a not-present state), or if 374 it is now authorized (change in authz for the absent item). */ 375 if (! report_everything) 376 SVN_ERR(reporter->delete_path(report_baton, this_report_relpath, 377 iterpool)); 378 continue; 379 } 380 381 /* Is the entry NOT on the disk? We may be able to restore it. */ 382 if (restore_files 383 && svn_hash_gets(dirents, child) == NULL) 384 { 385 svn_wc__db_status_t wrk_status; 386 svn_node_kind_t wrk_kind; 387 const svn_checksum_t *checksum; 388 svn_boolean_t conflicted; 389 390 SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, 391 NULL, NULL, NULL, NULL, NULL, NULL, 392 &checksum, NULL, NULL, NULL, NULL, NULL, 393 NULL, NULL, NULL, NULL, &conflicted, 394 NULL, NULL, NULL, NULL, NULL, NULL, 395 db, this_abspath, iterpool, iterpool)); 396 397 if ((wrk_status == svn_wc__db_status_normal 398 || wrk_status == svn_wc__db_status_added 399 || wrk_status == svn_wc__db_status_incomplete) 400 && (wrk_kind == svn_node_dir || checksum)) 401 { 402 svn_node_kind_t dirent_kind; 403 404 /* It is possible on a case insensitive system that the 405 entry is not really missing, but just cased incorrectly. 406 In this case we can't overwrite it with the pristine 407 version */ 408 SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool)); 409 410 if (dirent_kind == svn_node_none) 411 { 412 SVN_ERR(restore_node(db, this_abspath, wrk_kind, 413 conflicted, use_commit_times, 414 cancel_func, cancel_baton, 415 notify_func, notify_baton, iterpool)); 416 } 417 } 418 } 419 420 /* And finally prepare for reporting */ 421 if (!ths->repos_relpath) 422 { 423 ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child, 424 iterpool); 425 } 426 else 427 { 428 const char *childname 429 = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath); 430 431 if (childname == NULL || strcmp(childname, child) != 0) 432 { 433 this_switched = TRUE; 434 } 435 } 436 437 /* Tweak THIS_DEPTH to a useful value. */ 438 if (ths->depth == svn_depth_unknown) 439 ths->depth = svn_depth_infinity; 440 441 /*** Files ***/ 442 if (ths->kind == svn_node_file 443 || ths->kind == svn_node_symlink) 444 { 445 if (report_everything) 446 { 447 /* Report the file unconditionally, one way or another. */ 448 if (this_switched) 449 SVN_ERR(reporter->link_path(report_baton, 450 this_report_relpath, 451 svn_path_url_add_component2( 452 dir_repos_root, 453 ths->repos_relpath, iterpool), 454 ths->revnum, 455 ths->depth, 456 FALSE, 457 ths->lock ? ths->lock->token : NULL, 458 iterpool)); 459 else 460 SVN_ERR(reporter->set_path(report_baton, 461 this_report_relpath, 462 ths->revnum, 463 ths->depth, 464 FALSE, 465 ths->lock ? ths->lock->token : NULL, 466 iterpool)); 467 } 468 469 /* Possibly report a disjoint URL ... */ 470 else if (this_switched) 471 SVN_ERR(reporter->link_path(report_baton, 472 this_report_relpath, 473 svn_path_url_add_component2( 474 dir_repos_root, 475 ths->repos_relpath, iterpool), 476 ths->revnum, 477 ths->depth, 478 FALSE, 479 ths->lock ? ths->lock->token : NULL, 480 iterpool)); 481 /* ... or perhaps just a differing revision or lock token, 482 or the mere presence of the file in a depth-empty dir. */ 483 else if (ths->revnum != dir_rev 484 || ths->lock 485 || dir_depth == svn_depth_empty) 486 SVN_ERR(reporter->set_path(report_baton, 487 this_report_relpath, 488 ths->revnum, 489 ths->depth, 490 FALSE, 491 ths->lock ? ths->lock->token : NULL, 492 iterpool)); 493 } /* end file case */ 494 495 /*** Directories (in recursive mode) ***/ 496 else if (ths->kind == svn_node_dir 497 && (depth > svn_depth_files 498 || depth == svn_depth_unknown)) 499 { 500 svn_boolean_t is_incomplete; 501 svn_boolean_t start_empty; 502 svn_depth_t report_depth = ths->depth; 503 504 is_incomplete = (ths->status == svn_wc__db_status_incomplete); 505 start_empty = is_incomplete; 506 507 if (!SVN_DEPTH_IS_RECURSIVE(depth)) 508 report_depth = svn_depth_empty; 509 510 /* When a <= 1.6 working copy is upgraded without some of its 511 subdirectories we miss some information in the database. If we 512 report the revision as -1, the update editor will receive an 513 add_directory() while it still knows the directory. 514 515 This would raise strange tree conflicts and probably assertions 516 as it would a BASE vs BASE conflict */ 517 if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum)) 518 ths->revnum = dir_rev; 519 520 if (depth_compatibility_trick 521 && ths->depth <= svn_depth_files 522 && depth > ths->depth) 523 { 524 start_empty = TRUE; 525 } 526 527 if (report_everything) 528 { 529 /* Report the dir unconditionally, one way or another... */ 530 if (this_switched) 531 SVN_ERR(reporter->link_path(report_baton, 532 this_report_relpath, 533 svn_path_url_add_component2( 534 dir_repos_root, 535 ths->repos_relpath, iterpool), 536 ths->revnum, 537 report_depth, 538 start_empty, 539 ths->lock ? ths->lock->token 540 : NULL, 541 iterpool)); 542 else 543 SVN_ERR(reporter->set_path(report_baton, 544 this_report_relpath, 545 ths->revnum, 546 report_depth, 547 start_empty, 548 ths->lock ? ths->lock->token : NULL, 549 iterpool)); 550 } 551 else if (this_switched) 552 { 553 /* ...or possibly report a disjoint URL ... */ 554 SVN_ERR(reporter->link_path(report_baton, 555 this_report_relpath, 556 svn_path_url_add_component2( 557 dir_repos_root, 558 ths->repos_relpath, iterpool), 559 ths->revnum, 560 report_depth, 561 start_empty, 562 ths->lock ? ths->lock->token : NULL, 563 iterpool)); 564 } 565 else if (ths->revnum != dir_rev 566 || ths->lock 567 || is_incomplete 568 || dir_depth == svn_depth_empty 569 || dir_depth == svn_depth_files 570 || (dir_depth == svn_depth_immediates 571 && ths->depth != svn_depth_empty) 572 || (ths->depth < svn_depth_infinity 573 && SVN_DEPTH_IS_RECURSIVE(depth))) 574 { 575 /* ... or perhaps just a differing revision, lock token, 576 incomplete subdir, the mere presence of the directory 577 in a depth-empty or depth-files dir, or if the parent 578 dir is at depth-immediates but the child is not at 579 depth-empty. Also describe shallow subdirs if we are 580 trying to set depth to infinity. */ 581 SVN_ERR(reporter->set_path(report_baton, 582 this_report_relpath, 583 ths->revnum, 584 report_depth, 585 start_empty, 586 ths->lock ? ths->lock->token : NULL, 587 iterpool)); 588 } 589 590 /* Finally, recurse if necessary and appropriate. */ 591 if (SVN_DEPTH_IS_RECURSIVE(depth)) 592 { 593 const char *repos_relpath = ths->repos_relpath; 594 595 if (repos_relpath == NULL) 596 { 597 repos_relpath = svn_relpath_join(dir_repos_relpath, child, 598 iterpool); 599 } 600 601 SVN_ERR(report_revisions_and_depths(db, 602 this_abspath, 603 this_report_relpath, 604 ths->revnum, 605 repos_relpath, 606 dir_repos_root, 607 ths->depth, 608 reporter, report_baton, 609 restore_files, depth, 610 honor_depth_exclude, 611 depth_compatibility_trick, 612 start_empty, 613 use_commit_times, 614 cancel_func, cancel_baton, 615 notify_func, notify_baton, 616 iterpool)); 617 } 618 } /* end directory case */ 619 } /* end main entries loop */ 620 621 /* We're done examining this dir's entries, so free everything. */ 622 svn_pool_destroy(iterpool); 623 624 return SVN_NO_ERROR; 625} 626 627 628/*------------------------------------------------------------------*/ 629/*** Public Interfaces ***/ 630 631 632svn_error_t * 633svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, 634 const char *local_abspath, 635 const svn_ra_reporter3_t *reporter, 636 void *report_baton, 637 svn_boolean_t restore_files, 638 svn_depth_t depth, 639 svn_boolean_t honor_depth_exclude, 640 svn_boolean_t depth_compatibility_trick, 641 svn_boolean_t use_commit_times, 642 svn_cancel_func_t cancel_func, 643 void *cancel_baton, 644 svn_wc_notify_func2_t notify_func, 645 void *notify_baton, 646 apr_pool_t *scratch_pool) 647{ 648 svn_wc__db_t *db = wc_ctx->db; 649 svn_error_t *fserr, *err; 650 svn_revnum_t target_rev = SVN_INVALID_REVNUM; 651 svn_boolean_t start_empty; 652 svn_wc__db_status_t status; 653 svn_node_kind_t target_kind; 654 const char *repos_relpath, *repos_root_url; 655 svn_depth_t target_depth; 656 svn_wc__db_lock_t *target_lock; 657 svn_node_kind_t disk_kind; 658 svn_depth_t report_depth; 659 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 660 661 /* Get the base rev, which is the first revnum that entries will be 662 compared to, and some other WC info about the target. */ 663 err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev, 664 &repos_relpath, &repos_root_url, 665 NULL, NULL, NULL, NULL, &target_depth, 666 NULL, NULL, &target_lock, 667 NULL, NULL, NULL, 668 db, local_abspath, scratch_pool, 669 scratch_pool); 670 671 if (err 672 || (status != svn_wc__db_status_normal 673 && status != svn_wc__db_status_incomplete)) 674 { 675 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 676 return svn_error_trace(err); 677 678 svn_error_clear(err); 679 680 /* We don't know about this node, so all we have to do is tell 681 the reporter that we don't know this node. 682 683 But first we have to start the report by sending some basic 684 information for the root. */ 685 686 if (depth == svn_depth_unknown) 687 depth = svn_depth_infinity; 688 689 SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE, 690 NULL, scratch_pool)); 691 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); 692 693 /* Finish the report, which causes the update editor to be 694 driven. */ 695 SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); 696 697 return SVN_NO_ERROR; 698 } 699 700 if (target_depth == svn_depth_unknown) 701 target_depth = svn_depth_infinity; 702 703 start_empty = (status == svn_wc__db_status_incomplete); 704 if (depth_compatibility_trick 705 && target_depth <= svn_depth_immediates 706 && depth > target_depth) 707 { 708 start_empty = TRUE; 709 } 710 711 if (restore_files) 712 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 713 else 714 disk_kind = svn_node_unknown; 715 716 /* Determine if there is a missing node that should be restored */ 717 if (restore_files 718 && disk_kind == svn_node_none) 719 { 720 svn_wc__db_status_t wrk_status; 721 svn_node_kind_t wrk_kind; 722 const svn_checksum_t *checksum; 723 svn_boolean_t conflicted; 724 725 err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, 726 NULL, NULL, NULL, NULL, NULL, &checksum, NULL, 727 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 728 NULL, &conflicted, NULL, NULL, NULL, NULL, 729 NULL, NULL, 730 db, local_abspath, 731 scratch_pool, scratch_pool); 732 733 734 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 735 { 736 svn_error_clear(err); 737 wrk_status = svn_wc__db_status_not_present; 738 wrk_kind = svn_node_file; 739 } 740 else 741 SVN_ERR(err); 742 743 if ((wrk_status == svn_wc__db_status_normal 744 || wrk_status == svn_wc__db_status_added 745 || wrk_status == svn_wc__db_status_incomplete) 746 && (wrk_kind == svn_node_dir || checksum)) 747 { 748 SVN_ERR(restore_node(wc_ctx->db, local_abspath, 749 wrk_kind, conflicted, use_commit_times, 750 cancel_func, cancel_baton, 751 notify_func, notify_baton, 752 scratch_pool)); 753 } 754 } 755 756 { 757 report_depth = target_depth; 758 759 if (honor_depth_exclude 760 && depth != svn_depth_unknown 761 && depth < target_depth) 762 report_depth = depth; 763 764 /* The first call to the reporter merely informs it that the 765 top-level directory being updated is at BASE_REV. Its PATH 766 argument is ignored. */ 767 SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth, 768 start_empty, NULL, scratch_pool)); 769 } 770 if (target_kind == svn_node_dir) 771 { 772 if (depth != svn_depth_empty) 773 { 774 /* Recursively crawl ROOT_DIRECTORY and report differing 775 revisions. */ 776 err = report_revisions_and_depths(wc_ctx->db, 777 local_abspath, 778 "", 779 target_rev, 780 repos_relpath, 781 repos_root_url, 782 report_depth, 783 reporter, report_baton, 784 restore_files, depth, 785 honor_depth_exclude, 786 depth_compatibility_trick, 787 start_empty, 788 use_commit_times, 789 cancel_func, cancel_baton, 790 notify_func, notify_baton, 791 scratch_pool); 792 if (err) 793 goto abort_report; 794 } 795 } 796 797 else if (target_kind == svn_node_file || target_kind == svn_node_symlink) 798 { 799 const char *parent_abspath, *base; 800 svn_wc__db_status_t parent_status; 801 const char *parent_repos_relpath; 802 803 svn_dirent_split(&parent_abspath, &base, local_abspath, 804 scratch_pool); 805 806 /* We can assume a file is in the same repository as its parent 807 directory, so we only look at the relpath. */ 808 err = svn_wc__db_base_get_info(&parent_status, NULL, NULL, 809 &parent_repos_relpath, NULL, NULL, NULL, 810 NULL, NULL, NULL, NULL, NULL, NULL, 811 NULL, NULL, NULL, 812 db, parent_abspath, 813 scratch_pool, scratch_pool); 814 815 if (err) 816 goto abort_report; 817 818 if (strcmp(repos_relpath, 819 svn_relpath_join(parent_repos_relpath, base, 820 scratch_pool)) != 0) 821 { 822 /* This file is disjoint with respect to its parent 823 directory. Since we are looking at the actual target of 824 the report (not some file in a subdirectory of a target 825 directory), and that target is a file, we need to pass an 826 empty string to link_path. */ 827 err = reporter->link_path(report_baton, 828 "", 829 svn_path_url_add_component2( 830 repos_root_url, 831 repos_relpath, 832 scratch_pool), 833 target_rev, 834 svn_depth_infinity, 835 FALSE, 836 target_lock ? target_lock->token : NULL, 837 scratch_pool); 838 if (err) 839 goto abort_report; 840 } 841 else if (target_lock) 842 { 843 /* If this entry is a file node, we just want to report that 844 node's revision. Since we are looking at the actual target 845 of the report (not some file in a subdirectory of a target 846 directory), and that target is a file, we need to pass an 847 empty string to set_path. */ 848 err = reporter->set_path(report_baton, "", target_rev, 849 svn_depth_infinity, 850 FALSE, 851 target_lock ? target_lock->token : NULL, 852 scratch_pool); 853 if (err) 854 goto abort_report; 855 } 856 } 857 858 /* Finish the report, which causes the update editor to be driven. */ 859 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); 860 861 abort_report: 862 /* Clean up the fs transaction. */ 863 if ((fserr = reporter->abort_report(report_baton, scratch_pool))) 864 { 865 fserr = svn_error_quick_wrap(fserr, _("Error aborting report")); 866 svn_error_compose(err, fserr); 867 } 868 return svn_error_trace(err); 869} 870 871/*** Copying stream ***/ 872 873/* A copying stream is a bit like the unix tee utility: 874 * 875 * It reads the SOURCE when asked for data and while returning it, 876 * also writes the same data to TARGET. 877 */ 878struct copying_stream_baton 879{ 880 /* Stream to read input from. */ 881 svn_stream_t *source; 882 883 /* Stream to write all data read to. */ 884 svn_stream_t *target; 885}; 886 887 888/* */ 889static svn_error_t * 890read_handler_copy(void *baton, char *buffer, apr_size_t *len) 891{ 892 struct copying_stream_baton *btn = baton; 893 894 SVN_ERR(svn_stream_read_full(btn->source, buffer, len)); 895 896 return svn_stream_write(btn->target, buffer, len); 897} 898 899/* */ 900static svn_error_t * 901close_handler_copy(void *baton) 902{ 903 struct copying_stream_baton *btn = baton; 904 905 SVN_ERR(svn_stream_close(btn->target)); 906 return svn_stream_close(btn->source); 907} 908 909/* Implements svn_stream_seek_fn_t */ 910static svn_error_t * 911seek_handler_copy(void *baton, const svn_stream_mark_t *mark) 912{ 913 struct copying_stream_baton *btn = baton; 914 915 /* Only reset support. */ 916 if (mark) 917 { 918 return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, 919 NULL, NULL); 920 } 921 else 922 { 923 SVN_ERR(svn_stream_reset(btn->source)); 924 SVN_ERR(svn_stream_reset(btn->target)); 925 } 926 927 return SVN_NO_ERROR; 928} 929 930 931/* Return a stream - allocated in POOL - which reads its input 932 * from SOURCE and, while returning that to the caller, at the 933 * same time writes that to TARGET. 934 */ 935static svn_stream_t * 936copying_stream(svn_stream_t *source, 937 svn_stream_t *target, 938 apr_pool_t *pool) 939{ 940 struct copying_stream_baton *baton; 941 svn_stream_t *stream; 942 943 baton = apr_palloc(pool, sizeof (*baton)); 944 baton->source = source; 945 baton->target = target; 946 947 stream = svn_stream_create(baton, pool); 948 svn_stream_set_read2(stream, NULL /* only full read support */, 949 read_handler_copy); 950 svn_stream_set_close(stream, close_handler_copy); 951 952 if (svn_stream_supports_reset(source) && svn_stream_supports_reset(target)) 953 { 954 svn_stream_set_seek(stream, seek_handler_copy); 955 } 956 957 return stream; 958} 959 960 961/* Set *STREAM to a stream from which the caller can read the pristine text 962 * of the working version of the file at LOCAL_ABSPATH. If the working 963 * version of LOCAL_ABSPATH has no pristine text because it is locally 964 * added, set *STREAM to an empty stream. If the working version of 965 * LOCAL_ABSPATH is not a file, return an error. 966 * 967 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum. 968 * 969 * Arrange for the actual checksum of the text to be calculated and written 970 * into *ACTUAL_MD5_CHECKSUM when the stream is read. 971 */ 972static svn_error_t * 973read_and_checksum_pristine_text(svn_stream_t **stream, 974 const svn_checksum_t **expected_md5_checksum, 975 svn_checksum_t **actual_md5_checksum, 976 svn_wc__db_t *db, 977 const char *local_abspath, 978 apr_pool_t *result_pool, 979 apr_pool_t *scratch_pool) 980{ 981 svn_stream_t *base_stream; 982 983 SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath, 984 result_pool, scratch_pool)); 985 if (base_stream == NULL) 986 { 987 base_stream = svn_stream_empty(result_pool); 988 *expected_md5_checksum = NULL; 989 *actual_md5_checksum = NULL; 990 } 991 else 992 { 993 const svn_checksum_t *expected_md5; 994 995 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, 996 NULL, NULL, NULL, NULL, &expected_md5, 997 NULL, NULL, NULL, NULL, NULL, NULL, 998 NULL, NULL, NULL, NULL, NULL, NULL, 999 NULL, NULL, NULL, NULL, 1000 db, local_abspath, 1001 result_pool, scratch_pool)); 1002 if (expected_md5 == NULL) 1003 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 1004 _("Pristine checksum for file '%s' is missing"), 1005 svn_dirent_local_style(local_abspath, 1006 scratch_pool)); 1007 if (expected_md5->kind != svn_checksum_md5) 1008 SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath, 1009 expected_md5, 1010 result_pool, scratch_pool)); 1011 *expected_md5_checksum = expected_md5; 1012 1013 /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually* 1014 found when the base stream is read. */ 1015 base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum, 1016 NULL, svn_checksum_md5, TRUE, 1017 result_pool); 1018 } 1019 1020 *stream = base_stream; 1021 return SVN_NO_ERROR; 1022} 1023 1024typedef struct open_txdelta_stream_baton_t 1025{ 1026 svn_boolean_t need_reset; 1027 svn_stream_t *base_stream; 1028 svn_stream_t *local_stream; 1029} open_txdelta_stream_baton_t; 1030 1031/* Implements svn_txdelta_stream_open_func_t */ 1032static svn_error_t * 1033open_txdelta_stream(svn_txdelta_stream_t **txdelta_stream_p, 1034 void *baton, 1035 apr_pool_t *result_pool, 1036 apr_pool_t *scratch_pool) 1037{ 1038 open_txdelta_stream_baton_t *b = baton; 1039 1040 if (b->need_reset) 1041 { 1042 /* Under rare circumstances, we can be restarted and would need to 1043 * supply the delta stream again. In this case, reset both streams. */ 1044 SVN_ERR(svn_stream_reset(b->base_stream)); 1045 SVN_ERR(svn_stream_reset(b->local_stream)); 1046 } 1047 1048 svn_txdelta2(txdelta_stream_p, b->base_stream, b->local_stream, 1049 FALSE, result_pool); 1050 b->need_reset = TRUE; 1051 return SVN_NO_ERROR; 1052} 1053 1054svn_error_t * 1055svn_wc__internal_transmit_text_deltas(svn_stream_t *tempstream, 1056 const svn_checksum_t **new_text_base_md5_checksum, 1057 const svn_checksum_t **new_text_base_sha1_checksum, 1058 svn_wc__db_t *db, 1059 const char *local_abspath, 1060 svn_boolean_t fulltext, 1061 const svn_delta_editor_t *editor, 1062 void *file_baton, 1063 apr_pool_t *result_pool, 1064 apr_pool_t *scratch_pool) 1065{ 1066 const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */ 1067 svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */ 1068 svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */ 1069 svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */ 1070 svn_wc__db_install_data_t *install_data = NULL; 1071 svn_error_t *err; 1072 svn_error_t *err2; 1073 svn_stream_t *base_stream; /* delta source */ 1074 svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */ 1075 1076 /* Translated input */ 1077 SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db, 1078 local_abspath, local_abspath, 1079 SVN_WC_TRANSLATE_TO_NF, 1080 scratch_pool, scratch_pool)); 1081 1082 /* If the caller wants a copy of the working file translated to 1083 * repository-normal form, make the copy by tee-ing the TEMPSTREAM. 1084 * This is only needed for the 1.6 API. Even when using the 1.6 API 1085 * this temporary file is not used by the functions that would have used 1086 * it when using the 1.6 code. It's possible that 3rd party users (if 1087 * there are any) might expect this file to be a text-base. */ 1088 if (tempstream) 1089 { 1090 /* Wrap the translated stream with a new stream that writes the 1091 translated contents into the new text base file as we read from it. 1092 Note that the new text base file will be closed when the new stream 1093 is closed. */ 1094 local_stream = copying_stream(local_stream, tempstream, scratch_pool); 1095 } 1096 if (new_text_base_sha1_checksum) 1097 { 1098 svn_stream_t *new_pristine_stream; 1099 1100 SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream, 1101 &install_data, 1102 &local_sha1_checksum, NULL, 1103 db, local_abspath, 1104 scratch_pool, scratch_pool)); 1105 local_stream = copying_stream(local_stream, new_pristine_stream, 1106 scratch_pool); 1107 } 1108 1109 /* If sending a full text is requested, or if there is no pristine text 1110 * (e.g. the node is locally added), then set BASE_STREAM to an empty 1111 * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL. 1112 * 1113 * Otherwise, set BASE_STREAM to a stream providing the base (source) text 1114 * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum, 1115 * and arrange for its VERIFY_CHECKSUM to be calculated later. */ 1116 if (! fulltext) 1117 { 1118 /* We will be computing a delta against the pristine contents */ 1119 /* We need the expected checksum to be an MD-5 checksum rather than a 1120 * SHA-1 because we want to pass it to apply_textdelta(). */ 1121 SVN_ERR(read_and_checksum_pristine_text(&base_stream, 1122 &expected_md5_checksum, 1123 &verify_checksum, 1124 db, local_abspath, 1125 scratch_pool, scratch_pool)); 1126 } 1127 else 1128 { 1129 /* Send a fulltext. */ 1130 base_stream = svn_stream_empty(scratch_pool); 1131 expected_md5_checksum = NULL; 1132 verify_checksum = NULL; 1133 } 1134 1135 /* Arrange the stream to calculate the resulting MD5. */ 1136 local_stream = svn_stream_checksummed2(local_stream, &local_md5_checksum, 1137 NULL, svn_checksum_md5, TRUE, 1138 scratch_pool); 1139 1140 /* Tell the editor to apply a textdelta stream to the file baton. */ 1141 { 1142 open_txdelta_stream_baton_t baton = { 0 }; 1143 1144 /* apply_textdelta_stream() is working against a base with this checksum */ 1145 const char *base_digest_hex = NULL; 1146 1147 if (expected_md5_checksum) 1148 /* ### Why '..._display()'? expected_md5_checksum should never be all- 1149 * zero, but if it is, we would want to pass NULL not an all-zero 1150 * digest to apply_textdelta_stream(), wouldn't we? */ 1151 base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum, 1152 scratch_pool); 1153 1154 baton.need_reset = FALSE; 1155 baton.base_stream = svn_stream_disown(base_stream, scratch_pool); 1156 baton.local_stream = svn_stream_disown(local_stream, scratch_pool); 1157 err = editor->apply_textdelta_stream(editor, file_baton, base_digest_hex, 1158 open_txdelta_stream, &baton, 1159 scratch_pool); 1160 } 1161 1162 /* Close the two streams to force writing the digest */ 1163 err2 = svn_stream_close(base_stream); 1164 if (err2) 1165 { 1166 /* Set verify_checksum to NULL if svn_stream_close() returns error 1167 because checksum will be uninitialized in this case. */ 1168 verify_checksum = NULL; 1169 err = svn_error_compose_create(err, err2); 1170 } 1171 1172 err = svn_error_compose_create(err, svn_stream_close(local_stream)); 1173 1174 /* If we have an error, it may be caused by a corrupt text base, 1175 so check the checksum. */ 1176 if (expected_md5_checksum && verify_checksum 1177 && !svn_checksum_match(expected_md5_checksum, verify_checksum)) 1178 { 1179 /* The entry checksum does not match the actual text 1180 base checksum. Extreme badness. Of course, 1181 theoretically we could just switch to 1182 fulltext transmission here, and everything would 1183 work fine; after all, we're going to replace the 1184 text base with a new one in a moment anyway, and 1185 we'd fix the checksum then. But it's better to 1186 error out. People should know that their text 1187 bases are getting corrupted, so they can 1188 investigate. Other commands could be affected, 1189 too, such as `svn diff'. */ 1190 1191 err = svn_error_compose_create( 1192 svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum, 1193 scratch_pool, 1194 _("Checksum mismatch for text base of '%s'"), 1195 svn_dirent_local_style(local_abspath, 1196 scratch_pool)), 1197 err); 1198 1199 return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL); 1200 } 1201 1202 /* Now, handle that delta transmission error if any, so we can stop 1203 thinking about it after this point. */ 1204 SVN_ERR_W(err, apr_psprintf(scratch_pool, 1205 _("While preparing '%s' for commit"), 1206 svn_dirent_local_style(local_abspath, 1207 scratch_pool))); 1208 1209 if (new_text_base_md5_checksum) 1210 *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum, 1211 result_pool); 1212 if (new_text_base_sha1_checksum) 1213 { 1214 SVN_ERR(svn_wc__db_pristine_install(install_data, 1215 local_sha1_checksum, 1216 local_md5_checksum, 1217 scratch_pool)); 1218 *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum, 1219 result_pool); 1220 } 1221 1222 /* Close the file baton, and get outta here. */ 1223 return svn_error_trace( 1224 editor->close_file(file_baton, 1225 svn_checksum_to_cstring(local_md5_checksum, 1226 scratch_pool), 1227 scratch_pool)); 1228} 1229 1230svn_error_t * 1231svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum, 1232 const svn_checksum_t **new_text_base_sha1_checksum, 1233 svn_wc_context_t *wc_ctx, 1234 const char *local_abspath, 1235 svn_boolean_t fulltext, 1236 const svn_delta_editor_t *editor, 1237 void *file_baton, 1238 apr_pool_t *result_pool, 1239 apr_pool_t *scratch_pool) 1240{ 1241 return svn_wc__internal_transmit_text_deltas(NULL, 1242 new_text_base_md5_checksum, 1243 new_text_base_sha1_checksum, 1244 wc_ctx->db, local_abspath, 1245 fulltext, editor, 1246 file_baton, result_pool, 1247 scratch_pool); 1248} 1249 1250svn_error_t * 1251svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, 1252 const char *local_abspath, 1253 const svn_delta_editor_t *editor, 1254 void *baton, 1255 apr_pool_t *scratch_pool) 1256{ 1257 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1258 int i; 1259 apr_array_header_t *propmods; 1260 svn_node_kind_t kind; 1261 1262 SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, 1263 FALSE /* allow_missing */, 1264 FALSE /* show_deleted */, 1265 FALSE /* show_hidden */, 1266 iterpool)); 1267 1268 if (kind == svn_node_none) 1269 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1270 _("The node '%s' was not found."), 1271 svn_dirent_local_style(local_abspath, iterpool)); 1272 1273 /* Get an array of local changes by comparing the hashes. */ 1274 SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath, 1275 scratch_pool, iterpool)); 1276 1277 /* Apply each local change to the baton */ 1278 for (i = 0; i < propmods->nelts; i++) 1279 { 1280 const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t); 1281 1282 svn_pool_clear(iterpool); 1283 1284 if (kind == svn_node_file) 1285 SVN_ERR(editor->change_file_prop(baton, p->name, p->value, 1286 iterpool)); 1287 else 1288 SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, 1289 iterpool)); 1290 } 1291 1292 svn_pool_destroy(iterpool); 1293 return SVN_NO_ERROR; 1294} 1295 1296svn_error_t * 1297svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx, 1298 const char *local_abspath, 1299 const svn_delta_editor_t *editor, 1300 void *baton, 1301 apr_pool_t *scratch_pool) 1302{ 1303 return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath, 1304 editor, baton, scratch_pool); 1305} 1306