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