diff options
Diffstat (limited to 'subversion/libsvn_repos/dump.c')
-rw-r--r-- | subversion/libsvn_repos/dump.c | 1503 |
1 files changed, 1503 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/dump.c b/subversion/libsvn_repos/dump.c new file mode 100644 index 0000000..75843d7 --- /dev/null +++ b/subversion/libsvn_repos/dump.c @@ -0,0 +1,1503 @@ +/* dump.c --- writing filesystem contents into a portable 'dumpfile' format. + * + * ==================================================================== + * 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 "svn_private_config.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_fs.h" +#include "svn_hash.h" +#include "svn_iter.h" +#include "svn_repos.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_time.h" +#include "svn_checksum.h" +#include "svn_props.h" +#include "svn_sorts.h" + +#include "private/svn_mergeinfo_private.h" +#include "private/svn_fs_private.h" + +#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) + +/*----------------------------------------------------------------------*/ + + + +/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and + store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, + in which case the delta will be computed against an empty file, as + per the svn_fs_get_file_delta_stream docstring. Record the length + of the temporary file in *LEN, and rewind the file before + returning. */ +static svn_error_t * +store_delta(apr_file_t **tempfile, svn_filesize_t *len, + svn_fs_root_t *oldroot, const char *oldpath, + svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) +{ + svn_stream_t *temp_stream; + apr_off_t offset = 0; + svn_txdelta_stream_t *delta_stream; + svn_txdelta_window_handler_t wh; + void *whb; + + /* Create a temporary file and open a stream to it. Note that we need + the file handle in order to rewind it. */ + SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, + svn_io_file_del_on_pool_cleanup, + pool, pool)); + temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); + + /* Compute the delta and send it to the temporary file. */ + SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, + newroot, newpath, pool)); + svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); + SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); + + /* Get the length of the temporary file and rewind it. */ + SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); + *len = offset; + offset = 0; + return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); +} + + +/*----------------------------------------------------------------------*/ + +/** An editor which dumps node-data in 'dumpfile format' to a file. **/ + +/* Look, mom! No file batons! */ + +struct edit_baton +{ + /* The relpath which implicitly prepends all full paths coming into + this editor. This will almost always be "". */ + const char *path; + + /* The stream to dump to. */ + svn_stream_t *stream; + + /* Send feedback here, if non-NULL */ + svn_repos_notify_func_t notify_func; + void *notify_baton; + + /* The fs revision root, so we can read the contents of paths. */ + svn_fs_root_t *fs_root; + svn_revnum_t current_rev; + + /* The fs, so we can grab historic information if needed. */ + svn_fs_t *fs; + + /* True if dumped nodes should output deltas instead of full text. */ + svn_boolean_t use_deltas; + + /* True if this "dump" is in fact a verify. */ + svn_boolean_t verify; + + /* The first revision dumped in this dumpstream. */ + svn_revnum_t oldest_dumped_rev; + + /* If not NULL, set to true if any references to revisions older than + OLDEST_DUMPED_REV were found in the dumpstream. */ + svn_boolean_t *found_old_reference; + + /* If not NULL, set to true if any mergeinfo was dumped which contains + revisions older than OLDEST_DUMPED_REV. */ + svn_boolean_t *found_old_mergeinfo; + + /* reusable buffer for writing file contents */ + char buffer[SVN__STREAM_CHUNK_SIZE]; + apr_size_t bufsize; +}; + +struct dir_baton +{ + struct edit_baton *edit_baton; + struct dir_baton *parent_dir_baton; + + /* is this directory a new addition to this revision? */ + svn_boolean_t added; + + /* has this directory been written to the output stream? */ + svn_boolean_t written_out; + + /* the repository relpath associated with this directory */ + const char *path; + + /* The comparison repository relpath and revision of this directory. + If both of these are valid, use them as a source against which to + compare the directory instead of the default comparison source of + PATH in the previous revision. */ + const char *cmp_path; + svn_revnum_t cmp_rev; + + /* hash of paths that need to be deleted, though some -might- be + replaced. maps const char * paths to this dir_baton. (they're + full paths, because that's what the editor driver gives us. but + really, they're all within this directory.) */ + apr_hash_t *deleted_entries; + + /* pool to be used for deleting the hash items */ + apr_pool_t *pool; +}; + + +/* Make a directory baton to represent the directory was path + (relative to EDIT_BATON's path) is PATH. + + CMP_PATH/CMP_REV are the path/revision against which this directory + should be compared for changes. If either is omitted (NULL for the + path, SVN_INVALID_REVNUM for the rev), just compare this directory + PATH against itself in the previous revision. + + PARENT_DIR_BATON is the directory baton of this directory's parent, + or NULL if this is the top-level directory of the edit. ADDED + indicated if this directory is newly added in this revision. + Perform all allocations in POOL. */ +static struct dir_baton * +make_dir_baton(const char *path, + const char *cmp_path, + svn_revnum_t cmp_rev, + void *edit_baton, + void *parent_dir_baton, + svn_boolean_t added, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *pb = parent_dir_baton; + struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); + const char *full_path; + + /* A path relative to nothing? I don't think so. */ + SVN_ERR_ASSERT_NO_RETURN(!path || pb); + + /* Construct the full path of this node. */ + if (pb) + full_path = svn_relpath_join(eb->path, path, pool); + else + full_path = apr_pstrdup(pool, eb->path); + + /* Remove leading slashes from copyfrom paths. */ + if (cmp_path) + cmp_path = svn_relpath_canonicalize(cmp_path, pool); + + new_db->edit_baton = eb; + new_db->parent_dir_baton = pb; + new_db->path = full_path; + new_db->cmp_path = cmp_path; + new_db->cmp_rev = cmp_rev; + new_db->added = added; + new_db->written_out = FALSE; + new_db->deleted_entries = apr_hash_make(pool); + new_db->pool = pool; + + return new_db; +} + + +/* This helper is the main "meat" of the editor -- it does all the + work of writing a node record. + + Write out a node record for PATH of type KIND under EB->FS_ROOT. + ACTION describes what is happening to the node (see enum svn_node_action). + Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. + + If the node was itself copied, IS_COPY is TRUE and the + path/revision of the copy source are in CMP_PATH/CMP_REV. If + IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part + of a copied subtree. + */ +static svn_error_t * +dump_node(struct edit_baton *eb, + const char *path, + svn_node_kind_t kind, + enum svn_node_action action, + svn_boolean_t is_copy, + const char *cmp_path, + svn_revnum_t cmp_rev, + apr_pool_t *pool) +{ + svn_stringbuf_t *propstring; + svn_filesize_t content_length = 0; + apr_size_t len; + svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; + const char *compare_path = path; + svn_revnum_t compare_rev = eb->current_rev - 1; + svn_fs_root_t *compare_root = NULL; + apr_file_t *delta_file = NULL; + + /* Maybe validate the path. */ + if (eb->verify || eb->notify_func) + { + svn_error_t *err = svn_fs__path_valid(path, pool); + + if (err) + { + if (eb->notify_func) + { + char errbuf[512]; /* ### svn_strerror() magic number */ + svn_repos_notify_t *notify; + notify = svn_repos_notify_create(svn_repos_notify_warning, pool); + + notify->warning = svn_repos_notify_warning_invalid_fspath; + notify->warning_str = apr_psprintf( + pool, + _("E%06d: While validating fspath '%s': %s"), + err->apr_err, path, + svn_err_best_message(err, errbuf, sizeof(errbuf))); + + eb->notify_func(eb->notify_baton, notify, pool); + } + + /* Return the error in addition to notifying about it. */ + if (eb->verify) + return svn_error_trace(err); + else + svn_error_clear(err); + } + } + + /* Write out metadata headers for this file node. */ + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", + path)); + if (kind == svn_node_file) + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); + else if (kind == svn_node_dir) + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); + + /* Remove leading slashes from copyfrom paths. */ + if (cmp_path) + cmp_path = svn_relpath_canonicalize(cmp_path, pool); + + /* Validate the comparison path/rev. */ + if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) + { + compare_path = cmp_path; + compare_rev = cmp_rev; + } + + if (action == svn_node_action_change) + { + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); + + /* either the text or props changed, or possibly both. */ + SVN_ERR(svn_fs_revision_root(&compare_root, + svn_fs_root_fs(eb->fs_root), + compare_rev, pool)); + + SVN_ERR(svn_fs_props_changed(&must_dump_props, + compare_root, compare_path, + eb->fs_root, path, pool)); + if (kind == svn_node_file) + SVN_ERR(svn_fs_contents_changed(&must_dump_text, + compare_root, compare_path, + eb->fs_root, path, pool)); + } + else if (action == svn_node_action_replace) + { + if (! is_copy) + { + /* a simple delete+add, implied by a single 'replace' action. */ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION + ": replace\n")); + + /* definitely need to dump all content for a replace. */ + if (kind == svn_node_file) + must_dump_text = TRUE; + must_dump_props = TRUE; + } + else + { + /* more complex: delete original, then add-with-history. */ + + /* the path & kind headers have already been printed; just + add a delete action, and end the current record.*/ + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION + ": delete\n\n")); + + /* recurse: print an additional add-with-history record. */ + SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, + is_copy, compare_path, compare_rev, pool)); + + /* we can leave this routine quietly now, don't need to dump + any content; that was already done in the second record. */ + must_dump_text = FALSE; + must_dump_props = FALSE; + } + } + else if (action == svn_node_action_delete) + { + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); + + /* we can leave this routine quietly now, don't need to dump + any content. */ + must_dump_text = FALSE; + must_dump_props = FALSE; + } + else if (action == svn_node_action_add) + { + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); + + if (! is_copy) + { + /* Dump all contents for a simple 'add'. */ + if (kind == svn_node_file) + must_dump_text = TRUE; + must_dump_props = TRUE; + } + else + { + if (!eb->verify && cmp_rev < eb->oldest_dumped_rev + && eb->notify_func) + { + svn_repos_notify_t *notify = + svn_repos_notify_create(svn_repos_notify_warning, pool); + + notify->warning = svn_repos_notify_warning_found_old_reference; + notify->warning_str = apr_psprintf( + pool, + _("Referencing data in revision %ld," + " which is older than the oldest" + " dumped revision (r%ld). Loading this dump" + " into an empty repository" + " will fail."), + cmp_rev, eb->oldest_dumped_rev); + if (eb->found_old_reference) + *eb->found_old_reference = TRUE; + eb->notify_func(eb->notify_baton, notify, pool); + } + + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV + ": %ld\n" + SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH + ": %s\n", + cmp_rev, cmp_path)); + + SVN_ERR(svn_fs_revision_root(&compare_root, + svn_fs_root_fs(eb->fs_root), + compare_rev, pool)); + + /* Need to decide if the copied node had any extra textual or + property mods as well. */ + SVN_ERR(svn_fs_props_changed(&must_dump_props, + compare_root, compare_path, + eb->fs_root, path, pool)); + if (kind == svn_node_file) + { + svn_checksum_t *checksum; + const char *hex_digest; + SVN_ERR(svn_fs_contents_changed(&must_dump_text, + compare_root, compare_path, + eb->fs_root, path, pool)); + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, + compare_root, compare_path, + FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 + ": %s\n", hex_digest)); + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, + compare_root, compare_path, + FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 + ": %s\n", hex_digest)); + } + } + } + + if ((! must_dump_text) && (! must_dump_props)) + { + /* If we're not supposed to dump text or props, so be it, we can + just go home. However, if either one needs to be dumped, + then our dumpstream format demands that at a *minimum*, we + see a lone "PROPS-END" as a divider between text and props + content within the content-block. */ + len = 2; + return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ + } + + /*** Start prepping content to dump... ***/ + + /* If we are supposed to dump properties, write out a property + length header and generate a stringbuf that contains those + property values here. */ + if (must_dump_props) + { + apr_hash_t *prophash, *oldhash = NULL; + apr_size_t proplen; + svn_stream_t *propstream; + + SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); + + /* If this is a partial dump, then issue a warning if we dump mergeinfo + properties that refer to revisions older than the first revision + dumped. */ + if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) + { + svn_string_t *mergeinfo_str = svn_hash_gets(prophash, + SVN_PROP_MERGEINFO); + if (mergeinfo_str) + { + svn_mergeinfo_t mergeinfo, old_mergeinfo; + + SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data, + pool)); + SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( + &old_mergeinfo, mergeinfo, + eb->oldest_dumped_rev - 1, 0, + TRUE, pool, pool)); + if (apr_hash_count(old_mergeinfo)) + { + svn_repos_notify_t *notify = + svn_repos_notify_create(svn_repos_notify_warning, pool); + + notify->warning = svn_repos_notify_warning_found_old_mergeinfo; + notify->warning_str = apr_psprintf( + pool, + _("Mergeinfo referencing revision(s) prior " + "to the oldest dumped revision (r%ld). " + "Loading this dump may result in invalid " + "mergeinfo."), + eb->oldest_dumped_rev); + + if (eb->found_old_mergeinfo) + *eb->found_old_mergeinfo = TRUE; + eb->notify_func(eb->notify_baton, notify, pool); + } + } + } + + if (eb->use_deltas && compare_root) + { + /* Fetch the old property hash to diff against and output a header + saying that our property contents are a delta. */ + SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, + pool)); + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); + } + else + oldhash = apr_hash_make(pool); + propstring = svn_stringbuf_create_ensure(0, pool); + propstream = svn_stream_from_stringbuf(propstring, pool); + SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, + "PROPS-END", pool)); + SVN_ERR(svn_stream_close(propstream)); + proplen = propstring->len; + content_length += proplen; + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH + ": %" APR_SIZE_T_FMT "\n", proplen)); + } + + /* If we are supposed to dump text, write out a text length header + here, and an MD5 checksum (if available). */ + if (must_dump_text && (kind == svn_node_file)) + { + svn_checksum_t *checksum; + const char *hex_digest; + svn_filesize_t textlen; + + if (eb->use_deltas) + { + /* Compute the text delta now and write it into a temporary + file, so that we can find its length. Output a header + saying our text contents are a delta. */ + SVN_ERR(store_delta(&delta_file, &textlen, compare_root, + compare_path, eb->fs_root, path, pool)); + SVN_ERR(svn_stream_puts(eb->stream, + SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); + + if (compare_root) + { + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, + compare_root, compare_path, + FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 + ": %s\n", hex_digest)); + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, + compare_root, compare_path, + FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 + ": %s\n", hex_digest)); + } + } + else + { + /* Just fetch the length of the file. */ + SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); + } + + content_length += textlen; + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH + ": %" SVN_FILESIZE_T_FMT "\n", textlen)); + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, + eb->fs_root, path, FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 + ": %s\n", hex_digest)); + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, + eb->fs_root, path, FALSE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + if (hex_digest) + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 + ": %s\n", hex_digest)); + } + + /* 'Content-length:' is the last header before we dump the content, + and is the sum of the text and prop contents lengths. We write + this only for the benefit of non-Subversion RFC-822 parsers. */ + SVN_ERR(svn_stream_printf(eb->stream, pool, + SVN_REPOS_DUMPFILE_CONTENT_LENGTH + ": %" SVN_FILESIZE_T_FMT "\n\n", + content_length)); + + /* Dump property content if we're supposed to do so. */ + if (must_dump_props) + { + len = propstring->len; + SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); + } + + /* Dump text content */ + if (must_dump_text && (kind == svn_node_file)) + { + svn_stream_t *contents; + + if (delta_file) + { + /* Make sure to close the underlying file when the stream is + closed. */ + contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); + } + else + SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); + + SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), + NULL, NULL, pool)); + } + + len = 2; + return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ +} + + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, + edit_baton, NULL, FALSE, pool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + const char *mypath = apr_pstrdup(pb->pool, path); + + /* remember this path needs to be deleted. */ + svn_hash_sets(pb->deleted_entries, mypath, pb); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + void *val; + svn_boolean_t is_copy = FALSE; + struct dir_baton *new_db + = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool); + + /* This might be a replacement -- is the path already deleted? */ + val = svn_hash_gets(pb->deleted_entries, path); + + /* Detect an add-with-history. */ + is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); + + /* Dump the node. */ + SVN_ERR(dump_node(eb, path, + svn_node_dir, + val ? svn_node_action_replace : svn_node_action_add, + is_copy, + is_copy ? copyfrom_path : NULL, + is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, + pool)); + + if (val) + /* Delete the path, it's now been dumped. */ + svn_hash_sets(pb->deleted_entries, path, NULL); + + new_db->written_out = TRUE; + + *child_baton = new_db; + return SVN_NO_ERROR; +} + + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *new_db; + const char *cmp_path = NULL; + svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; + + /* If the parent directory has explicit comparison path and rev, + record the same for this one. */ + if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) + { + cmp_path = svn_relpath_join(pb->cmp_path, + svn_relpath_basename(path, pool), pool); + cmp_rev = pb->cmp_rev; + } + + new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool); + *child_baton = new_db; + return SVN_NO_ERROR; +} + + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + apr_pool_t *subpool = svn_pool_create(pool); + int i; + apr_array_header_t *sorted_entries; + + /* Sort entries lexically instead of as paths. Even though the entries + * are full paths they're all in the same directory (see comment in struct + * dir_baton definition). So we really want to sort by basename, in which + * case the lexical sort function is more efficient. */ + sorted_entries = svn_sort__hash(db->deleted_entries, + svn_sort_compare_items_lexically, pool); + for (i = 0; i < sorted_entries->nelts; i++) + { + const char *path = APR_ARRAY_IDX(sorted_entries, i, + svn_sort__item_t).key; + + svn_pool_clear(subpool); + + /* By sending 'svn_node_unknown', the Node-kind: header simply won't + be written out. No big deal at all, really. The loader + shouldn't care. */ + SVN_ERR(dump_node(eb, path, + svn_node_unknown, svn_node_action_delete, + FALSE, NULL, SVN_INVALID_REVNUM, subpool)); + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + void *val; + svn_boolean_t is_copy = FALSE; + + /* This might be a replacement -- is the path already deleted? */ + val = svn_hash_gets(pb->deleted_entries, path); + + /* Detect add-with-history. */ + is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); + + /* Dump the node. */ + SVN_ERR(dump_node(eb, path, + svn_node_file, + val ? svn_node_action_replace : svn_node_action_add, + is_copy, + is_copy ? copyfrom_path : NULL, + is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, + pool)); + + if (val) + /* delete the path, it's now been dumped. */ + svn_hash_sets(pb->deleted_entries, path, NULL); + + *file_baton = NULL; /* muhahahaha */ + return SVN_NO_ERROR; +} + + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t ancestor_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + const char *cmp_path = NULL; + svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; + + /* If the parent directory has explicit comparison path and rev, + record the same for this one. */ + if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) + { + cmp_path = svn_relpath_join(pb->cmp_path, + svn_relpath_basename(path, pool), pool); + cmp_rev = pb->cmp_rev; + } + + SVN_ERR(dump_node(eb, path, + svn_node_file, svn_node_action_change, + FALSE, cmp_path, cmp_rev, pool)); + + *file_baton = NULL; /* muhahahaha again */ + return SVN_NO_ERROR; +} + + +static svn_error_t * +change_dir_prop(void *parent_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = parent_baton; + struct edit_baton *eb = db->edit_baton; + + /* This function is what distinguishes between a directory that is + opened to merely get somewhere, vs. one that is opened because it + *actually* changed by itself. */ + if (! db->written_out) + { + SVN_ERR(dump_node(eb, db->path, + svn_node_dir, svn_node_action_change, + FALSE, db->cmp_path, db->cmp_rev, pool)); + db->written_out = TRUE; + } + 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) +{ + struct edit_baton *eb = baton; + svn_error_t *err; + svn_fs_root_t *fs_root; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_rev - 1; + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); + + err = svn_fs_node_proplist(props, fs_root, path, result_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *props = apr_hash_make(result_pool); + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} + +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) +{ + struct edit_baton *eb = baton; + svn_fs_root_t *fs_root; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_rev - 1; + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); + + SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_stream_t *contents; + svn_stream_t *file_stream; + const char *tmp_filename; + svn_error_t *err; + svn_fs_root_t *fs_root; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->current_rev - 1; + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); + + err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); + + *filename = apr_pstrdup(result_pool, tmp_filename); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +get_dump_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_fs_t *fs, + svn_revnum_t to_rev, + const char *root_path, + svn_stream_t *stream, + svn_boolean_t *found_old_reference, + svn_boolean_t *found_old_mergeinfo, + svn_error_t *(*custom_close_directory)(void *dir_baton, + apr_pool_t *scratch_pool), + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_revnum_t oldest_dumped_rev, + svn_boolean_t use_deltas, + svn_boolean_t verify, + apr_pool_t *pool) +{ + /* Allocate an edit baton to be stored in every directory baton. + Set it up for the directory baton we create here, which is the + root baton. */ + struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); + svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool); + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(pool); + + /* Set up the edit baton. */ + eb->stream = stream; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + eb->oldest_dumped_rev = oldest_dumped_rev; + eb->bufsize = sizeof(eb->buffer); + eb->path = apr_pstrdup(pool, root_path); + SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); + eb->fs = fs; + eb->current_rev = to_rev; + eb->use_deltas = use_deltas; + eb->verify = verify; + eb->found_old_reference = found_old_reference; + eb->found_old_mergeinfo = found_old_mergeinfo; + + /* Set up the editor. */ + dump_editor->open_root = open_root; + dump_editor->delete_entry = delete_entry; + dump_editor->add_directory = add_directory; + dump_editor->open_directory = open_directory; + if (custom_close_directory) + dump_editor->close_directory = custom_close_directory; + else + dump_editor->close_directory = close_directory; + dump_editor->change_dir_prop = change_dir_prop; + dump_editor->add_file = add_file; + dump_editor->open_file = open_file; + + *edit_baton = eb; + *editor = dump_editor; + + shim_callbacks->fetch_kind_func = fetch_kind_func; + shim_callbacks->fetch_props_func = fetch_props_func; + shim_callbacks->fetch_base_func = fetch_base_func; + shim_callbacks->fetch_baton = eb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, pool, pool)); + + return SVN_NO_ERROR; +} + +/*----------------------------------------------------------------------*/ + +/** The main dumping routine, svn_repos_dump_fs. **/ + + +/* Helper for svn_repos_dump_fs. + + Write a revision record of REV in FS to writable STREAM, using POOL. + */ +static svn_error_t * +write_revision_record(svn_stream_t *stream, + svn_fs_t *fs, + svn_revnum_t rev, + apr_pool_t *pool) +{ + apr_size_t len; + apr_hash_t *props; + svn_stringbuf_t *encoded_prophash; + apr_time_t timetemp; + svn_string_t *datevalue; + svn_stream_t *propstream; + + /* Read the revision props even if we're aren't going to dump + them for verification purposes */ + SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); + + /* Run revision date properties through the time conversion to + canonicalize them. */ + /* ### Remove this when it is no longer needed for sure. */ + datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE); + if (datevalue) + { + SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); + datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), + pool); + svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); + } + + encoded_prophash = svn_stringbuf_create_ensure(0, pool); + propstream = svn_stream_from_stringbuf(encoded_prophash, pool); + SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); + SVN_ERR(svn_stream_close(propstream)); + + /* ### someday write a revision-content-checksum */ + + SVN_ERR(svn_stream_printf(stream, pool, + SVN_REPOS_DUMPFILE_REVISION_NUMBER + ": %ld\n", rev)); + SVN_ERR(svn_stream_printf(stream, pool, + SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH + ": %" APR_SIZE_T_FMT "\n", + encoded_prophash->len)); + + /* Write out a regular Content-length header for the benefit of + non-Subversion RFC-822 parsers. */ + SVN_ERR(svn_stream_printf(stream, pool, + SVN_REPOS_DUMPFILE_CONTENT_LENGTH + ": %" APR_SIZE_T_FMT "\n\n", + encoded_prophash->len)); + + len = encoded_prophash->len; + SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); + + len = 1; + return svn_stream_write(stream, "\n", &len); +} + + + +/* The main dumper. */ +svn_error_t * +svn_repos_dump_fs3(svn_repos_t *repos, + svn_stream_t *stream, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_boolean_t incremental, + svn_boolean_t use_deltas, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + const svn_delta_editor_t *dump_editor; + void *dump_edit_baton = NULL; + svn_revnum_t i; + svn_fs_t *fs = svn_repos_fs(repos); + apr_pool_t *subpool = svn_pool_create(pool); + svn_revnum_t youngest; + const char *uuid; + int version; + svn_boolean_t found_old_reference = FALSE; + svn_boolean_t found_old_mergeinfo = FALSE; + svn_repos_notify_t *notify; + + /* Determine the current youngest revision of the filesystem. */ + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + + /* Use default vals if necessary. */ + if (! SVN_IS_VALID_REVNUM(start_rev)) + start_rev = 0; + if (! SVN_IS_VALID_REVNUM(end_rev)) + end_rev = youngest; + if (! stream) + stream = svn_stream_empty(pool); + + /* Validate the revisions. */ + if (start_rev > end_rev) + return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("Start revision %ld" + " is greater than end revision %ld"), + start_rev, end_rev); + if (end_rev > youngest) + return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("End revision %ld is invalid " + "(youngest revision is %ld)"), + end_rev, youngest); + if ((start_rev == 0) && incremental) + incremental = FALSE; /* revision 0 looks the same regardless of + whether or not this is an incremental + dump, so just simplify things. */ + + /* Write out the UUID. */ + SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); + + /* If we're not using deltas, use the previous version, for + compatibility with svn 1.0.x. */ + version = SVN_REPOS_DUMPFILE_FORMAT_VERSION; + if (!use_deltas) + version--; + + /* Write out "general" metadata for the dumpfile. In this case, a + magic header followed by a dumpfile format version. */ + SVN_ERR(svn_stream_printf(stream, pool, + SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", + version)); + SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID + ": %s\n\n", uuid)); + + /* Create a notify object that we can reuse in the loop. */ + if (notify_func) + notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, + pool); + + /* Main loop: we're going to dump revision i. */ + for (i = start_rev; i <= end_rev; i++) + { + svn_revnum_t from_rev, to_rev; + svn_fs_root_t *to_root; + svn_boolean_t use_deltas_for_rev; + + svn_pool_clear(subpool); + + /* Check for cancellation. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Special-case the initial revision dump: it needs to contain + *all* nodes, because it's the foundation of all future + revisions in the dumpfile. */ + if ((i == start_rev) && (! incremental)) + { + /* Special-special-case a dump of revision 0. */ + if (i == 0) + { + /* Just write out the one revision 0 record and move on. + The parser might want to use its properties. */ + SVN_ERR(write_revision_record(stream, fs, 0, subpool)); + to_rev = 0; + goto loop_end; + } + + /* Compare START_REV to revision 0, so that everything + appears to be added. */ + from_rev = 0; + to_rev = i; + } + else + { + /* In the normal case, we want to compare consecutive revs. */ + from_rev = i - 1; + to_rev = i; + } + + /* Write the revision record. */ + SVN_ERR(write_revision_record(stream, fs, to_rev, subpool)); + + /* Fetch the editor which dumps nodes to a file. Regardless of + what we've been told, don't use deltas for the first rev of a + non-incremental dump. */ + use_deltas_for_rev = use_deltas && (incremental || i != start_rev); + SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev, + "", stream, &found_old_reference, + &found_old_mergeinfo, NULL, + notify_func, notify_baton, + start_rev, use_deltas_for_rev, FALSE, subpool)); + + /* Drive the editor in one way or another. */ + SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool)); + + /* If this is the first revision of a non-incremental dump, + we're in for a full tree dump. Otherwise, we want to simply + replay the revision. */ + if ((i == start_rev) && (! incremental)) + { + svn_fs_root_t *from_root; + SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool)); + SVN_ERR(svn_repos_dir_delta2(from_root, "", "", + to_root, "", + dump_editor, dump_edit_baton, + NULL, + NULL, + FALSE, /* don't send text-deltas */ + svn_depth_infinity, + FALSE, /* don't send entry props */ + FALSE, /* don't ignore ancestry */ + subpool)); + } + else + { + SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, + dump_editor, dump_edit_baton, + NULL, NULL, subpool)); + + /* While our editor close_edit implementation is a no-op, we still + do this for completeness. */ + SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool)); + } + + loop_end: + if (notify_func) + { + notify->revision = to_rev; + notify_func(notify_baton, notify, subpool); + } + } + + if (notify_func) + { + /* Did we issue any warnings about references to revisions older than + the oldest dumped revision? If so, then issue a final generic + warning, since the inline warnings already issued might easily be + missed. */ + + notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool); + notify_func(notify_baton, notify, subpool); + + if (found_old_reference) + { + notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); + + notify->warning = svn_repos_notify_warning_found_old_reference; + notify->warning_str = _("The range of revisions dumped " + "contained references to " + "copy sources outside that " + "range."); + notify_func(notify_baton, notify, subpool); + } + + /* Ditto if we issued any warnings about old revisions referenced + in dumped mergeinfo. */ + if (found_old_mergeinfo) + { + notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); + + notify->warning = svn_repos_notify_warning_found_old_mergeinfo; + notify->warning_str = _("The range of revisions dumped " + "contained mergeinfo " + "which reference revisions outside " + "that range."); + notify_func(notify_baton, notify, subpool); + } + } + + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + + +/*----------------------------------------------------------------------*/ + +/* verify, based on dump */ + + +/* Creating a new revision that changes /A/B/E/bravo means creating new + directory listings for /, /A, /A/B, and /A/B/E in the new revision, with + each entry not changed in the new revision a link back to the entry in a + previous revision. svn_repos_replay()ing a revision does not verify that + those links are correct. + + For paths actually changed in the revision we verify, we get directory + contents or file length twice: once in the dump editor, and once here. + We could create a new verify baton, store in it the changed paths, and + skip those here, but that means building an entire wrapper editor and + managing two levels of batons. The impact from checking these entries + twice should be minimal, while the code to avoid it is not. +*/ + +static svn_error_t * +verify_directory_entry(void *baton, const void *key, apr_ssize_t klen, + void *val, apr_pool_t *pool) +{ + struct dir_baton *db = baton; + svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; + char *path = svn_relpath_join(db->path, (const char *)key, pool); + apr_hash_t *dirents; + svn_filesize_t len; + + /* since we can't access the directory entries directly by their ID, + we need to navigate from the FS_ROOT to them (relatively expensive + because we may start at a never rev than the last change to node). */ + switch (dirent->kind) { + case svn_node_dir: + /* Getting this directory's contents is enough to ensure that our + link to it is correct. */ + SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool)); + break; + case svn_node_file: + /* Getting this file's size is enough to ensure that our link to it + is correct. */ + SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool)); + break; + default: + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Unexpected node kind %d for '%s'"), + dirent->kind, path); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + apr_hash_t *dirents; + SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, + db->path, pool)); + SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, + dir_baton, pool)); + return close_directory(dir_baton, pool); +} + +/* Baton type used for forwarding notifications from FS API to REPOS API. */ +struct verify_fs2_notify_func_baton_t +{ + /* notification function to call (must not be NULL) */ + svn_repos_notify_func_t notify_func; + + /* baton to use for it */ + void *notify_baton; + + /* type of notification to send (we will simply plug in the revision) */ + svn_repos_notify_t *notify; +}; + +/* Forward the notification to BATON. */ +static void +verify_fs2_notify_func(svn_revnum_t revision, + void *baton, + apr_pool_t *pool) +{ + struct verify_fs2_notify_func_baton_t *notify_baton = baton; + + notify_baton->notify->revision = revision; + notify_baton->notify_func(notify_baton->notify_baton, + notify_baton->notify, pool); +} + +svn_error_t * +svn_repos_verify_fs2(svn_repos_t *repos, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_fs_t *fs = svn_repos_fs(repos); + svn_revnum_t youngest; + svn_revnum_t rev; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_repos_notify_t *notify; + svn_fs_progress_notify_func_t verify_notify = NULL; + struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL; + + /* Determine the current youngest revision of the filesystem. */ + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + + /* Use default vals if necessary. */ + if (! SVN_IS_VALID_REVNUM(start_rev)) + start_rev = 0; + if (! SVN_IS_VALID_REVNUM(end_rev)) + end_rev = youngest; + + /* Validate the revisions. */ + if (start_rev > end_rev) + return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("Start revision %ld" + " is greater than end revision %ld"), + start_rev, end_rev); + if (end_rev > youngest) + return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("End revision %ld is invalid " + "(youngest revision is %ld)"), + end_rev, youngest); + + /* Create a notify object that we can reuse within the loop and a + forwarding structure for notifications from inside svn_fs_verify(). */ + if (notify_func) + { + notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, + pool); + + verify_notify = verify_fs2_notify_func; + verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); + verify_notify_baton->notify_func = notify_func; + verify_notify_baton->notify_baton = notify_baton; + verify_notify_baton->notify + = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); + } + + /* Verify global metadata and backend-specific data first. */ + SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), + start_rev, end_rev, + verify_notify, verify_notify_baton, + cancel_func, cancel_baton, pool)); + + for (rev = start_rev; rev <= end_rev; rev++) + { + const svn_delta_editor_t *dump_editor; + void *dump_edit_baton; + const svn_delta_editor_t *cancel_editor; + void *cancel_edit_baton; + svn_fs_root_t *to_root; + apr_hash_t *props; + + svn_pool_clear(iterpool); + + /* Get cancellable dump editor, but with our close_directory handler. */ + SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, + fs, rev, "", + svn_stream_empty(iterpool), + NULL, NULL, + verify_close_directory, + notify_func, notify_baton, + start_rev, + FALSE, TRUE, /* use_deltas, verify */ + iterpool)); + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + dump_editor, dump_edit_baton, + &cancel_editor, + &cancel_edit_baton, + iterpool)); + + SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); + SVN_ERR(svn_fs_verify_root(to_root, iterpool)); + + SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, + cancel_editor, cancel_edit_baton, + NULL, NULL, iterpool)); + /* While our editor close_edit implementation is a no-op, we still + do this for completeness. */ + SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool)); + + SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool)); + + if (notify_func) + { + notify->revision = rev; + notify_func(notify_baton, notify, iterpool); + } + } + + /* We're done. */ + if (notify_func) + { + notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool); + notify_func(notify_baton, notify, iterpool); + } + + /* Per-backend verification. */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} |