diff options
Diffstat (limited to 'subversion/libsvn_repos/replay.c')
-rw-r--r-- | subversion/libsvn_repos/replay.c | 751 |
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(©from_root, ©from_path, ©from_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; } |