revert.c revision 299742
1/* 2 * revert.c: Handling of the in-wc side of the revert operation 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#include <string.h> 27#include <stdlib.h> 28 29#include <apr_pools.h> 30#include <apr_tables.h> 31 32#include "svn_types.h" 33#include "svn_pools.h" 34#include "svn_string.h" 35#include "svn_error.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_hash.h" 39#include "svn_wc.h" 40#include "svn_io.h" 41 42#include "wc.h" 43#include "adm_files.h" 44#include "workqueue.h" 45 46#include "svn_private_config.h" 47#include "private/svn_io_private.h" 48#include "private/svn_wc_private.h" 49#include "private/svn_sorts_private.h" 50 51/* Thoughts on Reversion. 52 53 What does is mean to revert a given PATH in a tree? We'll 54 consider things by their modifications. 55 56 Adds 57 58 - For files, svn_wc_remove_from_revision_control(), baby. 59 60 - Added directories may contain nothing but added children, and 61 reverting the addition of a directory necessarily means reverting 62 the addition of all the directory's children. Again, 63 svn_wc_remove_from_revision_control() should do the trick. 64 65 Deletes 66 67 - Restore properties to their unmodified state. 68 69 - For files, restore the pristine contents, and reset the schedule 70 to 'normal'. 71 72 - For directories, reset the schedule to 'normal'. All children 73 of a directory marked for deletion must also be marked for 74 deletion, but it's okay for those children to remain deleted even 75 if their parent directory is restored. That's what the 76 recursive flag is for. 77 78 Replaces 79 80 - Restore properties to their unmodified state. 81 82 - For files, restore the pristine contents, and reset the schedule 83 to 'normal'. 84 85 - For directories, reset the schedule to normal. A replaced 86 directory can have deleted children (left over from the initial 87 deletion), replaced children (children of the initial deletion 88 now re-added), and added children (new entries under the 89 replaced directory). Since this is technically an addition, it 90 necessitates recursion. 91 92 Modifications 93 94 - Restore properties and, for files, contents to their unmodified 95 state. 96 97*/ 98 99 100/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set 101 * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */ 102static svn_error_t * 103remove_conflict_file(svn_boolean_t *notify_required, 104 const char *conflict_abspath, 105 const char *local_abspath, 106 apr_pool_t *scratch_pool) 107{ 108 if (conflict_abspath) 109 { 110 svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE, 111 scratch_pool); 112 if (err) 113 svn_error_clear(err); 114 else 115 *notify_required = TRUE; 116 } 117 118 return SVN_NO_ERROR; 119} 120 121 122/* Sort copied children obtained from the revert list based on 123 * their paths in descending order (longest paths first). */ 124static int 125compare_revert_list_copied_children(const void *a, const void *b) 126{ 127 const svn_wc__db_revert_list_copied_child_info_t * const *ca = a; 128 const svn_wc__db_revert_list_copied_child_info_t * const *cb = b; 129 int i; 130 131 i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath); 132 133 /* Reverse the result of svn_path_compare_paths() to achieve 134 * descending order. */ 135 return -i; 136} 137 138 139/* Remove all reverted copied children from the directory at LOCAL_ABSPATH. 140 * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF 141 * should be set if LOCAL_ABSPATH is itself a reverted copy). 142 * 143 * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether 144 * LOCAL_ABSPATH itself was removed. 145 * 146 * All reverted copied file children are removed from disk. Reverted copied 147 * directories left empty as a result are also removed from disk. 148 */ 149static svn_error_t * 150revert_restore_handle_copied_dirs(svn_boolean_t *removed_self, 151 svn_wc__db_t *db, 152 const char *local_abspath, 153 svn_boolean_t remove_self, 154 svn_cancel_func_t cancel_func, 155 void *cancel_baton, 156 apr_pool_t *scratch_pool) 157{ 158 apr_array_header_t *copied_children; 159 svn_wc__db_revert_list_copied_child_info_t *child_info; 160 int i; 161 svn_node_kind_t on_disk; 162 apr_pool_t *iterpool; 163 svn_error_t *err; 164 165 if (removed_self) 166 *removed_self = FALSE; 167 168 SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children, 169 db, local_abspath, 170 scratch_pool, 171 scratch_pool)); 172 iterpool = svn_pool_create(scratch_pool); 173 174 /* Remove all copied file children. */ 175 for (i = 0; i < copied_children->nelts; i++) 176 { 177 child_info = APR_ARRAY_IDX( 178 copied_children, i, 179 svn_wc__db_revert_list_copied_child_info_t *); 180 181 if (cancel_func) 182 SVN_ERR(cancel_func(cancel_baton)); 183 184 if (child_info->kind != svn_node_file) 185 continue; 186 187 svn_pool_clear(iterpool); 188 189 /* Make sure what we delete from disk is really a file. */ 190 SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool)); 191 if (on_disk != svn_node_file) 192 continue; 193 194 SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool)); 195 } 196 197 /* Delete every empty child directory. 198 * We cannot delete children recursively since we want to keep any files 199 * that still exist on disk (e.g. unversioned files within the copied tree). 200 * So sort the children list such that longest paths come first and try to 201 * remove each child directory in order. */ 202 svn_sort__array(copied_children, compare_revert_list_copied_children); 203 for (i = 0; i < copied_children->nelts; i++) 204 { 205 child_info = APR_ARRAY_IDX( 206 copied_children, i, 207 svn_wc__db_revert_list_copied_child_info_t *); 208 209 if (cancel_func) 210 SVN_ERR(cancel_func(cancel_baton)); 211 212 if (child_info->kind != svn_node_dir) 213 continue; 214 215 svn_pool_clear(iterpool); 216 217 err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool); 218 if (err) 219 { 220 if (APR_STATUS_IS_ENOENT(err->apr_err) || 221 SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) || 222 APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 223 svn_error_clear(err); 224 else 225 return svn_error_trace(err); 226 } 227 } 228 229 if (remove_self) 230 { 231 /* Delete LOCAL_ABSPATH itself if no children are left. */ 232 err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); 233 if (err) 234 { 235 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 236 svn_error_clear(err); 237 else 238 return svn_error_trace(err); 239 } 240 else if (removed_self) 241 *removed_self = TRUE; 242 } 243 244 svn_pool_destroy(iterpool); 245 246 return SVN_NO_ERROR; 247} 248 249/* Forward definition */ 250static svn_error_t * 251revert_wc_data(svn_boolean_t *run_wq, 252 svn_boolean_t *notify_required, 253 svn_wc__db_t *db, 254 const char *local_abspath, 255 svn_wc__db_status_t status, 256 svn_node_kind_t kind, 257 svn_node_kind_t reverted_kind, 258 svn_filesize_t recorded_size, 259 apr_time_t recorded_time, 260 svn_boolean_t copied_here, 261 svn_boolean_t use_commit_times, 262 svn_cancel_func_t cancel_func, 263 void *cancel_baton, 264 apr_pool_t *scratch_pool); 265 266/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the 267 versioned tree. This function is called after svn_wc__db_op_revert 268 has done the database revert and created the revert list. Notifies 269 for all paths equal to or below LOCAL_ABSPATH that are reverted. 270 271 REVERT_ROOT is true for explicit revert targets and FALSE for targets 272 reached via recursion. 273 274 Sets *RUN_WQ to TRUE when the caller should (eventually) run the workqueue. 275 (The function sets it to FALSE when it has run the WQ itself) 276 277 If INFO is NULL, LOCAL_ABSPATH doesn't exist in DB. Otherwise INFO 278 specifies the state of LOCAL_ABSPATH in DB. 279 */ 280static svn_error_t * 281revert_restore(svn_boolean_t *run_wq, 282 svn_wc__db_t *db, 283 const char *local_abspath, 284 svn_depth_t depth, 285 svn_boolean_t metadata_only, 286 svn_boolean_t use_commit_times, 287 svn_boolean_t revert_root, 288 const struct svn_wc__db_info_t *info, 289 svn_cancel_func_t cancel_func, 290 void *cancel_baton, 291 svn_wc_notify_func2_t notify_func, 292 void *notify_baton, 293 apr_pool_t *scratch_pool) 294{ 295 svn_wc__db_status_t status; 296 svn_node_kind_t kind; 297 svn_boolean_t notify_required; 298 const apr_array_header_t *conflict_files; 299 svn_filesize_t recorded_size; 300 apr_time_t recorded_time; 301 svn_boolean_t copied_here; 302 svn_node_kind_t reverted_kind; 303 if (cancel_func) 304 SVN_ERR(cancel_func(cancel_baton)); 305 306 if (!revert_root) 307 { 308 svn_boolean_t is_wcroot; 309 310 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); 311 if (is_wcroot) 312 { 313 /* Issue #4162: Obstructing working copy. We can't access the working 314 copy data from the parent working copy for this node by just using 315 local_abspath */ 316 317 if (notify_func) 318 { 319 svn_wc_notify_t *notify = 320 svn_wc_create_notify( 321 local_abspath, 322 svn_wc_notify_update_skip_obstruction, 323 scratch_pool); 324 325 notify_func(notify_baton, notify, scratch_pool); 326 } 327 328 return SVN_NO_ERROR; /* We don't revert obstructing working copies */ 329 } 330 } 331 332 SVN_ERR(svn_wc__db_revert_list_read(¬ify_required, 333 &conflict_files, 334 &copied_here, &reverted_kind, 335 db, local_abspath, 336 scratch_pool, scratch_pool)); 337 338 if (info) 339 { 340 status = info->status; 341 kind = info->kind; 342 recorded_size = info->recorded_size; 343 recorded_time = info->recorded_time; 344 } 345 else 346 { 347 if (!copied_here) 348 { 349 if (notify_func && notify_required) 350 notify_func(notify_baton, 351 svn_wc_create_notify(local_abspath, 352 svn_wc_notify_revert, 353 scratch_pool), 354 scratch_pool); 355 356 if (notify_func) 357 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, 358 db, local_abspath, 359 scratch_pool)); 360 return SVN_NO_ERROR; 361 } 362 else 363 { 364 /* ### Initialise to values which prevent the code below from 365 * ### trying to restore anything to disk. 366 * ### 'status' should be status_unknown but that doesn't exist. */ 367 status = svn_wc__db_status_normal; 368 kind = svn_node_unknown; 369 recorded_size = SVN_INVALID_FILESIZE; 370 recorded_time = 0; 371 } 372 } 373 374 if (!metadata_only) 375 { 376 SVN_ERR(revert_wc_data(run_wq, 377 ¬ify_required, 378 db, local_abspath, status, kind, 379 reverted_kind, recorded_size, recorded_time, 380 copied_here, use_commit_times, 381 cancel_func, cancel_baton, scratch_pool)); 382 } 383 384 /* We delete these marker files even though they are not strictly metadata. 385 But for users that use revert as an API with metadata_only, these are. */ 386 if (conflict_files) 387 { 388 int i; 389 for (i = 0; i < conflict_files->nelts; i++) 390 { 391 SVN_ERR(remove_conflict_file(¬ify_required, 392 APR_ARRAY_IDX(conflict_files, i, 393 const char *), 394 local_abspath, scratch_pool)); 395 } 396 } 397 398 if (notify_func && notify_required) 399 notify_func(notify_baton, 400 svn_wc_create_notify(local_abspath, svn_wc_notify_revert, 401 scratch_pool), 402 scratch_pool); 403 404 if (depth == svn_depth_infinity && kind == svn_node_dir) 405 { 406 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 407 apr_hash_t *children, *conflicts; 408 apr_hash_index_t *hi; 409 410 SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE, 411 cancel_func, cancel_baton, 412 iterpool)); 413 414 SVN_ERR(svn_wc__db_read_children_info(&children, &conflicts, 415 db, local_abspath, FALSE, 416 scratch_pool, iterpool)); 417 418 for (hi = apr_hash_first(scratch_pool, children); 419 hi; 420 hi = apr_hash_next(hi)) 421 { 422 const char *child_name = apr_hash_this_key(hi); 423 const char *child_abspath; 424 425 svn_pool_clear(iterpool); 426 427 child_abspath = svn_dirent_join(local_abspath, child_name, iterpool); 428 429 SVN_ERR(revert_restore(run_wq, 430 db, child_abspath, depth, metadata_only, 431 use_commit_times, FALSE /* revert root */, 432 apr_hash_this_val(hi), 433 cancel_func, cancel_baton, 434 notify_func, notify_baton, 435 iterpool)); 436 } 437 438 /* Run the queue per directory */ 439 if (*run_wq) 440 { 441 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 442 iterpool)); 443 *run_wq = FALSE; 444 } 445 446 svn_pool_destroy(iterpool); 447 } 448 449 if (notify_func && (revert_root || kind == svn_node_dir)) 450 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, 451 db, local_abspath, scratch_pool)); 452 453 return SVN_NO_ERROR; 454} 455 456/* Perform the in-working copy revert of LOCAL_ABSPATH, to what is stored in DB */ 457static svn_error_t * 458revert_wc_data(svn_boolean_t *run_wq, 459 svn_boolean_t *notify_required, 460 svn_wc__db_t *db, 461 const char *local_abspath, 462 svn_wc__db_status_t status, 463 svn_node_kind_t kind, 464 svn_node_kind_t reverted_kind, 465 svn_filesize_t recorded_size, 466 apr_time_t recorded_time, 467 svn_boolean_t copied_here, 468 svn_boolean_t use_commit_times, 469 svn_cancel_func_t cancel_func, 470 void *cancel_baton, 471 apr_pool_t *scratch_pool) 472{ 473 svn_error_t *err; 474 apr_finfo_t finfo; 475 svn_node_kind_t on_disk; 476#ifdef HAVE_SYMLINK 477 svn_boolean_t special; 478#endif 479 480 /* Would be nice to use svn_io_dirent2_t here, but the performance 481 improvement that provides doesn't work, because we need the read 482 only and executable bits later on, in the most likely code path */ 483 err = svn_io_stat(&finfo, local_abspath, 484 APR_FINFO_TYPE | APR_FINFO_LINK 485 | APR_FINFO_SIZE | APR_FINFO_MTIME 486 | SVN__APR_FINFO_EXECUTABLE 487 | SVN__APR_FINFO_READONLY, 488 scratch_pool); 489 490 if (err && (APR_STATUS_IS_ENOENT(err->apr_err) 491 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 492 { 493 svn_error_clear(err); 494 on_disk = svn_node_none; 495#ifdef HAVE_SYMLINK 496 special = FALSE; 497#endif 498 } 499 else if (!err) 500 { 501 if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) 502 on_disk = svn_node_file; 503 else if (finfo.filetype == APR_DIR) 504 on_disk = svn_node_dir; 505 else 506 on_disk = svn_node_unknown; 507 508#ifdef HAVE_SYMLINK 509 special = (finfo.filetype == APR_LNK); 510#endif 511 } 512 else 513 return svn_error_trace(err); 514 515 if (copied_here) 516 { 517 /* The revert target itself is the op-root of a copy. */ 518 if (reverted_kind == svn_node_file && on_disk == svn_node_file) 519 { 520 SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); 521 on_disk = svn_node_none; 522 } 523 else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir) 524 { 525 svn_boolean_t removed; 526 527 SVN_ERR(revert_restore_handle_copied_dirs(&removed, db, 528 local_abspath, TRUE, 529 cancel_func, cancel_baton, 530 scratch_pool)); 531 if (removed) 532 on_disk = svn_node_none; 533 } 534 } 535 536 /* If we expect a versioned item to be present then check that any 537 item on disk matches the versioned item, if it doesn't match then 538 fix it or delete it. */ 539 if (on_disk != svn_node_none 540 && status != svn_wc__db_status_server_excluded 541 && status != svn_wc__db_status_deleted 542 && status != svn_wc__db_status_excluded 543 && status != svn_wc__db_status_not_present) 544 { 545 if (on_disk == svn_node_dir && kind != svn_node_dir) 546 { 547 SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE, 548 cancel_func, cancel_baton, scratch_pool)); 549 on_disk = svn_node_none; 550 } 551 else if (on_disk == svn_node_file && kind != svn_node_file) 552 { 553#ifdef HAVE_SYMLINK 554 /* Preserve symlinks pointing at directories. Changes on the 555 * directory node have been reverted. The symlink should remain. */ 556 if (!(special && kind == svn_node_dir)) 557#endif 558 { 559 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); 560 on_disk = svn_node_none; 561 } 562 } 563 else if (on_disk == svn_node_file) 564 { 565 svn_boolean_t modified; 566 apr_hash_t *props; 567#ifdef HAVE_SYMLINK 568 svn_string_t *special_prop; 569#endif 570 571 SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath, 572 scratch_pool, scratch_pool)); 573 574#ifdef HAVE_SYMLINK 575 special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL); 576 577 if ((special_prop != NULL) != special) 578 { 579 /* File/symlink mismatch. */ 580 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); 581 on_disk = svn_node_none; 582 } 583 else 584#endif 585 { 586 /* Issue #1663 asserts that we should compare a file in its 587 working copy format here, but before r1101473 we would only 588 do that if the file was already unequal to its recorded 589 information. 590 591 r1101473 removes the option of asking for a working format 592 compare but *also* check the recorded information first, as 593 that combination doesn't guarantee a stable behavior. 594 (See the revert_test.py: revert_reexpand_keyword) 595 596 But to have the same issue #1663 behavior for revert as we 597 had in <=1.6 we only have to check the recorded information 598 ourselves. And we already have everything we need, because 599 we called stat ourselves. */ 600 if (recorded_size != SVN_INVALID_FILESIZE 601 && recorded_time != 0 602 && recorded_size == finfo.size 603 && recorded_time == finfo.mtime) 604 { 605 modified = FALSE; 606 } 607 else 608 /* Side effect: fixes recorded timestamps */ 609 SVN_ERR(svn_wc__internal_file_modified_p(&modified, 610 db, local_abspath, 611 TRUE, scratch_pool)); 612 613 if (modified) 614 { 615 /* Install will replace the file */ 616 on_disk = svn_node_none; 617 } 618 else 619 { 620 if (status == svn_wc__db_status_normal) 621 { 622 svn_boolean_t read_only; 623 svn_string_t *needs_lock_prop; 624 625 SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo, 626 scratch_pool)); 627 628 needs_lock_prop = svn_hash_gets(props, 629 SVN_PROP_NEEDS_LOCK); 630 if (needs_lock_prop && !read_only) 631 { 632 SVN_ERR(svn_io_set_file_read_only(local_abspath, 633 FALSE, 634 scratch_pool)); 635 *notify_required = TRUE; 636 } 637 else if (!needs_lock_prop && read_only) 638 { 639 SVN_ERR(svn_io_set_file_read_write(local_abspath, 640 FALSE, 641 scratch_pool)); 642 *notify_required = TRUE; 643 } 644 } 645 646#if !defined(WIN32) && !defined(__OS2__) 647#ifdef HAVE_SYMLINK 648 if (!special) 649#endif 650 { 651 svn_boolean_t executable; 652 svn_string_t *executable_prop; 653 654 SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo, 655 scratch_pool)); 656 executable_prop = svn_hash_gets(props, 657 SVN_PROP_EXECUTABLE); 658 if (executable_prop && !executable) 659 { 660 SVN_ERR(svn_io_set_file_executable(local_abspath, 661 TRUE, FALSE, 662 scratch_pool)); 663 *notify_required = TRUE; 664 } 665 else if (!executable_prop && executable) 666 { 667 SVN_ERR(svn_io_set_file_executable(local_abspath, 668 FALSE, FALSE, 669 scratch_pool)); 670 *notify_required = TRUE; 671 } 672 } 673#endif 674 } 675 } 676 } 677 } 678 679 /* If we expect a versioned item to be present and there is nothing 680 on disk then recreate it. */ 681 if (on_disk == svn_node_none 682 && status != svn_wc__db_status_server_excluded 683 && status != svn_wc__db_status_deleted 684 && status != svn_wc__db_status_excluded 685 && status != svn_wc__db_status_not_present) 686 { 687 if (kind == svn_node_dir) 688 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); 689 690 if (kind == svn_node_file) 691 { 692 svn_skel_t *work_item; 693 694 SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath, 695 NULL, use_commit_times, TRUE, 696 scratch_pool, scratch_pool)); 697 SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, 698 scratch_pool)); 699 *run_wq = TRUE; 700 } 701 *notify_required = TRUE; 702 } 703 704 return SVN_NO_ERROR; 705} 706 707/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all reverts. */ 708static svn_error_t * 709revert(svn_wc__db_t *db, 710 const char *local_abspath, 711 svn_depth_t depth, 712 svn_boolean_t use_commit_times, 713 svn_boolean_t clear_changelists, 714 svn_boolean_t metadata_only, 715 svn_cancel_func_t cancel_func, 716 void *cancel_baton, 717 svn_wc_notify_func2_t notify_func, 718 void *notify_baton, 719 apr_pool_t *scratch_pool) 720{ 721 svn_error_t *err; 722 const struct svn_wc__db_info_t *info = NULL; 723 svn_boolean_t run_queue = FALSE; 724 725 SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity); 726 727 /* We should have a write lock on the parent of local_abspath, except 728 when local_abspath is the working copy root. */ 729 { 730 const char *dir_abspath; 731 svn_boolean_t is_wcroot; 732 733 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); 734 735 if (! is_wcroot) 736 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 737 else 738 dir_abspath = local_abspath; 739 740 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); 741 } 742 743 err = svn_error_trace( 744 svn_wc__db_op_revert(db, local_abspath, depth, clear_changelists, 745 scratch_pool, scratch_pool)); 746 747 if (!err) 748 { 749 err = svn_error_trace( 750 svn_wc__db_read_single_info(&info, db, local_abspath, FALSE, 751 scratch_pool, scratch_pool)); 752 753 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 754 { 755 svn_error_clear(err); 756 err = NULL; 757 info = NULL; 758 } 759 } 760 761 if (!err) 762 err = svn_error_trace( 763 revert_restore(&run_queue, db, local_abspath, depth, metadata_only, 764 use_commit_times, TRUE /* revert root */, 765 info, cancel_func, cancel_baton, 766 notify_func, notify_baton, 767 scratch_pool)); 768 769 if (run_queue) 770 err = svn_error_compose_create(err, 771 svn_wc__wq_run(db, local_abspath, 772 cancel_func, cancel_baton, 773 scratch_pool)); 774 775 err = svn_error_compose_create(err, 776 svn_wc__db_revert_list_done(db, 777 local_abspath, 778 scratch_pool)); 779 780 return err; 781} 782 783 784/* Revert files in LOCAL_ABSPATH to depth DEPTH that match 785 CHANGELIST_HASH and notify for all reverts. */ 786static svn_error_t * 787revert_changelist(svn_wc__db_t *db, 788 const char *local_abspath, 789 svn_depth_t depth, 790 svn_boolean_t use_commit_times, 791 apr_hash_t *changelist_hash, 792 svn_boolean_t clear_changelists, 793 svn_boolean_t metadata_only, 794 svn_cancel_func_t cancel_func, 795 void *cancel_baton, 796 svn_wc_notify_func2_t notify_func, 797 void *notify_baton, 798 apr_pool_t *scratch_pool) 799{ 800 apr_pool_t *iterpool; 801 const apr_array_header_t *children; 802 int i; 803 804 if (cancel_func) 805 SVN_ERR(cancel_func(cancel_baton)); 806 807 /* Revert this node (depth=empty) if it matches one of the changelists. */ 808 if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash, 809 scratch_pool)) 810 SVN_ERR(revert(db, local_abspath, 811 svn_depth_empty, use_commit_times, clear_changelists, 812 metadata_only, 813 cancel_func, cancel_baton, 814 notify_func, notify_baton, 815 scratch_pool)); 816 817 if (depth == svn_depth_empty) 818 return SVN_NO_ERROR; 819 820 iterpool = svn_pool_create(scratch_pool); 821 822 /* We can handle both depth=files and depth=immediates by setting 823 depth=empty here. We don't need to distinguish files and 824 directories when making the recursive call because directories 825 can never match a changelist, so making the recursive call for 826 directories when asked for depth=files is a no-op. */ 827 if (depth == svn_depth_files || depth == svn_depth_immediates) 828 depth = svn_depth_empty; 829 830 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, 831 local_abspath, 832 scratch_pool, 833 iterpool)); 834 for (i = 0; i < children->nelts; ++i) 835 { 836 const char *child_abspath; 837 838 svn_pool_clear(iterpool); 839 840 child_abspath = svn_dirent_join(local_abspath, 841 APR_ARRAY_IDX(children, i, 842 const char *), 843 iterpool); 844 845 SVN_ERR(revert_changelist(db, child_abspath, depth, 846 use_commit_times, changelist_hash, 847 clear_changelists, metadata_only, 848 cancel_func, cancel_baton, 849 notify_func, notify_baton, 850 iterpool)); 851 } 852 853 svn_pool_destroy(iterpool); 854 855 return SVN_NO_ERROR; 856} 857 858 859/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH 860 (which must be either svn_depth_files or svn_depth_immediates) by 861 doing a non-recursive revert on each permissible path. Notifies 862 all reverted paths. 863 864 ### This won't revert a copied dir with one level of children since 865 ### the non-recursive revert on the dir will fail. Not sure how a 866 ### partially recursive revert should handle actual-only nodes. */ 867static svn_error_t * 868revert_partial(svn_wc__db_t *db, 869 const char *local_abspath, 870 svn_depth_t depth, 871 svn_boolean_t use_commit_times, 872 svn_boolean_t clear_changelists, 873 svn_boolean_t metadata_only, 874 svn_cancel_func_t cancel_func, 875 void *cancel_baton, 876 svn_wc_notify_func2_t notify_func, 877 void *notify_baton, 878 apr_pool_t *scratch_pool) 879{ 880 apr_pool_t *iterpool; 881 const apr_array_header_t *children; 882 int i; 883 884 SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates); 885 886 if (cancel_func) 887 SVN_ERR(cancel_func(cancel_baton)); 888 889 iterpool = svn_pool_create(scratch_pool); 890 891 /* Revert the root node itself (depth=empty), then move on to the 892 children. */ 893 SVN_ERR(revert(db, local_abspath, svn_depth_empty, 894 use_commit_times, clear_changelists, metadata_only, 895 cancel_func, cancel_baton, 896 notify_func, notify_baton, iterpool)); 897 898 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, 899 local_abspath, 900 scratch_pool, 901 iterpool)); 902 for (i = 0; i < children->nelts; ++i) 903 { 904 const char *child_abspath; 905 906 svn_pool_clear(iterpool); 907 908 child_abspath = svn_dirent_join(local_abspath, 909 APR_ARRAY_IDX(children, i, const char *), 910 iterpool); 911 912 /* For svn_depth_files: don't revert non-files. */ 913 if (depth == svn_depth_files) 914 { 915 svn_node_kind_t kind; 916 917 SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath, 918 FALSE /* allow_missing */, 919 TRUE /* show_deleted */, 920 FALSE /* show_hidden */, 921 iterpool)); 922 if (kind != svn_node_file) 923 continue; 924 } 925 926 /* Revert just this node (depth=empty). */ 927 SVN_ERR(revert(db, child_abspath, 928 svn_depth_empty, use_commit_times, clear_changelists, 929 metadata_only, 930 cancel_func, cancel_baton, 931 notify_func, notify_baton, 932 iterpool)); 933 } 934 935 svn_pool_destroy(iterpool); 936 937 return SVN_NO_ERROR; 938} 939 940 941svn_error_t * 942svn_wc_revert5(svn_wc_context_t *wc_ctx, 943 const char *local_abspath, 944 svn_depth_t depth, 945 svn_boolean_t use_commit_times, 946 const apr_array_header_t *changelist_filter, 947 svn_boolean_t clear_changelists, 948 svn_boolean_t metadata_only, 949 svn_cancel_func_t cancel_func, 950 void *cancel_baton, 951 svn_wc_notify_func2_t notify_func, 952 void *notify_baton, 953 apr_pool_t *scratch_pool) 954{ 955 if (changelist_filter && changelist_filter->nelts) 956 { 957 apr_hash_t *changelist_hash; 958 959 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, 960 scratch_pool)); 961 return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath, 962 depth, use_commit_times, 963 changelist_hash, 964 clear_changelists, 965 metadata_only, 966 cancel_func, cancel_baton, 967 notify_func, notify_baton, 968 scratch_pool)); 969 } 970 971 if (depth == svn_depth_empty || depth == svn_depth_infinity) 972 return svn_error_trace(revert(wc_ctx->db, local_abspath, 973 depth, use_commit_times, clear_changelists, 974 metadata_only, 975 cancel_func, cancel_baton, 976 notify_func, notify_baton, 977 scratch_pool)); 978 979 /* The user may expect svn_depth_files/svn_depth_immediates to work 980 on copied dirs with one level of children. It doesn't, the user 981 will get an error and will need to invoke an infinite revert. If 982 we identified those cases where svn_depth_infinity would not 983 revert too much we could invoke the recursive call above. */ 984 985 if (depth == svn_depth_files || depth == svn_depth_immediates) 986 return svn_error_trace(revert_partial(wc_ctx->db, local_abspath, 987 depth, use_commit_times, 988 clear_changelists, metadata_only, 989 cancel_func, cancel_baton, 990 notify_func, notify_baton, 991 scratch_pool)); 992 993 /* Bogus depth. Tell the caller. */ 994 return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL); 995} 996