2 * revert.c: Handling of the in-wc side of the revert operation
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
29 #include <apr_pools.h>
30 #include <apr_tables.h>
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"
43 #include "adm_files.h"
44 #include "workqueue.h"
46 #include "svn_private_config.h"
47 #include "private/svn_io_private.h"
48 #include "private/svn_wc_private.h"
50 /* Thoughts on Reversion.
52 What does is mean to revert a given PATH in a tree? We'll
53 consider things by their modifications.
57 - For files, svn_wc_remove_from_revision_control(), baby.
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.
66 - Restore properties to their unmodified state.
68 - For files, restore the pristine contents, and reset the schedule
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.
79 - Restore properties to their unmodified state.
81 - For files, restore the pristine contents, and reset the schedule
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.
93 - Restore properties and, for files, contents to their unmodified
99 /* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set
100 * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */
102 remove_conflict_file(svn_boolean_t *notify_required,
103 const char *conflict_abspath,
104 const char *local_abspath,
105 apr_pool_t *scratch_pool)
107 if (conflict_abspath)
109 svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE,
112 svn_error_clear(err);
114 *notify_required = TRUE;
121 /* Sort copied children obtained from the revert list based on
122 * their paths in descending order (longest paths first). */
124 compare_revert_list_copied_children(const void *a, const void *b)
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;
130 i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath);
132 /* Reverse the result of svn_path_compare_paths() to achieve
133 * descending order. */
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).
142 * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether
143 * LOCAL_ABSPATH itself was removed.
145 * All reverted copied file children are removed from disk. Reverted copied
146 * directories left empty as a result are also removed from disk.
149 revert_restore_handle_copied_dirs(svn_boolean_t *removed_self,
151 const char *local_abspath,
152 svn_boolean_t remove_self,
153 svn_cancel_func_t cancel_func,
155 apr_pool_t *scratch_pool)
157 const apr_array_header_t *copied_children;
158 svn_wc__db_revert_list_copied_child_info_t *child_info;
160 svn_node_kind_t on_disk;
161 apr_pool_t *iterpool;
165 *removed_self = FALSE;
167 SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children,
171 iterpool = svn_pool_create(scratch_pool);
173 /* Remove all copied file children. */
174 for (i = 0; i < copied_children->nelts; i++)
176 child_info = APR_ARRAY_IDX(
178 svn_wc__db_revert_list_copied_child_info_t *);
181 SVN_ERR(cancel_func(cancel_baton));
183 if (child_info->kind != svn_node_file)
186 svn_pool_clear(iterpool);
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)
193 SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool));
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++)
206 child_info = APR_ARRAY_IDX(
208 svn_wc__db_revert_list_copied_child_info_t *);
211 SVN_ERR(cancel_func(cancel_baton));
213 if (child_info->kind != svn_node_dir)
216 svn_pool_clear(iterpool);
218 err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool);
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);
226 return svn_error_trace(err);
232 /* Delete LOCAL_ABSPATH itself if no children are left. */
233 err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool);
236 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
237 svn_error_clear(err);
239 return svn_error_trace(err);
241 else if (removed_self)
242 *removed_self = TRUE;
245 svn_pool_destroy(iterpool);
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.
256 REVERT_ROOT is true for explicit revert targets and FALSE for targets
257 reached via recursion.
260 revert_restore(svn_wc__db_t *db,
261 const char *local_abspath,
263 svn_boolean_t use_commit_times,
264 svn_boolean_t revert_root,
265 svn_cancel_func_t cancel_func,
267 svn_wc_notify_func2_t notify_func,
269 apr_pool_t *scratch_pool)
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;
281 svn_boolean_t special;
283 svn_boolean_t copied_here;
284 svn_node_kind_t reverted_kind;
285 svn_boolean_t is_wcroot;
288 SVN_ERR(cancel_func(cancel_baton));
290 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
291 if (is_wcroot && !revert_root)
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
299 svn_wc_notify_t *notify = svn_wc_create_notify(
301 svn_wc_notify_update_skip_obstruction,
304 notify_func(notify_baton, notify, scratch_pool);
307 return SVN_NO_ERROR; /* We don't revert obstructing working copies */
310 SVN_ERR(svn_wc__db_revert_list_read(¬ify_required,
312 &copied_here, &reverted_kind,
314 scratch_pool, scratch_pool));
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);
323 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
325 svn_error_clear(err);
329 if (notify_func && notify_required)
330 notify_func(notify_baton,
331 svn_wc_create_notify(local_abspath,
332 svn_wc_notify_revert,
337 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
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;
354 return svn_error_trace(err);
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,
363 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
364 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
366 svn_error_clear(err);
367 on_disk = svn_node_none;
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;
379 on_disk = svn_node_unknown;
382 special = (finfo.filetype == APR_LNK);
386 return svn_error_trace(err);
390 /* The revert target itself is the op-root of a copy. */
391 if (reverted_kind == svn_node_file && on_disk == svn_node_file)
393 SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
394 on_disk = svn_node_none;
396 else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir)
398 svn_boolean_t removed;
400 SVN_ERR(revert_restore_handle_copied_dirs(&removed, db,
402 cancel_func, cancel_baton,
405 on_disk = svn_node_none;
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)
418 if (on_disk == svn_node_dir && kind != svn_node_dir)
420 SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE,
421 cancel_func, cancel_baton, scratch_pool));
422 on_disk = svn_node_none;
424 else if (on_disk == svn_node_file && kind != svn_node_file)
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))
432 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
433 on_disk = svn_node_none;
436 else if (on_disk == svn_node_file)
438 svn_boolean_t modified;
441 svn_string_t *special_prop;
444 SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath,
445 scratch_pool, scratch_pool));
448 special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL);
450 if ((special_prop != NULL) != special)
452 /* File/symlink mismatch. */
453 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
454 on_disk = svn_node_none;
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
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)
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)
481 SVN_ERR(svn_wc__internal_file_modified_p(&modified,
483 TRUE, scratch_pool));
487 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE,
489 on_disk = svn_node_none;
493 if (status == svn_wc__db_status_normal)
495 svn_boolean_t read_only;
496 svn_string_t *needs_lock_prop;
498 SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo,
501 needs_lock_prop = svn_hash_gets(props,
502 SVN_PROP_NEEDS_LOCK);
503 if (needs_lock_prop && !read_only)
505 SVN_ERR(svn_io_set_file_read_only(local_abspath,
508 notify_required = TRUE;
510 else if (!needs_lock_prop && read_only)
512 SVN_ERR(svn_io_set_file_read_write(local_abspath,
515 notify_required = TRUE;
519 #if !defined(WIN32) && !defined(__OS2__)
524 svn_boolean_t executable;
525 svn_string_t *executable_prop;
527 SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo,
529 executable_prop = svn_hash_gets(props,
530 SVN_PROP_EXECUTABLE);
531 if (executable_prop && !executable)
533 SVN_ERR(svn_io_set_file_executable(local_abspath,
536 notify_required = TRUE;
538 else if (!executable_prop && executable)
540 SVN_ERR(svn_io_set_file_executable(local_abspath,
543 notify_required = TRUE;
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)
560 if (kind == svn_node_dir)
561 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
563 if (kind == svn_node_file)
565 svn_skel_t *work_item;
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,
573 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
576 notify_required = TRUE;
582 for (i = 0; i < conflict_files->nelts; i++)
584 SVN_ERR(remove_conflict_file(¬ify_required,
585 APR_ARRAY_IDX(conflict_files, i,
587 local_abspath, scratch_pool));
591 if (notify_func && notify_required)
592 notify_func(notify_baton,
593 svn_wc_create_notify(local_abspath, svn_wc_notify_revert,
597 if (depth == svn_depth_infinity && kind == svn_node_dir)
599 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
600 const apr_array_header_t *children;
603 SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
604 cancel_func, cancel_baton,
607 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
611 for (i = 0; i < children->nelts; ++i)
613 const char *child_abspath;
615 svn_pool_clear(iterpool);
617 child_abspath = svn_dirent_join(local_abspath,
618 APR_ARRAY_IDX(children, i,
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,
629 svn_pool_destroy(iterpool);
633 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
634 db, local_abspath, scratch_pool));
640 svn_wc__revert_internal(svn_wc__db_t *db,
641 const char *local_abspath,
643 svn_boolean_t use_commit_times,
644 svn_cancel_func_t cancel_func,
646 svn_wc_notify_func2_t notify_func,
648 apr_pool_t *scratch_pool)
652 SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity);
654 /* We should have a write lock on the parent of local_abspath, except
655 when local_abspath is the working copy root. */
657 const char *dir_abspath;
658 svn_boolean_t is_wcroot;
660 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
663 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
665 dir_abspath = local_abspath;
667 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
670 err = svn_wc__db_op_revert(db, local_abspath, depth,
671 scratch_pool, scratch_pool);
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,
680 err = svn_error_compose_create(err,
681 svn_wc__db_revert_list_done(db,
689 /* Revert files in LOCAL_ABSPATH to depth DEPTH that match
690 CHANGELIST_HASH and notify for all reverts. */
692 revert_changelist(svn_wc__db_t *db,
693 const char *local_abspath,
695 svn_boolean_t use_commit_times,
696 apr_hash_t *changelist_hash,
697 svn_cancel_func_t cancel_func,
699 svn_wc_notify_func2_t notify_func,
701 apr_pool_t *scratch_pool)
703 apr_pool_t *iterpool;
704 const apr_array_header_t *children;
708 SVN_ERR(cancel_func(cancel_baton));
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,
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,
719 if (depth == svn_depth_empty)
722 iterpool = svn_pool_create(scratch_pool);
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;
732 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
736 for (i = 0; i < children->nelts; ++i)
738 const char *child_abspath;
740 svn_pool_clear(iterpool);
742 child_abspath = svn_dirent_join(local_abspath,
743 APR_ARRAY_IDX(children, i,
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,
754 svn_pool_destroy(iterpool);
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
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. */
769 revert_partial(svn_wc__db_t *db,
770 const char *local_abspath,
772 svn_boolean_t use_commit_times,
773 svn_cancel_func_t cancel_func,
775 svn_wc_notify_func2_t notify_func,
777 apr_pool_t *scratch_pool)
779 apr_pool_t *iterpool;
780 const apr_array_header_t *children;
783 SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates);
786 SVN_ERR(cancel_func(cancel_baton));
788 iterpool = svn_pool_create(scratch_pool);
790 /* Revert the root node itself (depth=empty), then move on to the
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));
796 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
800 for (i = 0; i < children->nelts; ++i)
802 const char *child_abspath;
804 svn_pool_clear(iterpool);
806 child_abspath = svn_dirent_join(local_abspath,
807 APR_ARRAY_IDX(children, i, const char *),
810 /* For svn_depth_files: don't revert non-files. */
811 if (depth == svn_depth_files)
813 svn_node_kind_t kind;
815 SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath,
816 FALSE /* allow_missing */,
817 TRUE /* show_deleted */,
818 FALSE /* show_hidden */,
820 if (kind != svn_node_file)
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,
832 svn_pool_destroy(iterpool);
839 svn_wc_revert4(svn_wc_context_t *wc_ctx,
840 const char *local_abspath,
842 svn_boolean_t use_commit_times,
843 const apr_array_header_t *changelist_filter,
844 svn_cancel_func_t cancel_func,
846 svn_wc_notify_func2_t notify_func,
848 apr_pool_t *scratch_pool)
850 if (changelist_filter && changelist_filter->nelts)
852 apr_hash_t *changelist_hash;
854 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
856 return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath,
857 depth, use_commit_times,
859 cancel_func, cancel_baton,
860 notify_func, notify_baton,
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,
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. */
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,
884 /* Bogus depth. Tell the caller. */
885 return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL);