adm_crawler.c revision 299742
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 910/* Return a stream - allocated in POOL - which reads its input 911 * from SOURCE and, while returning that to the caller, at the 912 * same time writes that to TARGET. 913 */ 914static svn_stream_t * 915copying_stream(svn_stream_t *source, 916 svn_stream_t *target, 917 apr_pool_t *pool) 918{ 919 struct copying_stream_baton *baton; 920 svn_stream_t *stream; 921 922 baton = apr_palloc(pool, sizeof (*baton)); 923 baton->source = source; 924 baton->target = target; 925 926 stream = svn_stream_create(baton, pool); 927 svn_stream_set_read2(stream, NULL /* only full read support */, 928 read_handler_copy); 929 svn_stream_set_close(stream, close_handler_copy); 930 931 return stream; 932} 933 934 935/* Set *STREAM to a stream from which the caller can read the pristine text 936 * of the working version of the file at LOCAL_ABSPATH. If the working 937 * version of LOCAL_ABSPATH has no pristine text because it is locally 938 * added, set *STREAM to an empty stream. If the working version of 939 * LOCAL_ABSPATH is not a file, return an error. 940 * 941 * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum. 942 * 943 * Arrange for the actual checksum of the text to be calculated and written 944 * into *ACTUAL_MD5_CHECKSUM when the stream is read. 945 */ 946static svn_error_t * 947read_and_checksum_pristine_text(svn_stream_t **stream, 948 const svn_checksum_t **expected_md5_checksum, 949 svn_checksum_t **actual_md5_checksum, 950 svn_wc__db_t *db, 951 const char *local_abspath, 952 apr_pool_t *result_pool, 953 apr_pool_t *scratch_pool) 954{ 955 svn_stream_t *base_stream; 956 957 SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath, 958 result_pool, scratch_pool)); 959 if (base_stream == NULL) 960 { 961 base_stream = svn_stream_empty(result_pool); 962 *expected_md5_checksum = NULL; 963 *actual_md5_checksum = NULL; 964 } 965 else 966 { 967 const svn_checksum_t *expected_md5; 968 969 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, 970 NULL, NULL, NULL, NULL, &expected_md5, 971 NULL, NULL, NULL, NULL, NULL, NULL, 972 NULL, NULL, NULL, NULL, NULL, NULL, 973 NULL, NULL, NULL, NULL, 974 db, local_abspath, 975 result_pool, scratch_pool)); 976 if (expected_md5 == NULL) 977 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 978 _("Pristine checksum for file '%s' is missing"), 979 svn_dirent_local_style(local_abspath, 980 scratch_pool)); 981 if (expected_md5->kind != svn_checksum_md5) 982 SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath, 983 expected_md5, 984 result_pool, scratch_pool)); 985 *expected_md5_checksum = expected_md5; 986 987 /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually* 988 found when the base stream is read. */ 989 base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum, 990 NULL, svn_checksum_md5, TRUE, 991 result_pool); 992 } 993 994 *stream = base_stream; 995 return SVN_NO_ERROR; 996} 997 998 999svn_error_t * 1000svn_wc__internal_transmit_text_deltas(const char **tempfile, 1001 const svn_checksum_t **new_text_base_md5_checksum, 1002 const svn_checksum_t **new_text_base_sha1_checksum, 1003 svn_wc__db_t *db, 1004 const char *local_abspath, 1005 svn_boolean_t fulltext, 1006 const svn_delta_editor_t *editor, 1007 void *file_baton, 1008 apr_pool_t *result_pool, 1009 apr_pool_t *scratch_pool) 1010{ 1011 svn_txdelta_window_handler_t handler; 1012 void *wh_baton; 1013 const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */ 1014 svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */ 1015 svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */ 1016 svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */ 1017 svn_wc__db_install_data_t *install_data = NULL; 1018 svn_error_t *err; 1019 svn_error_t *err2; 1020 svn_stream_t *base_stream; /* delta source */ 1021 svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */ 1022 1023 /* Translated input */ 1024 SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db, 1025 local_abspath, local_abspath, 1026 SVN_WC_TRANSLATE_TO_NF, 1027 scratch_pool, scratch_pool)); 1028 1029 /* If the caller wants a copy of the working file translated to 1030 * repository-normal form, make the copy by tee-ing the stream and set 1031 * *TEMPFILE to the path to it. This is only needed for the 1.6 API, 1032 * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file 1033 * is not used by the functions that would have used it when using 1034 * the 1.6 code. It's possible that 3rd party users (if there are any) 1035 * might expect this file to be a text-base. */ 1036 if (tempfile) 1037 { 1038 svn_stream_t *tempstream; 1039 1040 /* It can't be the same location as in 1.6 because the admin directory 1041 no longer exists. */ 1042 SVN_ERR(svn_stream_open_unique(&tempstream, tempfile, 1043 NULL, svn_io_file_del_none, 1044 result_pool, scratch_pool)); 1045 1046 /* Wrap the translated stream with a new stream that writes the 1047 translated contents into the new text base file as we read from it. 1048 Note that the new text base file will be closed when the new stream 1049 is closed. */ 1050 local_stream = copying_stream(local_stream, tempstream, scratch_pool); 1051 } 1052 if (new_text_base_sha1_checksum) 1053 { 1054 svn_stream_t *new_pristine_stream; 1055 1056 SVN_ERR(svn_wc__db_pristine_prepare_install(&new_pristine_stream, 1057 &install_data, 1058 &local_sha1_checksum, NULL, 1059 db, local_abspath, 1060 scratch_pool, scratch_pool)); 1061 local_stream = copying_stream(local_stream, new_pristine_stream, 1062 scratch_pool); 1063 } 1064 1065 /* If sending a full text is requested, or if there is no pristine text 1066 * (e.g. the node is locally added), then set BASE_STREAM to an empty 1067 * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL. 1068 * 1069 * Otherwise, set BASE_STREAM to a stream providing the base (source) text 1070 * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum, 1071 * and arrange for its VERIFY_CHECKSUM to be calculated later. */ 1072 if (! fulltext) 1073 { 1074 /* We will be computing a delta against the pristine contents */ 1075 /* We need the expected checksum to be an MD-5 checksum rather than a 1076 * SHA-1 because we want to pass it to apply_textdelta(). */ 1077 SVN_ERR(read_and_checksum_pristine_text(&base_stream, 1078 &expected_md5_checksum, 1079 &verify_checksum, 1080 db, local_abspath, 1081 scratch_pool, scratch_pool)); 1082 } 1083 else 1084 { 1085 /* Send a fulltext. */ 1086 base_stream = svn_stream_empty(scratch_pool); 1087 expected_md5_checksum = NULL; 1088 verify_checksum = NULL; 1089 } 1090 1091 /* Tell the editor that we're about to apply a textdelta to the 1092 file baton; the editor returns to us a window consumer and baton. */ 1093 { 1094 /* apply_textdelta() is working against a base with this checksum */ 1095 const char *base_digest_hex = NULL; 1096 1097 if (expected_md5_checksum) 1098 /* ### Why '..._display()'? expected_md5_checksum should never be all- 1099 * zero, but if it is, we would want to pass NULL not an all-zero 1100 * digest to apply_textdelta(), wouldn't we? */ 1101 base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum, 1102 scratch_pool); 1103 1104 SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool, 1105 &handler, &wh_baton)); 1106 } 1107 1108 /* Run diff processing, throwing windows at the handler. */ 1109 err = svn_txdelta_run(base_stream, local_stream, 1110 handler, wh_baton, 1111 svn_checksum_md5, &local_md5_checksum, 1112 NULL, NULL, 1113 scratch_pool, scratch_pool); 1114 1115 /* Close the two streams to force writing the digest */ 1116 err2 = svn_stream_close(base_stream); 1117 if (err2) 1118 { 1119 /* Set verify_checksum to NULL if svn_stream_close() returns error 1120 because checksum will be uninitialized in this case. */ 1121 verify_checksum = NULL; 1122 err = svn_error_compose_create(err, err2); 1123 } 1124 1125 err = svn_error_compose_create(err, svn_stream_close(local_stream)); 1126 1127 /* If we have an error, it may be caused by a corrupt text base, 1128 so check the checksum. */ 1129 if (expected_md5_checksum && verify_checksum 1130 && !svn_checksum_match(expected_md5_checksum, verify_checksum)) 1131 { 1132 /* The entry checksum does not match the actual text 1133 base checksum. Extreme badness. Of course, 1134 theoretically we could just switch to 1135 fulltext transmission here, and everything would 1136 work fine; after all, we're going to replace the 1137 text base with a new one in a moment anyway, and 1138 we'd fix the checksum then. But it's better to 1139 error out. People should know that their text 1140 bases are getting corrupted, so they can 1141 investigate. Other commands could be affected, 1142 too, such as `svn diff'. */ 1143 1144 if (tempfile) 1145 err = svn_error_compose_create( 1146 err, 1147 svn_io_remove_file2(*tempfile, TRUE, scratch_pool)); 1148 1149 err = svn_error_compose_create( 1150 svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum, 1151 scratch_pool, 1152 _("Checksum mismatch for text base of '%s'"), 1153 svn_dirent_local_style(local_abspath, 1154 scratch_pool)), 1155 err); 1156 1157 return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL); 1158 } 1159 1160 /* Now, handle that delta transmission error if any, so we can stop 1161 thinking about it after this point. */ 1162 SVN_ERR_W(err, apr_psprintf(scratch_pool, 1163 _("While preparing '%s' for commit"), 1164 svn_dirent_local_style(local_abspath, 1165 scratch_pool))); 1166 1167 if (new_text_base_md5_checksum) 1168 *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum, 1169 result_pool); 1170 if (new_text_base_sha1_checksum) 1171 { 1172 SVN_ERR(svn_wc__db_pristine_install(install_data, 1173 local_sha1_checksum, 1174 local_md5_checksum, 1175 scratch_pool)); 1176 *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum, 1177 result_pool); 1178 } 1179 1180 /* Close the file baton, and get outta here. */ 1181 return svn_error_trace( 1182 editor->close_file(file_baton, 1183 svn_checksum_to_cstring(local_md5_checksum, 1184 scratch_pool), 1185 scratch_pool)); 1186} 1187 1188svn_error_t * 1189svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum, 1190 const svn_checksum_t **new_text_base_sha1_checksum, 1191 svn_wc_context_t *wc_ctx, 1192 const char *local_abspath, 1193 svn_boolean_t fulltext, 1194 const svn_delta_editor_t *editor, 1195 void *file_baton, 1196 apr_pool_t *result_pool, 1197 apr_pool_t *scratch_pool) 1198{ 1199 return svn_wc__internal_transmit_text_deltas(NULL, 1200 new_text_base_md5_checksum, 1201 new_text_base_sha1_checksum, 1202 wc_ctx->db, local_abspath, 1203 fulltext, editor, 1204 file_baton, result_pool, 1205 scratch_pool); 1206} 1207 1208svn_error_t * 1209svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, 1210 const char *local_abspath, 1211 const svn_delta_editor_t *editor, 1212 void *baton, 1213 apr_pool_t *scratch_pool) 1214{ 1215 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1216 int i; 1217 apr_array_header_t *propmods; 1218 svn_node_kind_t kind; 1219 1220 SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, 1221 FALSE /* allow_missing */, 1222 FALSE /* show_deleted */, 1223 FALSE /* show_hidden */, 1224 iterpool)); 1225 1226 if (kind == svn_node_none) 1227 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1228 _("The node '%s' was not found."), 1229 svn_dirent_local_style(local_abspath, iterpool)); 1230 1231 /* Get an array of local changes by comparing the hashes. */ 1232 SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath, 1233 scratch_pool, iterpool)); 1234 1235 /* Apply each local change to the baton */ 1236 for (i = 0; i < propmods->nelts; i++) 1237 { 1238 const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t); 1239 1240 svn_pool_clear(iterpool); 1241 1242 if (kind == svn_node_file) 1243 SVN_ERR(editor->change_file_prop(baton, p->name, p->value, 1244 iterpool)); 1245 else 1246 SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, 1247 iterpool)); 1248 } 1249 1250 svn_pool_destroy(iterpool); 1251 return SVN_NO_ERROR; 1252} 1253 1254svn_error_t * 1255svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx, 1256 const char *local_abspath, 1257 const svn_delta_editor_t *editor, 1258 void *baton, 1259 apr_pool_t *scratch_pool) 1260{ 1261 return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath, 1262 editor, baton, scratch_pool); 1263} 1264