summaryrefslogtreecommitdiff
path: root/subversion/libsvn_repos/replay.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/replay.c')
-rw-r--r--subversion/libsvn_repos/replay.c751
1 files changed, 707 insertions, 44 deletions
diff --git a/subversion/libsvn_repos/replay.c b/subversion/libsvn_repos/replay.c
index 5103280..985a673 100644
--- a/subversion/libsvn_repos/replay.c
+++ b/subversion/libsvn_repos/replay.c
@@ -27,14 +27,18 @@
#include "svn_types.h"
#include "svn_delta.h"
+#include "svn_hash.h"
#include "svn_fs.h"
#include "svn_checksum.h"
#include "svn_repos.h"
+#include "svn_sorts.h"
#include "svn_props.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "private/svn_fspath.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_delta_private.h"
/*** Backstory ***/
@@ -99,6 +103,7 @@
(though not necessarily in the same order in which they
occurred). */
+/* #define USE_EV2_IMPL */
/*** Helper functions. ***/
@@ -136,7 +141,6 @@ struct path_driver_cb_baton
void *authz_read_baton;
const char *base_path; /* relpath */
- int base_path_len;
svn_revnum_t low_water_mark;
/* Stack of active copy operations. */
@@ -214,10 +218,10 @@ add_subdir(svn_fs_root_t *source_root,
changed path (because it was modified after the copy but before the
commit), we remove it from the changed_paths hash so that future
calls to path_driver_cb_func will ignore it. */
- change = apr_hash_get(changed_paths, new_edit_path, APR_HASH_KEY_STRING);
+ change = svn_hash_gets(changed_paths, new_edit_path);
if (change)
{
- apr_hash_set(changed_paths, new_edit_path, APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(changed_paths, new_edit_path, NULL);
/* If it's a delete, skip this entry. */
if (change->change_kind == svn_fs_path_change_delete)
@@ -338,20 +342,6 @@ add_subdir(svn_fs_root_t *source_root,
return SVN_NO_ERROR;
}
-static svn_boolean_t
-is_within_base_path(const char *path, const char *base_path,
- apr_ssize_t base_len)
-{
- if (base_path[0] == '\0')
- return TRUE;
-
- if (strncmp(base_path, path, base_len) == 0
- && (path[base_len] == '/' || path[base_len] == '\0'))
- return TRUE;
-
- return FALSE;
-}
-
/* Given PATH deleted under ROOT, return in READABLE whether the path was
readable prior to the deletion. Consult COPIES (a stack of 'struct
copy_info') and AUTHZ_READ_FUNC. */
@@ -378,7 +368,7 @@ was_readable(svn_boolean_t *readable,
}
if (copies->nelts != 0)
- info = &APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info);
+ info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *);
/* Are we under a copy? */
if (info && (relpath = svn_relpath_skip_ancestor(info->path, path)))
@@ -415,7 +405,12 @@ was_readable(svn_boolean_t *readable,
revision root, fspath, and revnum of the copyfrom of CHANGE, which
corresponds to PATH under ROOT. If the copyfrom info is valid
(i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE
- too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided. */
+ too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided.
+
+ NOTE: If the copyfrom information in CHANGE is marked as unknown
+ (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be
+ trusted), this function will also update those members of the
+ CHANGE structure to carry accurate copyfrom information. */
static svn_error_t *
fill_copyfrom(svn_fs_root_t **copyfrom_root,
const char **copyfrom_path,
@@ -481,7 +476,6 @@ path_driver_cb_func(void **dir_baton,
svn_fs_root_t *source_root = cb->compare_root;
const char *source_fspath = NULL;
const char *base_path = cb->base_path;
- int base_path_len = cb->base_path_len;
*dir_baton = NULL;
@@ -493,11 +487,11 @@ path_driver_cb_func(void **dir_baton,
while (cb->copies->nelts > 0
&& ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies,
cb->copies->nelts - 1,
- struct copy_info).path,
+ struct copy_info *)->path,
edit_path))
- cb->copies->nelts--;
+ apr_array_pop(cb->copies);
- change = apr_hash_get(cb->changed_paths, edit_path, APR_HASH_KEY_STRING);
+ change = svn_hash_gets(cb->changed_paths, edit_path);
if (! change)
{
/* This can only happen if the path was removed from cb->changed_paths
@@ -571,8 +565,7 @@ path_driver_cb_func(void **dir_baton,
all. */
if (copyfrom_path
&& ((! src_readable)
- || (! is_within_base_path(copyfrom_path + 1, base_path,
- base_path_len))
+ || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL)
|| (cb->low_water_mark > copyfrom_rev)))
{
copyfrom_path = NULL;
@@ -613,11 +606,13 @@ path_driver_cb_func(void **dir_baton,
(possibly nested) copy operation. */
if (change->node_kind == svn_node_dir)
{
- struct copy_info *info = &APR_ARRAY_PUSH(cb->copies,
- struct copy_info);
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
info->path = apr_pstrdup(cb->pool, edit_path);
info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path);
info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
}
/* Save the source so that we can use it later, when we
@@ -633,11 +628,13 @@ path_driver_cb_func(void **dir_baton,
past... */
if (change->node_kind == svn_node_dir && cb->copies->nelts > 0)
{
- struct copy_info *info = &APR_ARRAY_PUSH(cb->copies,
- struct copy_info);
+ struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info));
+
info->path = apr_pstrdup(cb->pool, edit_path);
info->copyfrom_path = NULL;
info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info;
}
source_root = NULL;
source_fspath = NULL;
@@ -670,13 +667,14 @@ path_driver_cb_func(void **dir_baton,
delta source. */
if (cb->copies->nelts > 0)
{
- struct copy_info *info = &APR_ARRAY_IDX(cb->copies,
- cb->copies->nelts - 1,
- struct copy_info);
+ struct copy_info *info = APR_ARRAY_IDX(cb->copies,
+ cb->copies->nelts - 1,
+ struct copy_info *);
if (info->copyfrom_path)
{
- const char *relpath = svn_relpath__is_child(info->path,
- edit_path, pool);
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ edit_path);
+ SVN_ERR_ASSERT(relpath && *relpath);
SVN_ERR(svn_fs_revision_root(&source_root,
svn_fs_root_fs(root),
info->copyfrom_rev, pool));
@@ -797,6 +795,49 @@ path_driver_cb_func(void **dir_baton,
return SVN_NO_ERROR;
}
+#ifdef USE_EV2_IMPL
+static svn_error_t *
+fetch_kind_func(svn_node_kind_t *kind,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_props_func(apr_hash_t **props,
+ void *baton,
+ const char *path,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_root_t *root = baton;
+ svn_fs_root_t *prev_root;
+ svn_fs_t *fs = svn_fs_root_fs(root);
+
+ if (!SVN_IS_VALID_REVNUM(base_revision))
+ base_revision = svn_fs_revision_root_revision(root) - 1;
+
+ SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool));
+ SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool));
+
+ return SVN_NO_ERROR;
+}
+#endif
+
@@ -811,12 +852,12 @@ svn_repos_replay2(svn_fs_root_t *root,
void *authz_read_baton,
apr_pool_t *pool)
{
+#ifndef USE_EV2_IMPL
apr_hash_t *fs_changes;
apr_hash_t *changed_paths;
apr_hash_index_t *hi;
apr_array_header_t *paths;
struct path_driver_cb_baton cb_baton;
- size_t base_path_len;
/* Special-case r0, which we know is an empty revision; if we don't
special-case it we might end up trying to compare it to "r-1". */
@@ -834,8 +875,6 @@ svn_repos_replay2(svn_fs_root_t *root,
else if (base_path[0] == '/')
++base_path;
- base_path_len = strlen(base_path);
-
/* Make an array from the keys of our CHANGED_PATHS hash, and copy
the values into a new hash whose keys have no leading slashes. */
paths = apr_array_make(pool, apr_hash_count(fs_changes),
@@ -868,14 +907,14 @@ svn_repos_replay2(svn_fs_root_t *root,
/* If the base_path doesn't match the top directory of this path
we don't want anything to do with it... */
- if (is_within_base_path(path, base_path, base_path_len))
+ if (svn_relpath_skip_ancestor(base_path, path) != NULL)
{
APR_ARRAY_PUSH(paths, const char *) = path;
apr_hash_set(changed_paths, path, keylen, change);
}
/* ...unless this was a change to one of the parent directories of
base_path. */
- else if (is_within_base_path(base_path, path, keylen))
+ else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
{
APR_ARRAY_PUSH(paths, const char *) = path;
apr_hash_set(changed_paths, path, keylen, change);
@@ -896,7 +935,6 @@ svn_repos_replay2(svn_fs_root_t *root,
cb_baton.authz_read_func = authz_read_func;
cb_baton.authz_read_baton = authz_read_baton;
cb_baton.base_path = base_path;
- cb_baton.base_path_len = base_path_len;
cb_baton.low_water_mark = low_water_mark;
cb_baton.compare_root = NULL;
@@ -910,7 +948,7 @@ svn_repos_replay2(svn_fs_root_t *root,
pool));
}
- cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info));
+ cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *));
cb_baton.pool = pool;
/* Determine the revision to use throughout the edit, and call
@@ -922,7 +960,632 @@ svn_repos_replay2(svn_fs_root_t *root,
}
/* Call the path-based editor driver. */
- return svn_delta_path_driver(editor, edit_baton,
- SVN_INVALID_REVNUM, paths,
- path_driver_cb_func, &cb_baton, pool);
+ return svn_delta_path_driver2(editor, edit_baton,
+ paths, TRUE,
+ path_driver_cb_func, &cb_baton, pool);
+#else
+ svn_editor_t *editorv2;
+ struct svn_delta__extra_baton *exb;
+ svn_delta__unlock_func_t unlock_func;
+ svn_boolean_t send_abs_paths;
+ const char *repos_root = "";
+ void *unlock_baton;
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ SVN_ERR(editor->set_target_revision(edit_baton, 0, pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* Determine the revision to use throughout the edit, and call
+ EDITOR's set_target_revision() function. */
+ if (svn_fs_is_revision_root(root))
+ {
+ svn_revnum_t revision = svn_fs_revision_root_revision(root);
+ SVN_ERR(editor->set_target_revision(edit_baton, revision, pool));
+ }
+
+ if (! base_path)
+ base_path = "";
+ else if (base_path[0] == '/')
+ ++base_path;
+
+ /* Use the shim to convert our editor to an Ev2 editor, and pass it down
+ the stack. */
+ SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb,
+ &unlock_func, &unlock_baton,
+ editor, edit_baton,
+ &send_abs_paths,
+ repos_root, "",
+ NULL, NULL,
+ fetch_kind_func, root,
+ fetch_props_func, root,
+ pool, pool));
+
+ /* Tell the shim that we're starting the process. */
+ SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root)));
+
+ /* ### We're ignoring SEND_DELTAS here. */
+ SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark,
+ editorv2, authz_read_func, authz_read_baton,
+ pool));
+
+ return SVN_NO_ERROR;
+#endif
+}
+
+
+/*****************************************************************
+ * Ev2 Implementation *
+ *****************************************************************/
+
+/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting
+ the appropriate editor calls to add it and its children without any
+ history. This is meant to be used when either a subset of the tree
+ has been ignored and we need to copy something from that subset to
+ the part of the tree we do care about, or if a subset of the tree is
+ unavailable because of authz and we need to use it as the source of
+ a copy. */
+static svn_error_t *
+add_subdir_ev2(svn_fs_root_t *source_root,
+ svn_fs_root_t *target_root,
+ svn_editor_t *editor,
+ const char *repos_relpath,
+ const char *source_fspath,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_hash_t *changed_paths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_index_t *hi;
+ apr_hash_t *dirents;
+ apr_hash_t *props = NULL;
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children,
+ props, SVN_INVALID_REVNUM));
+
+ /* We have to get the dirents from the source path, not the target,
+ because we want nested copies from *readable* paths to be handled by
+ path_driver_cb_func, not add_subdir (in order to preserve history). */
+ SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_path_change2_t *change;
+ svn_boolean_t readable = TRUE;
+ svn_fs_dirent_t *dent = svn__apr_hash_index_val(hi);
+ const char *copyfrom_path = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+ const char *child_relpath;
+
+ svn_pool_clear(iterpool);
+
+ child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool);
+
+ /* If a file or subdirectory of the copied directory is listed as a
+ changed path (because it was modified after the copy but before the
+ commit), we remove it from the changed_paths hash so that future
+ calls to path_driver_cb_func will ignore it. */
+ change = svn_hash_gets(changed_paths, child_relpath);
+ if (change)
+ {
+ svn_hash_sets(changed_paths, child_relpath, NULL);
+
+ /* If it's a delete, skip this entry. */
+ if (change->change_kind == svn_fs_path_change_delete)
+ continue;
+
+ /* If it's a replacement, check for copyfrom info (if we
+ don't have it already. */
+ if (change->change_kind == svn_fs_path_change_replace)
+ {
+ if (! change->copyfrom_known)
+ {
+ SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev,
+ &change->copyfrom_path,
+ target_root, child_relpath,
+ result_pool));
+ change->copyfrom_known = TRUE;
+ }
+ copyfrom_path = change->copyfrom_path;
+ copyfrom_rev = change->copyfrom_rev;
+ }
+ }
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&readable, target_root, child_relpath,
+ authz_read_baton, iterpool));
+
+ if (! readable)
+ continue;
+
+ if (dent->kind == svn_node_dir)
+ {
+ svn_fs_root_t *new_source_root;
+ const char *new_source_fspath;
+
+ if (copyfrom_path)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(source_root);
+ SVN_ERR(svn_fs_revision_root(&new_source_root, fs,
+ copyfrom_rev, result_pool));
+ new_source_fspath = copyfrom_path;
+ }
+ else
+ {
+ new_source_root = source_root;
+ new_source_fspath = svn_fspath__join(source_fspath, dent->name,
+ iterpool);
+ }
+
+ /* ### authz considerations?
+ *
+ * I think not; when path_driver_cb_func() calls add_subdir(), it
+ * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable.
+ */
+ if (change && change->change_kind == svn_fs_path_change_replace
+ && copyfrom_path == NULL)
+ {
+ SVN_ERR(svn_editor_add_directory(editor, child_relpath,
+ children, props,
+ SVN_INVALID_REVNUM));
+ }
+ else
+ {
+ SVN_ERR(add_subdir_ev2(new_source_root, target_root,
+ editor, child_relpath,
+ new_source_fspath,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool, iterpool));
+ }
+ }
+ else if (dent->kind == svn_node_file)
+ {
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, target_root,
+ child_relpath, iterpool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ target_root,
+ child_relpath, TRUE, iterpool));
+
+ SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum,
+ contents, props, SVN_INVALID_REVNUM));
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+replay_node(svn_fs_root_t *root,
+ const char *repos_relpath,
+ svn_editor_t *editor,
+ svn_revnum_t low_water_mark,
+ const char *base_repos_relpath,
+ apr_array_header_t *copies,
+ apr_hash_t *changed_paths,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_path_change2_t *change;
+ svn_boolean_t do_add = FALSE;
+ svn_boolean_t do_delete = FALSE;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+ svn_revnum_t replaces_rev;
+
+ /* First, flush the copies stack so it only contains ancestors of path. */
+ while (copies->nelts > 0
+ && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *)->path,
+ repos_relpath) == NULL) )
+ apr_array_pop(copies);
+
+ change = svn_hash_gets(changed_paths, repos_relpath);
+ if (! change)
+ {
+ /* This can only happen if the path was removed from changed_paths
+ by an earlier call to add_subdir, which means the path was already
+ handled and we should simply ignore it. */
+ return SVN_NO_ERROR;
+ }
+ switch (change->change_kind)
+ {
+ case svn_fs_path_change_add:
+ do_add = TRUE;
+ break;
+
+ case svn_fs_path_change_delete:
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_replace:
+ do_add = TRUE;
+ do_delete = TRUE;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ /* Handle any deletions. */
+ if (do_delete && ! do_add)
+ {
+ svn_boolean_t readable;
+
+ /* Issue #4121: delete under under a copy, of a path that was unreadable
+ at its pre-copy location. */
+ SVN_ERR(was_readable(&readable, root, repos_relpath, copies,
+ authz_read_func, authz_read_baton,
+ scratch_pool, scratch_pool));
+ if (readable)
+ SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM));
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle replacements. */
+ if (do_delete && do_add)
+ replaces_rev = svn_fs_revision_root_revision(root);
+ else
+ replaces_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the node kind if it makes sense to do so. */
+ if (! do_delete || do_add)
+ {
+ if (change->node_kind == svn_node_unknown)
+ SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath,
+ scratch_pool));
+ if ((change->node_kind != svn_node_dir) &&
+ (change->node_kind != svn_node_file))
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Filesystem path '%s' is neither a file "
+ "nor a directory"), repos_relpath);
+ }
+
+ /* Handle any adds/opens. */
+ if (do_add)
+ {
+ svn_boolean_t src_readable;
+ svn_fs_root_t *copyfrom_root;
+
+ /* Was this node copied? */
+ SVN_ERR(fill_copyfrom(&copyfrom_root, &copyfrom_path, &copyfrom_rev,
+ &src_readable, root, change,
+ authz_read_func, authz_read_baton,
+ repos_relpath, scratch_pool, scratch_pool));
+
+ /* If we have a copyfrom path, and we can't read it or we're just
+ ignoring it, or the copyfrom rev is prior to the low water mark
+ then we just null them out and do a raw add with no history at
+ all. */
+ if (copyfrom_path
+ && ((! src_readable)
+ || (svn_relpath_skip_ancestor(base_repos_relpath,
+ copyfrom_path + 1) == NULL)
+ || (low_water_mark > copyfrom_rev)))
+ {
+ copyfrom_path = NULL;
+ copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+
+ /* Do the right thing based on the path KIND. */
+ if (change->node_kind == svn_node_dir)
+ {
+ /* If this is a copy, but we can't represent it as such,
+ then we just do a recursive add of the source path
+ contents. */
+ if (change->copyfrom_path && ! copyfrom_path)
+ {
+ SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor,
+ repos_relpath, change->copyfrom_path,
+ authz_read_func, authz_read_baton,
+ changed_paths, result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_array_header_t *children;
+ apr_hash_t *props;
+ apr_hash_t *dirents;
+
+ SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath,
+ scratch_pool));
+ SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool));
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_editor_add_directory(editor, repos_relpath,
+ children, props,
+ replaces_rev));
+ }
+ }
+ }
+ else
+ {
+ if (copyfrom_path)
+ {
+ if (copyfrom_path[0] == '/')
+ ++copyfrom_path;
+ SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev,
+ repos_relpath, replaces_rev));
+ }
+ else
+ {
+ apr_hash_t *props;
+ svn_checksum_t *checksum;
+ svn_stream_t *contents;
+
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root,
+ repos_relpath, TRUE, scratch_pool));
+
+ SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum,
+ contents, props, replaces_rev));
+ }
+ }
+
+ /* If we represent this as a copy... */
+ if (copyfrom_path)
+ {
+ /* If it is a directory, make sure descendants get the correct
+ delta source by remembering that we are operating inside a
+ (possibly nested) copy operation. */
+ if (change->node_kind == svn_node_dir)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ info->copyfrom_rev = copyfrom_rev;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ else
+ /* Else, we are an add without history... */
+ {
+ /* If an ancestor is added with history, we need to forget about
+ that here, go on with life and repeat all the mistakes of our
+ past... */
+ if (change->node_kind == svn_node_dir && copies->nelts > 0)
+ {
+ struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info));
+
+ info->path = apr_pstrdup(result_pool, repos_relpath);
+ info->copyfrom_path = NULL;
+ info->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ APR_ARRAY_PUSH(copies, struct copy_info *) = info;
+ }
+ }
+ }
+ else if (! do_delete)
+ {
+ /* If we are inside an add with history, we need to adjust the
+ delta source. */
+ if (copies->nelts > 0)
+ {
+ struct copy_info *info = APR_ARRAY_IDX(copies,
+ copies->nelts - 1,
+ struct copy_info *);
+ if (info->copyfrom_path)
+ {
+ const char *relpath = svn_relpath_skip_ancestor(info->path,
+ repos_relpath);
+ SVN_ERR_ASSERT(relpath && *relpath);
+ repos_relpath = svn_relpath_join(info->copyfrom_path,
+ relpath, scratch_pool);
+ }
+ }
+ }
+
+ if (! do_delete && !do_add)
+ {
+ apr_hash_t *props = NULL;
+
+ /* Is this a copy that was downgraded to a raw add? (If so,
+ we'll need to transmit properties and file contents and such
+ for it regardless of what the CHANGE structure's text_mod
+ and prop_mod flags say.) */
+ svn_boolean_t downgraded_copy = (change->copyfrom_known
+ && change->copyfrom_path
+ && (! copyfrom_path));
+
+ /* Handle property modifications. */
+ if (change->prop_mod || downgraded_copy)
+ {
+ SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath,
+ scratch_pool));
+ }
+
+ /* Handle textual modifications. */
+ if (change->node_kind == svn_node_file
+ && (change->text_mod || change->prop_mod || downgraded_copy))
+ {
+ svn_checksum_t *checksum = NULL;
+ svn_stream_t *contents = NULL;
+
+ if (change->text_mod)
+ {
+ SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
+ root, repos_relpath, TRUE,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath,
+ scratch_pool));
+ }
+
+ SVN_ERR(svn_editor_alter_file(editor, repos_relpath,
+ SVN_INVALID_REVNUM, props, checksum,
+ contents));
+ }
+
+ if (change->node_kind == svn_node_dir
+ && (change->prop_mod || downgraded_copy))
+ {
+ apr_array_header_t *children = NULL;
+
+ SVN_ERR(svn_editor_alter_directory(editor, repos_relpath,
+ SVN_INVALID_REVNUM, children,
+ props));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos__replay_ev2(svn_fs_root_t *root,
+ const char *base_repos_relpath,
+ svn_revnum_t low_water_mark,
+ svn_editor_t *editor,
+ svn_repos_authz_func_t authz_read_func,
+ void *authz_read_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *fs_changes;
+ apr_hash_t *changed_paths;
+ apr_hash_index_t *hi;
+ apr_array_header_t *paths;
+ apr_array_header_t *copies;
+ apr_pool_t *iterpool;
+ svn_error_t *err = SVN_NO_ERROR;
+ int i;
+
+ SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath));
+
+ /* Special-case r0, which we know is an empty revision; if we don't
+ special-case it we might end up trying to compare it to "r-1". */
+ if (svn_fs_is_revision_root(root)
+ && svn_fs_revision_root_revision(root) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Fetch the paths changed under ROOT. */
+ SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
+
+ /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+ the values into a new hash whose keys have no leading slashes. */
+ paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
+ sizeof(const char *));
+ changed_paths = apr_hash_make(scratch_pool);
+ for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t keylen;
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t allowed = TRUE;
+
+ apr_hash_this(hi, &key, &keylen, &val);
+ path = key;
+ change = val;
+
+ if (authz_read_func)
+ SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
+ scratch_pool));
+
+ if (allowed)
+ {
+ if (path[0] == '/')
+ {
+ path++;
+ keylen--;
+ }
+
+ /* If the base_path doesn't match the top directory of this path
+ we don't want anything to do with it... */
+ if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ /* ...unless this was a change to one of the parent directories of
+ base_path. */
+ else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
+ {
+ APR_ARRAY_PUSH(paths, const char *) = path;
+ apr_hash_set(changed_paths, path, keylen, change);
+ }
+ }
+ }
+
+ /* If we were not given a low water mark, assume that everything is there,
+ all the way back to revision 0. */
+ if (! SVN_IS_VALID_REVNUM(low_water_mark))
+ low_water_mark = 0;
+
+ copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *));
+
+ /* Sort the paths. Although not strictly required by the API, this has
+ the pleasant side effect of maintaining a consistent ordering of
+ dumpfile contents. */
+ qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths);
+
+ /* Now actually handle the various paths. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+ err = replay_node(root, repos_relpath, editor, low_water_mark,
+ base_repos_relpath, copies, changed_paths,
+ authz_read_func, authz_read_baton,
+ scratch_pool, iterpool);
+ if (err)
+ break;
+ }
+
+ if (err)
+ return svn_error_compose_create(err, svn_editor_abort(editor));
+ else
+ SVN_ERR(svn_editor_complete(editor));
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
}