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