summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/revert.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/revert.c')
-rw-r--r--subversion/libsvn_wc/revert.c886
1 files changed, 886 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/revert.c b/subversion/libsvn_wc/revert.c
new file mode 100644
index 0000000..5e190e8
--- /dev/null
+++ b/subversion/libsvn_wc/revert.c
@@ -0,0 +1,886 @@
+/*
+ * 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"
+
+/* 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.
+
+ 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)
+{
+ const 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. */
+ qsort(copied_children->elts, copied_children->nelts,
+ sizeof(svn_wc__db_revert_list_copied_child_info_t *),
+ 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;
+}
+
+
+/* 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.
+ */
+static svn_error_t *
+revert_restore(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ svn_boolean_t revert_root,
+ 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;
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ svn_node_kind_t on_disk;
+ svn_boolean_t notify_required;
+ const apr_array_header_t *conflict_files;
+ svn_filesize_t recorded_size;
+ apr_time_t recorded_time;
+ apr_finfo_t finfo;
+#ifdef HAVE_SYMLINK
+ svn_boolean_t special;
+#endif
+ svn_boolean_t copied_here;
+ svn_node_kind_t reverted_kind;
+ svn_boolean_t is_wcroot;
+
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
+ if (is_wcroot && !revert_root)
+ {
+ /* 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(&notify_required,
+ &conflict_files,
+ &copied_here, &reverted_kind,
+ db, local_abspath,
+ scratch_pool, scratch_pool));
+
+ err = svn_wc__db_read_info(&status, &kind,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &recorded_size, &recorded_time, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ db, local_abspath, scratch_pool, scratch_pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+
+ if (!copied_here)
+ {
+ 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
+ {
+ /* ### 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;
+ }
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ 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
+ && 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 (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)
+ {
+ 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
+ SVN_ERR(svn_wc__internal_file_modified_p(&modified,
+ db, local_abspath,
+ TRUE, scratch_pool));
+
+ if (modified)
+ {
+ SVN_ERR(svn_io_remove_file2(local_abspath, FALSE,
+ scratch_pool));
+ 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;
+
+ /* ### Get the checksum from read_info above and pass in here? */
+ 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));
+ SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
+ scratch_pool));
+ }
+ notify_required = TRUE;
+ }
+
+ if (conflict_files)
+ {
+ int i;
+ for (i = 0; i < conflict_files->nelts; i++)
+ {
+ SVN_ERR(remove_conflict_file(&notify_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);
+ const apr_array_header_t *children;
+ int i;
+
+ SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
+ cancel_func, cancel_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);
+
+ SVN_ERR(revert_restore(db, child_abspath, depth,
+ use_commit_times, FALSE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ }
+
+ if (notify_func)
+ SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
+ db, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__revert_internal(svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t use_commit_times,
+ 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;
+
+ 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_wc__db_op_revert(db, local_abspath, depth,
+ scratch_pool, scratch_pool);
+
+ if (!err)
+ err = revert_restore(db, local_abspath, depth,
+ use_commit_times, TRUE /* revert root */,
+ cancel_func, cancel_baton,
+ notify_func, notify_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_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(svn_wc__revert_internal(db, local_abspath,
+ svn_depth_empty, use_commit_times,
+ 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,
+ 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_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(svn_wc__revert_internal(db, local_abspath, svn_depth_empty,
+ use_commit_times, 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(svn_wc__revert_internal(db, child_abspath,
+ svn_depth_empty, use_commit_times,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc_revert4(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_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,
+ cancel_func, cancel_baton,
+ notify_func, notify_baton,
+ scratch_pool));
+ }
+
+ if (depth == svn_depth_empty || depth == svn_depth_infinity)
+ return svn_error_trace(svn_wc__revert_internal(wc_ctx->db, local_abspath,
+ depth, use_commit_times,
+ 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,
+ 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);
+}
OpenPOWER on IntegriCloud