| /* |
| * revert.c: Handling of the in-wc side of the revert operation |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include <apr_pools.h> |
| #include <apr_tables.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_hash.h" |
| #include "svn_wc.h" |
| #include "svn_io.h" |
| |
| #include "wc.h" |
| #include "adm_files.h" |
| #include "workqueue.h" |
| |
| #include "svn_private_config.h" |
| #include "private/svn_io_private.h" |
| #include "private/svn_wc_private.h" |
| #include "private/svn_sorts_private.h" |
| |
| /* Thoughts on Reversion. |
| |
| What does is mean to revert a given PATH in a tree? We'll |
| consider things by their modifications. |
| |
| Adds |
| |
| - For files, svn_wc_remove_from_revision_control(), baby. |
| |
| - Added directories may contain nothing but added children, and |
| reverting the addition of a directory necessarily means reverting |
| the addition of all the directory's children. Again, |
| svn_wc_remove_from_revision_control() should do the trick. |
| |
| - For a copy, we remove the item from disk as well. The thinking here |
| is that Subversion is responsible for the existence of the item: it |
| must have been created by something like 'svn copy' or 'svn merge'. |
| |
| - For a plain add, removing the file or directory from disk is optional. |
| The user's idea of Subversion's involvement could be either that |
| Subversion was just responsible for adding an existing item to version |
| control, as with 'svn add', and so should not be responsible for |
| deleting it from disk; or that Subversion is responsible for the |
| existence of the item, e.g. if created by 'svn patch' or svn mkdir'. |
| It depends on the use case. |
| |
| Deletes |
| |
| - Restore properties to their unmodified state. |
| |
| - For files, restore the pristine contents, and reset the schedule |
| to 'normal'. |
| |
| - For directories, reset the schedule to 'normal'. All children |
| of a directory marked for deletion must also be marked for |
| deletion, but it's okay for those children to remain deleted even |
| if their parent directory is restored. That's what the |
| recursive flag is for. |
| |
| Replaces |
| |
| - Restore properties to their unmodified state. |
| |
| - For files, restore the pristine contents, and reset the schedule |
| to 'normal'. |
| |
| - For directories, reset the schedule to normal. A replaced |
| directory can have deleted children (left over from the initial |
| deletion), replaced children (children of the initial deletion |
| now re-added), and added children (new entries under the |
| replaced directory). Since this is technically an addition, it |
| necessitates recursion. |
| |
| Modifications |
| |
| - Restore properties and, for files, contents to their unmodified |
| state. |
| |
| */ |
| |
| |
| /* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set |
| * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */ |
| static svn_error_t * |
| remove_conflict_file(svn_boolean_t *notify_required, |
| const char *conflict_abspath, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| if (conflict_abspath) |
| { |
| svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE, |
| scratch_pool); |
| if (err) |
| svn_error_clear(err); |
| else |
| *notify_required = TRUE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Sort copied children obtained from the revert list based on |
| * their paths in descending order (longest paths first). */ |
| static int |
| compare_revert_list_copied_children(const void *a, const void *b) |
| { |
| const svn_wc__db_revert_list_copied_child_info_t * const *ca = a; |
| const svn_wc__db_revert_list_copied_child_info_t * const *cb = b; |
| int i; |
| |
| i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath); |
| |
| /* Reverse the result of svn_path_compare_paths() to achieve |
| * descending order. */ |
| return -i; |
| } |
| |
| |
| /* Remove all reverted copied children from the directory at LOCAL_ABSPATH. |
| * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF |
| * should be set if LOCAL_ABSPATH is itself a reverted copy). |
| * |
| * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether |
| * LOCAL_ABSPATH itself was removed. |
| * |
| * All reverted copied file children are removed from disk. Reverted copied |
| * directories left empty as a result are also removed from disk. |
| */ |
| static svn_error_t * |
| revert_restore_handle_copied_dirs(svn_boolean_t *removed_self, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_boolean_t remove_self, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *copied_children; |
| svn_wc__db_revert_list_copied_child_info_t *child_info; |
| int i; |
| svn_node_kind_t on_disk; |
| apr_pool_t *iterpool; |
| svn_error_t *err; |
| |
| if (removed_self) |
| *removed_self = FALSE; |
| |
| SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children, |
| db, local_abspath, |
| scratch_pool, |
| scratch_pool)); |
| iterpool = svn_pool_create(scratch_pool); |
| |
| /* Remove all copied file children. */ |
| for (i = 0; i < copied_children->nelts; i++) |
| { |
| child_info = APR_ARRAY_IDX( |
| copied_children, i, |
| svn_wc__db_revert_list_copied_child_info_t *); |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| if (child_info->kind != svn_node_file) |
| continue; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Make sure what we delete from disk is really a file. */ |
| SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool)); |
| if (on_disk != svn_node_file) |
| continue; |
| |
| SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool)); |
| } |
| |
| /* Delete every empty child directory. |
| * We cannot delete children recursively since we want to keep any files |
| * that still exist on disk (e.g. unversioned files within the copied tree). |
| * So sort the children list such that longest paths come first and try to |
| * remove each child directory in order. */ |
| svn_sort__array(copied_children, compare_revert_list_copied_children); |
| for (i = 0; i < copied_children->nelts; i++) |
| { |
| child_info = APR_ARRAY_IDX( |
| copied_children, i, |
| svn_wc__db_revert_list_copied_child_info_t *); |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| if (child_info->kind != svn_node_dir) |
| continue; |
| |
| svn_pool_clear(iterpool); |
| |
| err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool); |
| if (err) |
| { |
| if (APR_STATUS_IS_ENOENT(err->apr_err) || |
| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) || |
| APR_STATUS_IS_ENOTEMPTY(err->apr_err)) |
| svn_error_clear(err); |
| else |
| return svn_error_trace(err); |
| } |
| } |
| |
| if (remove_self) |
| { |
| /* Delete LOCAL_ABSPATH itself if no children are left. */ |
| err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); |
| if (err) |
| { |
| if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) |
| svn_error_clear(err); |
| else |
| return svn_error_trace(err); |
| } |
| else if (removed_self) |
| *removed_self = TRUE; |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Forward definition */ |
| static svn_error_t * |
| revert_wc_data(svn_boolean_t *run_wq, |
| svn_boolean_t *notify_required, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_wc__db_status_t status, |
| svn_node_kind_t kind, |
| svn_node_kind_t reverted_kind, |
| svn_filesize_t recorded_size, |
| apr_time_t recorded_time, |
| svn_boolean_t copied_here, |
| svn_boolean_t use_commit_times, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool); |
| |
| /* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the |
| versioned tree. This function is called after svn_wc__db_op_revert |
| has done the database revert and created the revert list. Notifies |
| for all paths equal to or below LOCAL_ABSPATH that are reverted. |
| |
| REVERT_ROOT is true for explicit revert targets and FALSE for targets |
| reached via recursion. |
| |
| Sets *RUN_WQ to TRUE when the caller should (eventually) run the workqueue. |
| (The function sets it to FALSE when it has run the WQ itself) |
| |
| If INFO is NULL, LOCAL_ABSPATH doesn't exist in DB. Otherwise INFO |
| specifies the state of LOCAL_ABSPATH in DB. |
| */ |
| static svn_error_t * |
| revert_restore(svn_boolean_t *run_wq, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_boolean_t metadata_only, |
| svn_boolean_t use_commit_times, |
| svn_boolean_t revert_root, |
| svn_boolean_t added_keep_local, |
| const struct svn_wc__db_info_t *info, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_status_t status; |
| svn_node_kind_t kind; |
| svn_boolean_t notify_required; |
| const apr_array_header_t *conflict_files; |
| svn_filesize_t recorded_size; |
| apr_time_t recorded_time; |
| svn_boolean_t copied_here; |
| svn_node_kind_t reverted_kind; |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| if (!revert_root) |
| { |
| svn_boolean_t is_wcroot; |
| |
| SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); |
| if (is_wcroot) |
| { |
| /* Issue #4162: Obstructing working copy. We can't access the working |
| copy data from the parent working copy for this node by just using |
| local_abspath */ |
| |
| if (notify_func) |
| { |
| svn_wc_notify_t *notify = |
| svn_wc_create_notify( |
| local_abspath, |
| svn_wc_notify_update_skip_obstruction, |
| scratch_pool); |
| |
| notify_func(notify_baton, notify, scratch_pool); |
| } |
| |
| return SVN_NO_ERROR; /* We don't revert obstructing working copies */ |
| } |
| } |
| |
| SVN_ERR(svn_wc__db_revert_list_read(¬ify_required, |
| &conflict_files, |
| &copied_here, &reverted_kind, |
| db, local_abspath, |
| scratch_pool, scratch_pool)); |
| |
| if (info) |
| { |
| status = info->status; |
| kind = info->kind; |
| recorded_size = info->recorded_size; |
| recorded_time = info->recorded_time; |
| } |
| else |
| { |
| if (added_keep_local && !copied_here) |
| { |
| /* It is a plain add, and we want to keep the local file/dir. */ |
| if (notify_func && notify_required) |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_revert, |
| scratch_pool), |
| scratch_pool); |
| |
| if (notify_func) |
| SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, |
| db, local_abspath, |
| scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| else if (!copied_here) |
| { |
| /* It is a plain add, and we don't want to keep the local file/dir. */ |
| status = svn_wc__db_status_not_present; |
| kind = svn_node_none; |
| recorded_size = SVN_INVALID_FILESIZE; |
| recorded_time = 0; |
| } |
| else |
| { |
| /* It is a copy, so we don't want to keep the local file/dir. */ |
| /* ### Initialise to values which prevent the code below from |
| * ### trying to restore anything to disk. |
| * ### 'status' should be status_unknown but that doesn't exist. */ |
| status = svn_wc__db_status_normal; |
| kind = svn_node_unknown; |
| recorded_size = SVN_INVALID_FILESIZE; |
| recorded_time = 0; |
| } |
| } |
| |
| if (!metadata_only) |
| { |
| SVN_ERR(revert_wc_data(run_wq, |
| ¬ify_required, |
| db, local_abspath, status, kind, |
| reverted_kind, recorded_size, recorded_time, |
| copied_here, use_commit_times, |
| cancel_func, cancel_baton, scratch_pool)); |
| } |
| |
| /* We delete these marker files even though they are not strictly metadata. |
| But for users that use revert as an API with metadata_only, these are. */ |
| if (conflict_files) |
| { |
| int i; |
| for (i = 0; i < conflict_files->nelts; i++) |
| { |
| SVN_ERR(remove_conflict_file(¬ify_required, |
| APR_ARRAY_IDX(conflict_files, i, |
| const char *), |
| local_abspath, scratch_pool)); |
| } |
| } |
| |
| if (notify_func && notify_required) |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, svn_wc_notify_revert, |
| scratch_pool), |
| scratch_pool); |
| |
| if (depth == svn_depth_infinity && kind == svn_node_dir) |
| { |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_hash_t *children, *conflicts; |
| apr_hash_index_t *hi; |
| |
| SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE, |
| cancel_func, cancel_baton, |
| iterpool)); |
| |
| SVN_ERR(svn_wc__db_read_children_info(&children, &conflicts, |
| db, local_abspath, FALSE, |
| scratch_pool, iterpool)); |
| |
| for (hi = apr_hash_first(scratch_pool, children); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *child_name = apr_hash_this_key(hi); |
| const char *child_abspath; |
| |
| svn_pool_clear(iterpool); |
| |
| child_abspath = svn_dirent_join(local_abspath, child_name, iterpool); |
| |
| SVN_ERR(revert_restore(run_wq, |
| db, child_abspath, depth, metadata_only, |
| use_commit_times, FALSE /* revert root */, |
| added_keep_local, |
| apr_hash_this_val(hi), |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| iterpool)); |
| } |
| |
| /* Run the queue per directory */ |
| if (*run_wq) |
| { |
| SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, |
| iterpool)); |
| *run_wq = FALSE; |
| } |
| |
| svn_pool_destroy(iterpool); |
| } |
| |
| if (notify_func && (revert_root || kind == svn_node_dir)) |
| SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, |
| db, local_abspath, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Perform the in-working copy revert of LOCAL_ABSPATH, to what is stored in DB */ |
| static svn_error_t * |
| revert_wc_data(svn_boolean_t *run_wq, |
| svn_boolean_t *notify_required, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_wc__db_status_t status, |
| svn_node_kind_t kind, |
| svn_node_kind_t reverted_kind, |
| svn_filesize_t recorded_size, |
| apr_time_t recorded_time, |
| svn_boolean_t copied_here, |
| svn_boolean_t use_commit_times, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| apr_finfo_t finfo; |
| svn_node_kind_t on_disk; |
| #ifdef HAVE_SYMLINK |
| svn_boolean_t special; |
| #endif |
| |
| /* Would be nice to use svn_io_dirent2_t here, but the performance |
| improvement that provides doesn't work, because we need the read |
| only and executable bits later on, in the most likely code path */ |
| err = svn_io_stat(&finfo, local_abspath, |
| APR_FINFO_TYPE | APR_FINFO_LINK |
| | APR_FINFO_SIZE | APR_FINFO_MTIME |
| | SVN__APR_FINFO_EXECUTABLE |
| | SVN__APR_FINFO_READONLY, |
| scratch_pool); |
| |
| if (err && (APR_STATUS_IS_ENOENT(err->apr_err) |
| || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) |
| { |
| svn_error_clear(err); |
| on_disk = svn_node_none; |
| #ifdef HAVE_SYMLINK |
| special = FALSE; |
| #endif |
| } |
| else if (!err) |
| { |
| if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) |
| on_disk = svn_node_file; |
| else if (finfo.filetype == APR_DIR) |
| on_disk = svn_node_dir; |
| else |
| on_disk = svn_node_unknown; |
| |
| #ifdef HAVE_SYMLINK |
| special = (finfo.filetype == APR_LNK); |
| #endif |
| } |
| else |
| return svn_error_trace(err); |
| |
| if (copied_here) |
| { |
| /* The revert target itself is the op-root of a copy. */ |
| if (reverted_kind == svn_node_file && on_disk == svn_node_file) |
| { |
| SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); |
| on_disk = svn_node_none; |
| } |
| else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir) |
| { |
| svn_boolean_t removed; |
| |
| SVN_ERR(revert_restore_handle_copied_dirs(&removed, db, |
| local_abspath, TRUE, |
| cancel_func, cancel_baton, |
| scratch_pool)); |
| if (removed) |
| on_disk = svn_node_none; |
| } |
| } |
| |
| /* If we expect a versioned item to be present then check that any |
| item on disk matches the versioned item, if it doesn't match then |
| fix it or delete it. */ |
| if (on_disk != svn_node_none) |
| { |
| if (on_disk == svn_node_dir && kind != svn_node_dir) |
| { |
| SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE, |
| cancel_func, cancel_baton, scratch_pool)); |
| on_disk = svn_node_none; |
| } |
| else if (on_disk == svn_node_file && kind != svn_node_file) |
| { |
| #ifdef HAVE_SYMLINK |
| /* Preserve symlinks pointing at directories. Changes on the |
| * directory node have been reverted. The symlink should remain. */ |
| if (!(special && kind == svn_node_dir)) |
| #endif |
| { |
| SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); |
| on_disk = svn_node_none; |
| } |
| } |
| else if (on_disk == svn_node_file |
| && status != svn_wc__db_status_server_excluded |
| && status != svn_wc__db_status_deleted |
| && status != svn_wc__db_status_excluded |
| && status != svn_wc__db_status_not_present) |
| { |
| svn_boolean_t modified; |
| apr_hash_t *props; |
| #ifdef HAVE_SYMLINK |
| svn_string_t *special_prop; |
| #endif |
| |
| SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath, |
| scratch_pool, scratch_pool)); |
| |
| #ifdef HAVE_SYMLINK |
| special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL); |
| |
| if ((special_prop != NULL) != special) |
| { |
| /* File/symlink mismatch. */ |
| SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); |
| on_disk = svn_node_none; |
| } |
| else |
| #endif |
| { |
| /* Issue #1663 asserts that we should compare a file in its |
| working copy format here, but before r1101473 we would only |
| do that if the file was already unequal to its recorded |
| information. |
| |
| r1101473 removes the option of asking for a working format |
| compare but *also* check the recorded information first, as |
| that combination doesn't guarantee a stable behavior. |
| (See the revert_test.py: revert_reexpand_keyword) |
| |
| But to have the same issue #1663 behavior for revert as we |
| had in <=1.6 we only have to check the recorded information |
| ourselves. And we already have everything we need, because |
| we called stat ourselves. */ |
| if (recorded_size != SVN_INVALID_FILESIZE |
| && recorded_time != 0 |
| && recorded_size == finfo.size |
| && recorded_time == finfo.mtime) |
| { |
| modified = FALSE; |
| } |
| else |
| /* Side effect: fixes recorded timestamps */ |
| SVN_ERR(svn_wc__internal_file_modified_p(&modified, |
| db, local_abspath, |
| TRUE, scratch_pool)); |
| |
| if (modified) |
| { |
| /* Install will replace the file */ |
| on_disk = svn_node_none; |
| } |
| else |
| { |
| if (status == svn_wc__db_status_normal) |
| { |
| svn_boolean_t read_only; |
| svn_string_t *needs_lock_prop; |
| |
| SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo, |
| scratch_pool)); |
| |
| needs_lock_prop = svn_hash_gets(props, |
| SVN_PROP_NEEDS_LOCK); |
| if (needs_lock_prop && !read_only) |
| { |
| SVN_ERR(svn_io_set_file_read_only(local_abspath, |
| FALSE, |
| scratch_pool)); |
| *notify_required = TRUE; |
| } |
| else if (!needs_lock_prop && read_only) |
| { |
| SVN_ERR(svn_io_set_file_read_write(local_abspath, |
| FALSE, |
| scratch_pool)); |
| *notify_required = TRUE; |
| } |
| } |
| |
| #if !defined(WIN32) && !defined(__OS2__) |
| #ifdef HAVE_SYMLINK |
| if (!special) |
| #endif |
| { |
| svn_boolean_t executable; |
| svn_string_t *executable_prop; |
| |
| SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo, |
| scratch_pool)); |
| executable_prop = svn_hash_gets(props, |
| SVN_PROP_EXECUTABLE); |
| if (executable_prop && !executable) |
| { |
| SVN_ERR(svn_io_set_file_executable(local_abspath, |
| TRUE, FALSE, |
| scratch_pool)); |
| *notify_required = TRUE; |
| } |
| else if (!executable_prop && executable) |
| { |
| SVN_ERR(svn_io_set_file_executable(local_abspath, |
| FALSE, FALSE, |
| scratch_pool)); |
| *notify_required = TRUE; |
| } |
| } |
| #endif |
| } |
| } |
| } |
| } |
| |
| /* If we expect a versioned item to be present and there is nothing |
| on disk then recreate it. */ |
| if (on_disk == svn_node_none |
| && status != svn_wc__db_status_server_excluded |
| && status != svn_wc__db_status_deleted |
| && status != svn_wc__db_status_excluded |
| && status != svn_wc__db_status_not_present) |
| { |
| if (kind == svn_node_dir) |
| SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); |
| |
| if (kind == svn_node_file) |
| { |
| svn_skel_t *work_item; |
| |
| SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath, |
| NULL, use_commit_times, TRUE, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, |
| scratch_pool)); |
| *run_wq = TRUE; |
| } |
| *notify_required = TRUE; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all reverts. */ |
| static svn_error_t * |
| revert(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| svn_boolean_t clear_changelists, |
| svn_boolean_t metadata_only, |
| svn_boolean_t added_keep_local, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| const struct svn_wc__db_info_t *info = NULL; |
| svn_boolean_t run_queue = FALSE; |
| |
| SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity); |
| |
| /* We should have a write lock on the parent of local_abspath, except |
| when local_abspath is the working copy root. */ |
| { |
| const char *dir_abspath; |
| svn_boolean_t is_wcroot; |
| |
| SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); |
| |
| if (! is_wcroot) |
| dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); |
| else |
| dir_abspath = local_abspath; |
| |
| SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); |
| } |
| |
| err = svn_error_trace( |
| svn_wc__db_op_revert(db, local_abspath, depth, clear_changelists, |
| scratch_pool, scratch_pool)); |
| |
| if (!err) |
| { |
| err = svn_error_trace( |
| svn_wc__db_read_single_info(&info, db, local_abspath, FALSE, |
| scratch_pool, scratch_pool)); |
| |
| if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) |
| { |
| svn_error_clear(err); |
| err = NULL; |
| info = NULL; |
| } |
| } |
| |
| if (!err) |
| err = svn_error_trace( |
| revert_restore(&run_queue, db, local_abspath, depth, metadata_only, |
| use_commit_times, TRUE /* revert root */, |
| added_keep_local, |
| info, cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool)); |
| |
| if (run_queue) |
| err = svn_error_compose_create(err, |
| svn_wc__wq_run(db, local_abspath, |
| cancel_func, cancel_baton, |
| scratch_pool)); |
| |
| err = svn_error_compose_create(err, |
| svn_wc__db_revert_list_done(db, |
| local_abspath, |
| scratch_pool)); |
| |
| return err; |
| } |
| |
| |
| /* Revert files in LOCAL_ABSPATH to depth DEPTH that match |
| CHANGELIST_HASH and notify for all reverts. */ |
| static svn_error_t * |
| revert_changelist(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| apr_hash_t *changelist_hash, |
| svn_boolean_t clear_changelists, |
| svn_boolean_t metadata_only, |
| svn_boolean_t added_keep_local, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool; |
| const apr_array_header_t *children; |
| int i; |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| /* Revert this node (depth=empty) if it matches one of the changelists. */ |
| if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash, |
| scratch_pool)) |
| SVN_ERR(revert(db, local_abspath, |
| svn_depth_empty, use_commit_times, clear_changelists, |
| metadata_only, added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool)); |
| |
| if (depth == svn_depth_empty) |
| return SVN_NO_ERROR; |
| |
| iterpool = svn_pool_create(scratch_pool); |
| |
| /* We can handle both depth=files and depth=immediates by setting |
| depth=empty here. We don't need to distinguish files and |
| directories when making the recursive call because directories |
| can never match a changelist, so making the recursive call for |
| directories when asked for depth=files is a no-op. */ |
| if (depth == svn_depth_files || depth == svn_depth_immediates) |
| depth = svn_depth_empty; |
| |
| SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, |
| local_abspath, |
| scratch_pool, |
| iterpool)); |
| for (i = 0; i < children->nelts; ++i) |
| { |
| const char *child_abspath; |
| |
| svn_pool_clear(iterpool); |
| |
| child_abspath = svn_dirent_join(local_abspath, |
| APR_ARRAY_IDX(children, i, |
| const char *), |
| iterpool); |
| |
| SVN_ERR(revert_changelist(db, child_abspath, depth, |
| use_commit_times, changelist_hash, |
| clear_changelists, metadata_only, |
| added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH |
| (which must be either svn_depth_files or svn_depth_immediates) by |
| doing a non-recursive revert on each permissible path. Notifies |
| all reverted paths. |
| |
| ### This won't revert a copied dir with one level of children since |
| ### the non-recursive revert on the dir will fail. Not sure how a |
| ### partially recursive revert should handle actual-only nodes. */ |
| static svn_error_t * |
| revert_partial(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| svn_boolean_t clear_changelists, |
| svn_boolean_t metadata_only, |
| svn_boolean_t added_keep_local, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool; |
| const apr_array_header_t *children; |
| int i; |
| |
| SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates); |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| iterpool = svn_pool_create(scratch_pool); |
| |
| /* Revert the root node itself (depth=empty), then move on to the |
| children. */ |
| SVN_ERR(revert(db, local_abspath, svn_depth_empty, |
| use_commit_times, clear_changelists, metadata_only, |
| added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, iterpool)); |
| |
| SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, |
| local_abspath, |
| scratch_pool, |
| iterpool)); |
| for (i = 0; i < children->nelts; ++i) |
| { |
| const char *child_abspath; |
| |
| svn_pool_clear(iterpool); |
| |
| child_abspath = svn_dirent_join(local_abspath, |
| APR_ARRAY_IDX(children, i, const char *), |
| iterpool); |
| |
| /* For svn_depth_files: don't revert non-files. */ |
| if (depth == svn_depth_files) |
| { |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath, |
| FALSE /* allow_missing */, |
| TRUE /* show_deleted */, |
| FALSE /* show_hidden */, |
| iterpool)); |
| if (kind != svn_node_file) |
| continue; |
| } |
| |
| /* Revert just this node (depth=empty). */ |
| SVN_ERR(revert(db, child_abspath, |
| svn_depth_empty, use_commit_times, clear_changelists, |
| metadata_only, added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_revert6(svn_wc_context_t *wc_ctx, |
| const char *local_abspath, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| const apr_array_header_t *changelist_filter, |
| svn_boolean_t clear_changelists, |
| svn_boolean_t metadata_only, |
| svn_boolean_t added_keep_local, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| if (changelist_filter && changelist_filter->nelts) |
| { |
| apr_hash_t *changelist_hash; |
| |
| SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, |
| scratch_pool)); |
| return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath, |
| depth, use_commit_times, |
| changelist_hash, |
| clear_changelists, |
| metadata_only, |
| added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool)); |
| } |
| |
| if (depth == svn_depth_empty || depth == svn_depth_infinity) |
| return svn_error_trace(revert(wc_ctx->db, local_abspath, |
| depth, use_commit_times, clear_changelists, |
| metadata_only, |
| added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool)); |
| |
| /* The user may expect svn_depth_files/svn_depth_immediates to work |
| on copied dirs with one level of children. It doesn't, the user |
| will get an error and will need to invoke an infinite revert. If |
| we identified those cases where svn_depth_infinity would not |
| revert too much we could invoke the recursive call above. */ |
| |
| if (depth == svn_depth_files || depth == svn_depth_immediates) |
| return svn_error_trace(revert_partial(wc_ctx->db, local_abspath, |
| depth, use_commit_times, |
| clear_changelists, metadata_only, |
| added_keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| scratch_pool)); |
| |
| /* Bogus depth. Tell the caller. */ |
| return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL); |
| } |