Modified: subversion/branches/addremove/subversion/svnrdump/dump_editor.c URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/svnrdump/dump_editor.c?rev=1878061&r1=1878060&r2=1878061&view=diff ============================================================================== --- subversion/branches/addremove/subversion/svnrdump/dump_editor.c (original) +++ subversion/branches/addremove/subversion/svnrdump/dump_editor.c Sat May 23 14:16:56 2020 @@ -22,951 +22,28 @@ * ==================================================================== */ -#include "svn_hash.h" -#include "svn_pools.h" -#include "svn_repos.h" -#include "svn_path.h" +#include "svn_types.h" #include "svn_props.h" -#include "svn_subst.h" -#include "svn_dirent_uri.h" +#include "svn_ra.h" +#include "svn_io.h" +#include "svn_delta.h" #include "private/svn_repos_private.h" -#include "private/svn_subr_private.h" -#include "private/svn_dep_compat.h" #include "private/svn_editor.h" #include "svnrdump.h" #include <assert.h> -#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) - - -/* A directory baton used by all directory-related callback functions - * in the dump editor. */ -struct dir_baton -{ - struct dump_edit_baton *eb; - - /* Pool for per-directory allocations */ - apr_pool_t *pool; - - /* the path to this directory */ - const char *repos_relpath; /* a relpath */ - - /* Copyfrom info for the node, if any. */ - const char *copyfrom_path; /* a relpath */ - svn_revnum_t copyfrom_rev; - - /* Headers accumulated so far for this directory */ - svn_repos__dumpfile_headers_t *headers; - - /* Properties which were modified during change_dir_prop. */ - apr_hash_t *props; - - /* Properties which were deleted during change_dir_prop. */ - apr_hash_t *deleted_props; - - /* Hash of paths that need to be deleted, though some -might- be - replaced. Maps const char * paths to this dir_baton. Note that - they're full paths, because that's what the editor driver gives - us, although they're all really within this directory. */ - apr_hash_t *deleted_entries; - - /* Flag to trigger dumping props. */ - svn_boolean_t dump_props; -}; - -/* A file baton used by all file-related callback functions in the dump - * editor */ -struct file_baton -{ - struct dump_edit_baton *eb; - - /* Pool for per-file allocations */ - apr_pool_t *pool; - - /* the path to this file */ - const char *repos_relpath; /* a relpath */ - - /* Properties which were modified during change_file_prop. */ - apr_hash_t *props; - - /* Properties which were deleted during change_file_prop. */ - apr_hash_t *deleted_props; - - /* The checksum of the file the delta is being applied to */ - const char *base_checksum; - - /* Copy state and source information (if any). */ - svn_boolean_t is_copy; - const char *copyfrom_path; - svn_revnum_t copyfrom_rev; - - /* The action associate with this node. */ - enum svn_node_action action; - - /* Flags to trigger dumping props and text. */ - svn_boolean_t dump_text; - svn_boolean_t dump_props; -}; /* The baton used by the dump editor. */ struct dump_edit_baton { - /* The output stream we write the dumpfile to */ - svn_stream_t *stream; - /* A backdoor ra session to fetch additional information during the edit. */ svn_ra_session_t *ra_session; - /* The repository relpath of the anchor of the editor when driven - via the RA update mechanism; NULL otherwise. (When the editor is - driven via the RA "replay" mechanism instead, the editor is - always anchored at the repository, we don't need to prepend an - anchor path to the dumped node paths, and open_root() doesn't - need to manufacture directory additions.) */ - const char *update_anchor_relpath; - - /* Pool for per-revision allocations */ - apr_pool_t *pool; - - /* Temporary file used for textdelta application along with its - absolute path; these two variables should be allocated in the - per-edit-session pool */ - const char *delta_abspath; - apr_file_t *delta_file; - /* The revision we're currently dumping. */ svn_revnum_t current_revision; - - /* The baton of the directory node whose block of - dump stream data has not been fully completed; NULL if there's no - such item. */ - struct dir_baton *pending_db; }; -/* Make a directory baton to represent the directory at PATH (relative - * to the EDIT_BATON). - * - * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this - * directory should be compared for changes. If the copyfrom - * information is valid, the directory will be compared against its - * copy source. - * - * PB is the directory baton of this directory's parent, or NULL if - * this is the top-level directory of the edit. - * - * Perform all allocations in POOL. */ -static struct dir_baton * -make_dir_baton(const char *path, - const char *copyfrom_path, - svn_revnum_t copyfrom_rev, - void *edit_baton, - struct dir_baton *pb, - apr_pool_t *pool) -{ - struct dump_edit_baton *eb = edit_baton; - struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); - const char *repos_relpath; - - /* Construct the full path of this node. */ - if (pb) - repos_relpath = svn_relpath_canonicalize(path, pool); - else - repos_relpath = ""; - - /* Strip leading slash from copyfrom_path so that the path is - canonical and svn_relpath_join can be used */ - if (copyfrom_path) - copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); - - new_db->eb = eb; - new_db->pool = pool; - new_db->repos_relpath = repos_relpath; - new_db->copyfrom_path = copyfrom_path - ? svn_relpath_canonicalize(copyfrom_path, pool) - : NULL; - new_db->copyfrom_rev = copyfrom_rev; - new_db->headers = NULL; - new_db->props = apr_hash_make(pool); - new_db->deleted_props = apr_hash_make(pool); - new_db->deleted_entries = apr_hash_make(pool); - - return new_db; -} - -/* Make a file baton to represent the directory at PATH (relative to - * PB->eb). PB is the directory baton of this directory's parent, or - * NULL if this is the top-level directory of the edit. Perform all - * allocations in POOL. */ -static struct file_baton * -make_file_baton(const char *path, - struct dir_baton *pb, - apr_pool_t *pool) -{ - struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); - - new_fb->eb = pb->eb; - new_fb->pool = pool; - new_fb->repos_relpath = svn_relpath_canonicalize(path, pool); - new_fb->props = apr_hash_make(pool); - new_fb->deleted_props = apr_hash_make(pool); - new_fb->is_copy = FALSE; - new_fb->copyfrom_path = NULL; - new_fb->copyfrom_rev = SVN_INVALID_REVNUM; - new_fb->action = svn_node_action_change; - - return new_fb; -} - -/* Append to HEADERS the required headers, and set *CONTENT to the property - * content section, to represent the property delta of PROPS/DELETED_PROPS. - */ -static svn_error_t * -get_props_content(svn_repos__dumpfile_headers_t *headers, - svn_stringbuf_t **content, - apr_hash_t *props, - apr_hash_t *deleted_props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_stream_t *content_stream; - apr_hash_t *normal_props; - - *content = svn_stringbuf_create_empty(result_pool); - - content_stream = svn_stream_from_stringbuf(*content, scratch_pool); - - SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool)); - SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props, - content_stream, "PROPS-END", - scratch_pool)); - SVN_ERR(svn_stream_close(content_stream)); - - /* Prop-delta: true */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); - - return SVN_NO_ERROR; -} - -/* A special case of dump_node(), for a delete record. - * - * The only thing special about this version is it only writes one blank - * line, not two, after the headers. Why? Historical precedent for the - * case where a delete record is used as part of a (delete + add-with-history) - * in implementing a replacement. - */ -static svn_error_t * -dump_node_delete(svn_stream_t *stream, - const char *node_relpath, - apr_pool_t *pool) -{ - svn_repos__dumpfile_headers_t *headers - = svn_repos__dumpfile_headers_create(pool); - - assert(svn_relpath_is_canonical(node_relpath)); - - /* Node-path: ... */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); - - /* Node-action: delete */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); - - SVN_ERR(svn_repos__dump_node_record(stream, headers, - NULL, FALSE, 0, /* props & text */ - FALSE /*content_length_always*/, pool)); - return SVN_NO_ERROR; -} - -/* Set *HEADERS_P to contain some headers for the node at PATH of type KIND. - * - * ACTION describes what is happening to the node (see enum - * svn_node_action). - * - * If the node was itself copied, IS_COPY is TRUE and the - * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV. - * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this - * node is part of a copied subtree. - * - * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a - * complete deletion record to the dump stream. - * - * If ACTION is svn_node_action_delete, then the node record will be - * complete. (The caller may want to write two blank lines after the - * header block.) - */ -static svn_error_t * -dump_node(svn_repos__dumpfile_headers_t **headers_p, - struct dump_edit_baton *eb, - const char *repos_relpath, - struct dir_baton *db, - struct file_baton *fb, - enum svn_node_action action, - svn_boolean_t is_copy, - const char *copyfrom_path, - svn_revnum_t copyfrom_rev, - apr_pool_t *pool) -{ - const char *node_relpath = repos_relpath; - svn_repos__dumpfile_headers_t *headers - = svn_repos__dumpfile_headers_create(pool); - - assert(svn_relpath_is_canonical(repos_relpath)); - assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path)); - assert(! (db && fb)); - - /* Add the edit root relpath prefix if necessary. */ - if (eb->update_anchor_relpath) - node_relpath = svn_relpath_join(eb->update_anchor_relpath, - node_relpath, pool); - - /* Node-path: ... */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); - - /* Node-kind: "file" | "dir" */ - if (fb) - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); - else if (db) - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); - - - /* Write the appropriate Node-action header */ - switch (action) - { - case svn_node_action_change: - /* We are here after a change_file_prop or change_dir_prop. They - set up whatever dump_props they needed to- nothing to - do here but print node action information. - - Node-action: change. */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); - break; - - case svn_node_action_delete: - /* Node-action: delete */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); - break; - - case svn_node_action_replace: - if (! is_copy) - { - /* Node-action: replace */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); - - /* Wait for a change_*_prop to be called before dumping - anything */ - if (fb) - fb->dump_props = TRUE; - else if (db) - db->dump_props = TRUE; - break; - } - else - { - /* More complex case: is_copy is true, and copyfrom_path/ - copyfrom_rev are present: delete the original, and then re-add - it */ - /* ### Why not write a 'replace' record? Don't know. */ - - /* ### Unusually, we end this 'delete' node record with only a single - blank line after the header block -- no extra blank line. */ - SVN_ERR(dump_node_delete(eb->stream, repos_relpath, pool)); - - /* The remaining action is a non-replacing add-with-history */ - /* action = svn_node_action_add; */ - } - /* FALL THROUGH to 'add' */ - - case svn_node_action_add: - /* Node-action: add */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); - - if (is_copy) - { - /* Node-copyfrom-rev / Node-copyfrom-path */ - svn_repos__dumpfile_header_pushf( - headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", copyfrom_rev); - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path); - } - else - { - /* fb->dump_props (for files) is handled in close_file() - which is called immediately. - - However, directories are not closed until all the work - inside them has been done; db->dump_props (for directories) - is handled (via dump_pending()) in all the functions that - can possibly be called after add_directory(): - - - add_directory() - - open_directory() - - delete_entry() - - close_directory() - - add_file() - - open_file() - - change_dir_prop() is a special case. */ - if (fb) - fb->dump_props = TRUE; - else if (db) - db->dump_props = TRUE; - } - - break; - } - - /* Return the headers so far. We don't necessarily have all the headers - yet -- there may be property-related and content length headers to - come, if this was not a 'delete' record. */ - *headers_p = headers; - return SVN_NO_ERROR; -} - -static svn_error_t * -dump_mkdir(struct dump_edit_baton *eb, - const char *repos_relpath, - apr_pool_t *pool) -{ - svn_stringbuf_t *prop_content; - svn_repos__dumpfile_headers_t *headers - = svn_repos__dumpfile_headers_create(pool); - - /* Node-path: ... */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath); - - /* Node-kind: dir */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); - - /* Node-action: add */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); - - /* Dump the (empty) property block. */ - SVN_ERR(get_props_content(headers, &prop_content, - apr_hash_make(pool), apr_hash_make(pool), - pool, pool)); - SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content, - FALSE, 0, FALSE /*content_length_always*/, - pool)); - - /* Newlines to tie it all off. */ - SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); - - return SVN_NO_ERROR; -} - -/* Dump pending headers and properties for the directory EB->pending_db (if - * not null), to allow starting the dump of a child node */ -static svn_error_t * -dump_pending_dir(struct dump_edit_baton *eb, - apr_pool_t *scratch_pool) -{ - struct dir_baton *db = eb->pending_db; - svn_stringbuf_t *prop_content = NULL; - - if (! db) - return SVN_NO_ERROR; - - /* Some pending properties to dump? */ - if (db->dump_props) - { - SVN_ERR(get_props_content(db->headers, &prop_content, - db->props, db->deleted_props, - scratch_pool, scratch_pool)); - } - SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content, - FALSE, 0, FALSE /*content_length_always*/, - scratch_pool)); - - /* No text is going to be dumped. Write a couple of newlines and - wait for the next node/ revision. */ - SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); - - if (db->dump_props) - { - /* Cleanup so that data is never dumped twice. */ - apr_hash_clear(db->props); - apr_hash_clear(db->deleted_props); - db->dump_props = FALSE; - } - - /* Anything that was pending is pending no longer. */ - eb->pending_db = NULL; - - return SVN_NO_ERROR; -} - - - -/*** Editor Function Implementations ***/ - -static svn_error_t * -open_root(void *edit_baton, - svn_revnum_t base_revision, - apr_pool_t *pool, - void **root_baton) -{ - struct dump_edit_baton *eb = edit_baton; - struct dir_baton *new_db = NULL; - - /* Clear the per-revision pool after each revision */ - svn_pool_clear(eb->pool); - - if (eb->update_anchor_relpath) - { - int i; - const char *parent_path = eb->update_anchor_relpath; - apr_array_header_t *dirs_to_add = - apr_array_make(pool, 4, sizeof(const char *)); - apr_pool_t *iterpool = svn_pool_create(pool); - - while (! svn_path_is_empty(parent_path)) - { - APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path; - parent_path = svn_relpath_dirname(parent_path, pool); - } - - for (i = dirs_to_add->nelts; i; --i) - { - const char *dir_to_add = - APR_ARRAY_IDX(dirs_to_add, i - 1, const char *); - - svn_pool_clear(iterpool); - - /* For parents of the source directory, we just manufacture - the adds ourselves. */ - if (i > 1) - { - SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool)); - } - else - { - /* ... but for the source directory itself, we'll defer - to letting the typical plumbing handle this task. */ - new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, - edit_baton, NULL, pool); - SVN_ERR(dump_node(&new_db->headers, - eb, new_db->repos_relpath, new_db, - NULL, svn_node_action_add, FALSE, - NULL, SVN_INVALID_REVNUM, pool)); - - /* Remember that we've started but not yet finished - handling this directory. */ - eb->pending_db = new_db; - } - } - svn_pool_destroy(iterpool); - } - - if (! new_db) - { - new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, - edit_baton, NULL, pool); - } - - *root_baton = new_db; - 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; - - SVN_ERR(dump_pending_dir(pb->eb, pool)); - - /* We don't dump this deletion immediate. Rather, we add this path - to the deleted_entries of the parent directory baton. That way, - we can tell (later) an addition from a replacement. All the real - deletions get handled in close_directory(). */ - svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->pool, path), 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; - void *was_deleted; - struct dir_baton *new_db; - svn_boolean_t is_copy; - - SVN_ERR(dump_pending_dir(pb->eb, pool)); - - new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, - pb, pb->pool); - - /* This might be a replacement -- is the path already deleted? */ - was_deleted = 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(&new_db->headers, - pb->eb, new_db->repos_relpath, new_db, NULL, - was_deleted ? svn_node_action_replace : svn_node_action_add, - is_copy, - is_copy ? new_db->copyfrom_path : NULL, - is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, - pool)); - - if (was_deleted) - /* Delete the path, it's now been dumped */ - svn_hash_sets(pb->deleted_entries, path, NULL); - - /* Remember that we've started, but not yet finished handling this - directory. */ - pb->eb->pending_db = new_db; - - *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 dir_baton *new_db; - const char *copyfrom_path = NULL; - svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; - - SVN_ERR(dump_pending_dir(pb->eb, pool)); - - /* If the parent directory has explicit comparison path and rev, - record the same for this one. */ - if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) - { - copyfrom_path = svn_relpath_join(pb->copyfrom_path, - svn_relpath_basename(path, NULL), - pb->pool); - copyfrom_rev = pb->copyfrom_rev; - } - - new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, - pb->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; - apr_hash_index_t *hi; - svn_boolean_t this_pending; - - /* Remember if this directory is the one currently pending. */ - this_pending = (db->eb->pending_db == db); - - SVN_ERR(dump_pending_dir(db->eb, pool)); - - /* If this directory was pending, then dump_pending() should have - taken care of all the props and such. Of course, the only way - that would be the case is if this directory was added/replaced. - - Otherwise, if stuff for this directory has already been written - out (at some point in the past, prior to our handling other - nodes), we might need to generate a second "change" record just - to carry the information we've since learned about the - directory. */ - if ((! this_pending) && (db->dump_props)) - { - SVN_ERR(dump_node(&db->headers, - db->eb, db->repos_relpath, db, NULL, - svn_node_action_change, FALSE, - NULL, SVN_INVALID_REVNUM, pool)); - db->eb->pending_db = db; - SVN_ERR(dump_pending_dir(db->eb, pool)); - } - - /* Dump the deleted directory entries */ - for (hi = apr_hash_first(pool, db->deleted_entries); hi; - hi = apr_hash_next(hi)) - { - const char *path = apr_hash_this_key(hi); - - SVN_ERR(dump_node_delete(db->eb->stream, path, pool)); - /* This deletion record is complete -- write an extra newline */ - SVN_ERR(svn_stream_puts(db->eb->stream, "\n")); - } - - /* ### should be unnecessary */ - apr_hash_clear(db->deleted_entries); - - 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 file_baton *fb; - void *was_deleted; - - SVN_ERR(dump_pending_dir(pb->eb, pool)); - - /* Make the file baton. */ - fb = make_file_baton(path, pb, pool); - - /* This might be a replacement -- is the path already deleted? */ - was_deleted = svn_hash_gets(pb->deleted_entries, path); - - /* Detect add-with-history. */ - if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev)) - { - fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool); - fb->copyfrom_rev = copyfrom_rev; - fb->is_copy = TRUE; - } - fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add; - - /* Delete the path, it's now been dumped. */ - if (was_deleted) - svn_hash_sets(pb->deleted_entries, path, NULL); - - *file_baton = fb; - 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 file_baton *fb; - - SVN_ERR(dump_pending_dir(pb->eb, pool)); - - /* Make the file baton. */ - fb = make_file_baton(path, pb, pool); - - /* If the parent directory has explicit copyfrom path and rev, - record the same for this one. */ - if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) - { - fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path, - svn_relpath_basename(path, NULL), - pb->pool); - fb->copyfrom_rev = pb->copyfrom_rev; - } - - *file_baton = fb; - 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; - svn_boolean_t this_pending; - - /* This directory is not pending, but something else is, so handle - the "something else". */ - this_pending = (db->eb->pending_db == db); - if (! this_pending) - SVN_ERR(dump_pending_dir(db->eb, pool)); - - if (svn_property_kind2(name) != svn_prop_regular_kind) - return SVN_NO_ERROR; - - if (value) - svn_hash_sets(db->props, - apr_pstrdup(db->pool, name), - svn_string_dup(value, db->pool)); - else - svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), ""); - - /* Make sure we eventually output the props */ - db->dump_props = TRUE; - - return SVN_NO_ERROR; -} - -static svn_error_t * -change_file_prop(void *file_baton, - const char *name, - const svn_string_t *value, - apr_pool_t *pool) -{ - struct file_baton *fb = file_baton; - - if (svn_property_kind2(name) != svn_prop_regular_kind) - return SVN_NO_ERROR; - - if (value) - svn_hash_sets(fb->props, - apr_pstrdup(fb->pool, name), - svn_string_dup(value, fb->pool)); - else - svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), ""); - - /* Dump the property headers and wait; close_file might need - to write text headers too depending on whether - apply_textdelta is called */ - fb->dump_props = TRUE; - - return SVN_NO_ERROR; -} - -static svn_error_t * -apply_textdelta(void *file_baton, const char *base_checksum, - apr_pool_t *pool, - svn_txdelta_window_handler_t *handler, - void **handler_baton) -{ - struct file_baton *fb = file_baton; - struct dump_edit_baton *eb = fb->eb; - svn_stream_t *delta_filestream; - - /* Use a temporary file to measure the Text-content-length */ - delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); - - /* Prepare to write the delta to the delta_filestream */ - svn_txdelta_to_svndiff3(handler, handler_baton, - delta_filestream, 0, - SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); - - /* Record that there's text to be dumped, and its base checksum. */ - fb->dump_text = TRUE; - fb->base_checksum = apr_pstrdup(fb->pool, base_checksum); - - return SVN_NO_ERROR; -} - -static svn_error_t * -close_file(void *file_baton, - const char *text_checksum, - apr_pool_t *pool) -{ - struct file_baton *fb = file_baton; - struct dump_edit_baton *eb = fb->eb; - svn_filesize_t text_content_length = 0; - svn_stringbuf_t *propstring = NULL; - svn_repos__dumpfile_headers_t *headers; - - SVN_ERR(dump_pending_dir(eb, pool)); - - /* Start dumping this node, by collecting some basic headers for it. */ - SVN_ERR(dump_node(&headers, eb, fb->repos_relpath, NULL, fb, - fb->action, fb->is_copy, fb->copyfrom_path, - fb->copyfrom_rev, pool)); - - /* Some pending properties to dump? We'll dump just the headers for - now, then dump the actual propchange content only after dumping - the text headers too (if present). */ - if (fb->dump_props) - { - SVN_ERR(get_props_content(headers, &propstring, - fb->props, fb->deleted_props, - pool, pool)); - } - - /* Dump the text headers */ - if (fb->dump_text) - { - /* Text-delta: true */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); - - SVN_ERR(svn_io_file_size_get(&text_content_length, eb->delta_file, - pool)); - - if (fb->base_checksum) - /* Text-delta-base-md5: */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, fb->base_checksum); - - /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */ - svn_repos__dumpfile_header_push( - headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum); - } - - /* Dump the headers and props now */ - SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, propstring, - fb->dump_text, text_content_length, - FALSE /*content_length_always*/, - pool)); - - if (fb->dump_props) - { - /* Cleanup */ - fb->dump_props = FALSE; - apr_hash_clear(fb->props); - apr_hash_clear(fb->deleted_props); - } - - /* Dump the text */ - if (fb->dump_text) - { - /* Seek to the beginning of the delta file, map it to a stream, - and copy the stream to eb->stream. Then close the stream and - truncate the file so we can reuse it for the next textdelta - application. Note that the file isn't created, opened or - closed here */ - svn_stream_t *delta_filestream; - apr_off_t offset = 0; - - SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool)); - delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); - SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool)); - - /* Cleanup */ - SVN_ERR(svn_stream_close(delta_filestream)); - SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool)); - } - - /* Write a couple of blank lines for matching output with `svnadmin - dump` */ - SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); - - return SVN_NO_ERROR; -} - -static svn_error_t * -close_edit(void *edit_baton, apr_pool_t *pool) -{ - return SVN_NO_ERROR; -} - static svn_error_t * fetch_base_func(const char **filename, void *baton, @@ -1085,47 +162,21 @@ svn_rdump__get_dump_editor(const svn_del apr_pool_t *pool) { struct dump_edit_baton *eb; - svn_delta_editor_t *de; svn_delta_shim_callbacks_t *shim_callbacks = svn_delta_shim_callbacks_default(pool); eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); - eb->stream = stream; eb->ra_session = ra_session; - eb->update_anchor_relpath = update_anchor_relpath; eb->current_revision = revision; - eb->pending_db = NULL; - - /* Create a special per-revision pool */ - eb->pool = svn_pool_create(pool); - /* Open a unique temporary file for all textdelta applications in - this edit session. The file is automatically closed and cleaned - up when the edit session is done. */ - SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath), - NULL, svn_io_file_del_on_close, pool, pool)); - - de = svn_delta_default_editor(pool); - de->open_root = open_root; - de->delete_entry = delete_entry; - de->add_directory = add_directory; - de->open_directory = open_directory; - de->close_directory = close_directory; - de->change_dir_prop = change_dir_prop; - de->change_file_prop = change_file_prop; - de->apply_textdelta = apply_textdelta; - de->add_file = add_file; - de->open_file = open_file; - de->close_file = close_file; - de->close_edit = close_edit; - - /* Set the edit_baton and editor. */ - *edit_baton = eb; - *editor = de; + SVN_ERR(svn_repos__get_dump_editor(editor, edit_baton, + stream, update_anchor_relpath, pool)); /* Wrap this editor in a cancellation editor. */ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, - de, eb, editor, edit_baton, pool)); + *editor, *edit_baton, + editor, edit_baton, + pool)); shim_callbacks->fetch_base_func = fetch_base_func; shim_callbacks->fetch_props_func = fetch_props_func;
Modified: subversion/branches/addremove/subversion/svnrdump/load_editor.c URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/svnrdump/load_editor.c?rev=1878061&r1=1878060&r2=1878061&view=diff ============================================================================== --- subversion/branches/addremove/subversion/svnrdump/load_editor.c (original) +++ subversion/branches/addremove/subversion/svnrdump/load_editor.c Sat May 23 14:16:56 2020 @@ -45,15 +45,58 @@ /** + * A vtable that is driven by get_dumpstream_loader(). + */ +typedef struct loader_fns_t +{ + /* Callback on starting a revision, to obtain an editor for the revision. + * + * This callback returns in *EDITOR and *EDIT_BATON an editor through which + * its caller will drive the changes for revision REVISION. + * + * This callback should call the editor's SET_TARGET_REVISION method if + * required. + * + * REV_PROPS holds the revision properties. + * + * POOL (and REV_PROPS) will persist until after the REVFINISH callback + * returns. + * + * Modelled on svn_ra_replay_revstart_callback_t. + */ + svn_error_t *(*revstart)( + svn_revnum_t revision, + void *baton, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_hash_t *rev_props, + apr_pool_t *pool); + + /* Fetch the node props for ORIG_PATH @ ORIG_REV, a node of kind KIND. + * + * This callback is required if a non-deltas-format dump is parsed; + * otherwise it will not be called and may be null. If required and not + * provided, the loader will return SVN_ERR_UNSUPPORTED_FEATURE. + * + * Only regular props are needed. Any special kinds of property such as + * entry-props and DAV/WC-props will be ignored. + */ + svn_error_t *(*fetch_props)( + apr_hash_t **props, + void *baton, + const char *orig_path, + svn_revnum_t orig_rev, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +} loader_fns_t; + +/** * General baton used by the parser functions. */ struct parse_baton { - /* Commit editor and baton used to transfer loaded revisions to - the target repository. */ - const svn_delta_editor_t *commit_editor; - void *commit_edit_baton; - /* RA session(s) for committing to the target repository. */ svn_ra_session_t *session; svn_ra_session_t *aux_session; @@ -72,7 +115,7 @@ struct parse_baton /* A mapping of svn_revnum_t * dump stream revisions to their corresponding svn_revnum_t * target repository revisions. */ - /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903 + /* ### See https://issues.apache.org/jira/browse/SVN-3903 ### for discussion about improving the memory costs of this mapping. */ apr_hash_t *rev_map; @@ -87,6 +130,9 @@ struct parse_baton /* An hash containing specific revision properties to skip while loading. */ apr_hash_t *skip_revprops; + + const loader_fns_t *callbacks; + void *cb_baton; }; /** @@ -144,8 +190,14 @@ struct revision_baton { svn_revnum_t rev; apr_hash_t *revprop_table; + svn_revnum_t head_rev_before_commit; apr_int32_t rev_offset; + /* Commit editor and baton used to transfer loaded revisions to + the target repository. */ + const svn_delta_editor_t *commit_editor; + void *commit_edit_baton; + const svn_string_t *datestamp; const svn_string_t *author; @@ -183,204 +235,83 @@ get_revision_mapping(apr_hash_t *rev_map static svn_error_t * -commit_callback(const svn_commit_info_t *commit_info, - void *baton, - apr_pool_t *pool) +magic_header_record(int version, + void *parse_baton, + apr_pool_t *pool) { - struct revision_baton *rb = baton; - struct parse_baton *pb = rb->pb; - - /* ### Don't print directly; generate a notification. */ - if (! pb->quiet) - SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n", - commit_info->revision)); - - /* Add the mapping of the dumpstream revision to the committed revision. */ - set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision); - - /* If the incoming dump stream has non-contiguous revisions (e.g. from - using svndumpfilter --drop-empty-revs without --renumber-revs) then - we must account for the missing gaps in PB->REV_MAP. Otherwise we - might not be able to map all mergeinfo source revisions to the correct - revisions in the target repos. */ - if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) - && (rb->rev != pb->last_rev_mapped + 1)) - { - svn_revnum_t i; - - for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) - { - set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); - } - } - - /* Update our "last revision mapped". */ - pb->last_rev_mapped = rb->rev; - return SVN_NO_ERROR; } -/* Implements `svn_ra__lock_retry_func_t'. */ static svn_error_t * -lock_retry_func(void *baton, - const svn_string_t *reposlocktoken, - apr_pool_t *pool) -{ - return svn_cmdline_printf(pool, - _("Failed to get lock on destination " - "repos, currently held by '%s'\n"), - reposlocktoken->data); -} - - -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) +uuid_record(const char *uuid, + void *parse_baton, + apr_pool_t *pool) { - struct revision_baton *rb = baton; - svn_stream_t *fstream; - svn_error_t *err; - - if (! SVN_IS_VALID_REVNUM(base_revision)) - base_revision = rb->rev - 1; - - SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, - svn_io_file_del_on_pool_cleanup, - result_pool, scratch_pool)); - - err = svn_ra_get_file(rb->pb->aux_session, path, base_revision, - fstream, NULL, NULL, scratch_pool); - if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - SVN_ERR(svn_stream_close(fstream)); - - *filename = NULL; - return SVN_NO_ERROR; - } - else if (err) - return svn_error_trace(err); - - SVN_ERR(svn_stream_close(fstream)); - 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) +/* Push information about another directory onto the linked list RB->db. + * + * CHILD_BATON is the baton returned by the commit editor. RELPATH is the + * repository-relative path of this directory. IS_ADDED is true iff this + * directory is being added (with or without history). If added with + * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else + * are NULL/SVN_INVALID_REVNUM. + */ +static void +push_directory(struct revision_baton *rb, + void *child_baton, + const char *relpath, + svn_boolean_t is_added, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev) { - struct revision_baton *rb = baton; - svn_node_kind_t node_kind; - - if (! SVN_IS_VALID_REVNUM(base_revision)) - base_revision = rb->rev - 1; + struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db)); - SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, - &node_kind, scratch_pool)); + SVN_ERR_ASSERT_NO_RETURN( + is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM)); - if (node_kind == svn_node_file) - { - SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision, - NULL, NULL, props, result_pool)); - } - else if (node_kind == svn_node_dir) + /* If this node is an existing (not newly added) child of a copied node, + calculate where it was copied from. */ + if (!is_added + && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) { - apr_array_header_t *tmp_props; + const char *name = svn_relpath_basename(relpath, NULL); - SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path, - base_revision, 0 /* Dirent fields */, - result_pool)); - tmp_props = svn_prop_hash_to_array(*props, result_pool); - SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, - result_pool)); - *props = svn_prop_array_to_hash(tmp_props, result_pool); - } - else - { - *props = apr_hash_make(result_pool); + copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name, + rb->pool); + copyfrom_rev = rb->db->copyfrom_rev; } - 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 revision_baton *rb = baton; - - if (! SVN_IS_VALID_REVNUM(base_revision)) - base_revision = rb->rev - 1; - - SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, - kind, scratch_pool)); - - return SVN_NO_ERROR; -} - -static svn_delta_shim_callbacks_t * -get_shim_callbacks(struct revision_baton *rb, - apr_pool_t *pool) -{ - svn_delta_shim_callbacks_t *callbacks = - svn_delta_shim_callbacks_default(pool); - - callbacks->fetch_props_func = fetch_props_func; - callbacks->fetch_kind_func = fetch_kind_func; - callbacks->fetch_base_func = fetch_base_func; - callbacks->fetch_baton = rb; - - return callbacks; + child_db->baton = child_baton; + child_db->relpath = relpath; + child_db->copyfrom_path = copyfrom_path; + child_db->copyfrom_rev = copyfrom_rev; + child_db->parent = rb->db; + rb->db = child_db; } -/* Acquire a lock (of sorts) on the repository associated with the - * given RA SESSION. This lock is just a revprop change attempt in a - * time-delay loop. This function is duplicated by svnsync in - * svnsync/svnsync.c - * - * ### TODO: Make this function more generic and - * expose it through a header for use by other Subversion - * applications to avoid duplication. +/* Called to obtain an editor for the revision described by RB. */ static svn_error_t * -get_lock(const svn_string_t **lock_string_p, - svn_ra_session_t *session, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *pool) +revision_start_edit(struct revision_baton *rb, + apr_pool_t *scratch_pool) { - svn_boolean_t be_atomic; - - SVN_ERR(svn_ra_has_capability(session, &be_atomic, - SVN_RA_CAPABILITY_ATOMIC_REVPROPS, - pool)); - if (! be_atomic) - { - /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */ - svn_error_t *err = - svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, - _("Target server does not support atomic revision " - "property edits; consider upgrading it to 1.7.")); - svn_handle_warning2(stderr, err, "svnrdump: "); - svn_error_clear(err); - } + struct parse_baton *pb = rb->pb; + void *child_baton; - return svn_ra__get_operational_lock(lock_string_p, NULL, session, - SVNRDUMP_PROP_LOCK, FALSE, - 10 /* retries */, lock_retry_func, NULL, - cancel_func, cancel_baton, pool); + SVN_ERR(pb->callbacks->revstart(rb->rev, + pb->cb_baton, + &rb->commit_editor, &rb->commit_edit_baton, + rb->revprop_table, + rb->pool)); + SVN_ERR(rb->commit_editor->open_root(rb->commit_edit_baton, + rb->head_rev_before_commit, + rb->pool, &child_baton)); + /* child_baton corresponds to the root directory baton here */ + push_directory(rb, child_baton, "", TRUE /*is_added*/, + NULL, SVN_INVALID_REVNUM); + return SVN_NO_ERROR; } static svn_error_t * @@ -389,13 +320,11 @@ new_revision_record(void **revision_bato void *parse_baton, apr_pool_t *pool) { + struct parse_baton *pb = parse_baton; struct revision_baton *rb; - struct parse_baton *pb; const char *rev_str; - svn_revnum_t head_rev; rb = apr_pcalloc(pool, sizeof(*rb)); - pb = parse_baton; rb->pool = svn_pool_create(pool); rb->pb = pb; rb->db = NULL; @@ -404,13 +333,14 @@ new_revision_record(void **revision_bato if (rev_str) rb->rev = SVN_STR_TO_REV(rev_str); - SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool)); + SVN_ERR(svn_ra_get_latest_revnum(pb->session, &rb->head_rev_before_commit, + pool)); /* FIXME: This is a lame fallback loading multiple segments of dump in several separate operations. It is highly susceptible to race conditions. Calculate the revision 'offset' for finding copyfrom sources. It might be positive or negative. */ - rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1)); + rb->rev_offset = (apr_int32_t) ((rb->rev) - (rb->head_rev_before_commit + 1)); /* Stash the oldest (non-zero) dumpstream revision seen. */ if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))) @@ -418,8 +348,8 @@ new_revision_record(void **revision_bato /* Set the commit_editor/ commit_edit_baton to NULL and wait for them to be created in new_node_record */ - rb->pb->commit_editor = NULL; - rb->pb->commit_edit_baton = NULL; + rb->commit_editor = NULL; + rb->commit_edit_baton = NULL; rb->revprop_table = apr_hash_make(rb->pool); *revision_baton = rb; @@ -427,73 +357,14 @@ new_revision_record(void **revision_bato } static svn_error_t * -magic_header_record(int version, - void *parse_baton, - apr_pool_t *pool) -{ - return SVN_NO_ERROR; -} - -static svn_error_t * -uuid_record(const char *uuid, - void *parse_baton, - apr_pool_t *pool) -{ - return SVN_NO_ERROR; -} - -/* Push information about another directory onto the linked list RB->db. - * - * CHILD_BATON is the baton returned by the commit editor. RELPATH is the - * repository-relative path of this directory. IS_ADDED is true iff this - * directory is being added (with or without history). If added with - * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else - * are NULL/SVN_INVALID_REVNUM. - */ -static void -push_directory(struct revision_baton *rb, - void *child_baton, - const char *relpath, - svn_boolean_t is_added, - const char *copyfrom_path, - svn_revnum_t copyfrom_rev) -{ - struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db)); - - SVN_ERR_ASSERT_NO_RETURN( - is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM)); - - /* If this node is an existing (not newly added) child of a copied node, - calculate where it was copied from. */ - if (!is_added - && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) - { - const char *name = svn_relpath_basename(relpath, NULL); - - copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name, - rb->pool); - copyfrom_rev = rb->db->copyfrom_rev; - } - - child_db->baton = child_baton; - child_db->relpath = relpath; - child_db->copyfrom_path = copyfrom_path; - child_db->copyfrom_rev = copyfrom_rev; - child_db->parent = rb->db; - rb->db = child_db; -} - -static svn_error_t * new_node_record(void **node_baton, apr_hash_t *headers, void *revision_baton, apr_pool_t *pool) { struct revision_baton *rb = revision_baton; - const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor; - void *commit_edit_baton = rb->pb->commit_edit_baton; + const struct svn_delta_editor_t *commit_editor = rb->commit_editor; struct node_baton *nb; - svn_revnum_t head_rev_before_commit = rb->rev - rb->rev_offset - 1; apr_hash_index_t *hi; void *child_baton; const char *nb_dirname; @@ -508,7 +379,6 @@ new_node_record(void **node_baton, /* If the creation of commit_editor is pending, create it now and open_root on it; also create a top-level directory baton. */ - if (!commit_editor) { /* The revprop_table should have been filled in with important @@ -521,23 +391,8 @@ new_node_record(void **node_baton, svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL); svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL); - SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session, - get_shim_callbacks(rb, rb->pool))); - SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, - &commit_edit_baton, rb->revprop_table, - commit_callback, revision_baton, - NULL, FALSE, rb->pool)); - - rb->pb->commit_editor = commit_editor; - rb->pb->commit_edit_baton = commit_edit_baton; - - SVN_ERR(commit_editor->open_root(commit_edit_baton, - head_rev_before_commit, - rb->pool, &child_baton)); - - /* child_baton corresponds to the root directory baton here */ - push_directory(rb, child_baton, "", TRUE /*is_added*/, - NULL, SVN_INVALID_REVNUM); + SVN_ERR(revision_start_edit(rb, pool)); + commit_editor = rb->commit_editor; } for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi)) @@ -612,7 +467,7 @@ new_node_record(void **node_baton, rb->pool); SVN_ERR(commit_editor->open_directory(relpath_compose, rb->db->baton, - head_rev_before_commit, + rb->head_rev_before_commit, rb->pool, &child_baton)); push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/, NULL, SVN_INVALID_REVNUM); @@ -655,7 +510,7 @@ new_node_record(void **node_baton, case svn_node_action_delete: case svn_node_action_replace: SVN_ERR(commit_editor->delete_entry(nb->path, - head_rev_before_commit, + rb->head_rev_before_commit, rb->db->baton, rb->pool)); if (nb->action == svn_node_action_delete) break; @@ -693,7 +548,7 @@ new_node_record(void **node_baton, break; default: SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton, - head_rev_before_commit, + rb->head_rev_before_commit, rb->pool, &child_baton)); push_directory(rb, child_baton, nb->path, FALSE /*is_added*/, NULL, SVN_INVALID_REVNUM); @@ -721,9 +576,10 @@ set_revision_property(void *baton, { if (! svn_hash_gets(rb->pb->skip_revprops, name)) svn_hash_sets(rb->revprop_table, - apr_pstrdup(rb->pool, name), value); + apr_pstrdup(rb->pool, name), + svn_string_dup(value, rb->pool)); } - else if (rb->rev_offset == -1 + else if (rb->head_rev_before_commit == 0 && ! svn_hash_gets(rb->pb->skip_revprops, name)) { /* Special case: set revision 0 properties directly (which is @@ -736,9 +592,9 @@ set_revision_property(void *baton, /* Remember any datestamp/ author that passes through (see comment in close_revision). */ if (!strcmp(name, SVN_PROP_REVISION_DATE)) - rb->datestamp = value; + rb->datestamp = svn_string_dup(value, rb->pool); if (!strcmp(name, SVN_PROP_REVISION_AUTHOR)) - rb->author = value; + rb->author = svn_string_dup(value, rb->pool); return SVN_NO_ERROR; } @@ -781,7 +637,7 @@ set_node_property(void *baton, prop = apr_palloc(nb->rb->pool, sizeof (*prop)); prop->name = apr_pstrdup(pool, name); - prop->value = value; + prop->value = svn_string_dup(value, pool); svn_hash_sets(nb->prop_changes, prop->name, prop); return SVN_NO_ERROR; @@ -856,16 +712,14 @@ remove_node_props(void *baton) /* Add-without-history; no "old" properties to worry about. */ return SVN_NO_ERROR; - if (nb->kind == svn_node_file) - { - SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, - orig_path, orig_rev, NULL, NULL, &props, pool)); - } - else /* nb->kind == svn_node_dir */ - { - SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props, - orig_path, orig_rev, 0, pool)); - } + if (! rb->pb->callbacks->fetch_props) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("This dumpstream reader requires a delta " + "format dumpstream")); + SVN_ERR(rb->pb->callbacks->fetch_props(&props, + rb->pb->cb_baton, + orig_path, orig_rev, nb->kind, + pool, pool)); for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { @@ -884,7 +738,7 @@ set_fulltext(svn_stream_t **stream, void *node_baton) { struct node_baton *nb = node_baton; - const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; + const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor; svn_txdelta_window_handler_t handler; void *handler_baton; apr_pool_t *pool = nb->rb->pool; @@ -902,7 +756,7 @@ apply_textdelta(svn_txdelta_window_handl void *node_baton) { struct node_baton *nb = node_baton; - const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; + const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor; apr_pool_t *pool = nb->rb->pool; SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, @@ -915,7 +769,7 @@ static svn_error_t * close_node(void *baton) { struct node_baton *nb = baton; - const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; + const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor; apr_pool_t *pool = nb->rb->pool; apr_hash_index_t *hi; @@ -956,8 +810,8 @@ static svn_error_t * close_revision(void *baton) { struct revision_baton *rb = baton; - const svn_delta_editor_t *commit_editor = rb->pb->commit_editor; - void *commit_edit_baton = rb->pb->commit_edit_baton; + const svn_delta_editor_t *commit_editor = rb->commit_editor; + void *commit_edit_baton = rb->commit_edit_baton; svn_revnum_t committed_rev = SVN_INVALID_REVNUM; /* Fake revision 0 */ @@ -982,20 +836,12 @@ close_revision(void *baton) } else { - svn_revnum_t head_rev_before_commit = rb->rev - rb->rev_offset - 1; - void *child_baton; - /* Legitimate revision with no node information */ - SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, - &commit_edit_baton, rb->revprop_table, - commit_callback, baton, - NULL, FALSE, rb->pool)); - - SVN_ERR(commit_editor->open_root(commit_edit_baton, - head_rev_before_commit, - rb->pool, &child_baton)); + SVN_ERR(revision_start_edit(rb, rb->pool)); + commit_editor = rb->commit_editor; + commit_edit_baton = rb->commit_edit_baton; - SVN_ERR(commit_editor->close_directory(child_baton, rb->pool)); + SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); } @@ -1007,7 +853,7 @@ close_revision(void *baton) { committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev); } - else if (rb->rev_offset == -1) + else if (rb->head_rev_before_commit == 0) { committed_rev = 0; } @@ -1016,16 +862,12 @@ close_revision(void *baton) { if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE)) { - SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE, - rb->datestamp, rb->pool)); SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, SVN_PROP_REVISION_DATE, NULL, rb->datestamp, rb->pool)); } if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR)) { - SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR, - rb->author, rb->pool)); SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, SVN_PROP_REVISION_AUTHOR, NULL, rb->author, rb->pool)); @@ -1037,27 +879,238 @@ close_revision(void *baton) return SVN_NO_ERROR; } -svn_error_t * -svn_rdump__load_dumpstream(svn_stream_t *stream, - svn_ra_session_t *session, - svn_ra_session_t *aux_session, - svn_boolean_t quiet, - apr_hash_t *skip_revprops, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *pool) +/*----------------------------------------------------------------------*/ + +/** + * Baton used for commit callback (and Ev2 shims). + */ +struct commit_baton_t +{ + svn_revnum_t rev; + struct parse_baton *pb; +}; + +/* + * - Notification of the commit. + * - Update the revision number mapping to take account of the actual + * committed revision number. + */ +static svn_error_t * +commit_callback(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + struct commit_baton_t *cb = baton; + struct parse_baton *pb = cb->pb; + + /* ### Don't print directly; generate a notification. */ + if (! pb->quiet) + SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n", + commit_info->revision)); + + /* Add the mapping of the dumpstream revision to the committed revision. */ + set_revision_mapping(pb->rev_map, cb->rev, commit_info->revision); + + /* If the incoming dump stream has non-contiguous revisions (e.g. from + using svndumpfilter --drop-empty-revs without --renumber-revs) then + we must account for the missing gaps in PB->REV_MAP. Otherwise we + might not be able to map all mergeinfo source revisions to the correct + revisions in the target repos. */ + if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) + && (cb->rev != pb->last_rev_mapped + 1)) + { + svn_revnum_t i; + + for (i = pb->last_rev_mapped + 1; i < cb->rev; i++) + { + set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); + } + } + + /* Update our "last revision mapped". */ + pb->last_rev_mapped = cb->rev; + + 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 commit_baton_t *cb = baton; + svn_stream_t *fstream; + svn_error_t *err; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = cb->rev - 1; + + SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + err = svn_ra_get_file(cb->pb->aux_session, path, base_revision, + fstream, NULL, NULL, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + SVN_ERR(svn_stream_close(fstream)); + + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + SVN_ERR(svn_stream_close(fstream)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_props(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + svn_node_kind_t node_kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct parse_baton *pb = baton; + + if (node_kind == svn_node_file) + { + SVN_ERR(svn_ra_get_file(pb->aux_session, path, base_revision, + NULL, NULL, props, result_pool)); + } + else if (node_kind == svn_node_dir) + { + apr_array_header_t *tmp_props; + + SVN_ERR(svn_ra_get_dir2(pb->aux_session, NULL, NULL, props, path, + base_revision, 0 /* Dirent fields */, + result_pool)); + tmp_props = svn_prop_hash_to_array(*props, result_pool); + SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, + result_pool)); + *props = svn_prop_array_to_hash(tmp_props, result_pool); + } + else + { + *props = apr_hash_make(result_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) +{ + struct commit_baton_t *cb = baton; + svn_node_kind_t node_kind; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = cb->rev - 1; + + SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision, + &node_kind, scratch_pool)); + SVN_ERR(fetch_props(props, cb->pb, path, base_revision, node_kind, + result_pool, scratch_pool)); + + 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 commit_baton_t *cb = baton; + + if (! SVN_IS_VALID_REVNUM(base_revision)) + base_revision = cb->rev - 1; + + SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision, + kind, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_delta_shim_callbacks_t * +get_shim_callbacks(struct commit_baton_t *cb, + apr_pool_t *pool) +{ + svn_delta_shim_callbacks_t *callbacks = + svn_delta_shim_callbacks_default(pool); + + callbacks->fetch_props_func = fetch_props_func; + callbacks->fetch_kind_func = fetch_kind_func; + callbacks->fetch_base_func = fetch_base_func; + callbacks->fetch_baton = cb; + + return callbacks; +} + +/* */ +static svn_error_t * +revstart(svn_revnum_t revision, + void *baton, + const svn_delta_editor_t **editor_p, + void **edit_baton_p, + apr_hash_t *rev_props, + apr_pool_t *result_pool) +{ + struct parse_baton *pb = baton; + struct commit_baton_t *cb = apr_palloc(result_pool, sizeof(*cb)); + + cb->rev = revision; + cb->pb = pb; + SVN_ERR(svn_ra__register_editor_shim_callbacks(pb->session, + get_shim_callbacks(cb, result_pool))); + SVN_ERR(svn_ra_get_commit_editor3(pb->session, + editor_p, edit_baton_p, + rev_props, + commit_callback, cb, + NULL, FALSE, result_pool)); + return SVN_NO_ERROR; +} + +/* Return an implementation of the dumpstream parser API that will drive + * commits over the RA layer to the location described by SESSION. + * + * Use AUX_SESSION (which is opened to the same URL as SESSION) + * for any secondary, out-of-band RA communications required. This is + * needed when loading a non-deltas dump, and for Ev2. + * + * Print feedback to the console for each revision, unless QUIET is true. + * + * Ignore (don't set) any revision property whose name is a key in + * SKIP_REVPROPS. The values in the hash are unimportant. + */ +static svn_error_t * +get_dumpstream_loader(svn_repos_parse_fns3_t **parser_p, + void **parse_baton_p, + svn_ra_session_t *session, + svn_ra_session_t *aux_session, + svn_boolean_t quiet, + apr_hash_t *skip_revprops, + apr_pool_t *pool) { svn_repos_parse_fns3_t *parser; struct parse_baton *parse_baton; - const svn_string_t *lock_string; - svn_boolean_t be_atomic; - svn_error_t *err; const char *session_url, *root_url, *parent_dir; + static const loader_fns_t callbacks = { revstart, fetch_props }; - SVN_ERR(svn_ra_has_capability(session, &be_atomic, - SVN_RA_CAPABILITY_ATOMIC_REVPROPS, - pool)); - SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool)); SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool)); SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir, @@ -1087,6 +1140,300 @@ svn_rdump__load_dumpstream(svn_stream_t parse_baton->last_rev_mapped = SVN_INVALID_REVNUM; parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM; parse_baton->skip_revprops = skip_revprops; + parse_baton->callbacks = &callbacks; + parse_baton->cb_baton = parse_baton; + + *parser_p = parser; + *parse_baton_p = parse_baton; + return SVN_NO_ERROR; +} + +/*----------------------------------------------------------------------*/ + +/* Dump-stream parser wrapper */ + +struct filter_parse_baton_t +{ + const svn_repos_parse_fns3_t *wrapped_parser; + struct parse_baton *wrapped_pb; +}; + +struct filter_revision_baton_t +{ + struct filter_parse_baton_t *pb; + void *wrapped_rb; +}; + +struct filter_node_baton_t +{ + struct filter_revision_baton_t *rb; + void *wrapped_nb; +}; + +static svn_error_t * +filter_magic_header_record(int version, + void *parse_baton, + apr_pool_t *pool) +{ + struct filter_parse_baton_t *pb = parse_baton; + + SVN_ERR(pb->wrapped_parser->magic_header_record(version, pb->wrapped_pb, + pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_uuid_record(const char *uuid, + void *parse_baton, + apr_pool_t *pool) +{ + struct filter_parse_baton_t *pb = parse_baton; + + SVN_ERR(pb->wrapped_parser->uuid_record(uuid, pb->wrapped_pb, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_new_revision_record(void **revision_baton, + apr_hash_t *headers, + void *parse_baton, + apr_pool_t *pool) +{ + struct filter_parse_baton_t *pb = parse_baton; + struct filter_revision_baton_t *rb = apr_pcalloc(pool, sizeof (*rb)); + + rb->pb = pb; + SVN_ERR(pb->wrapped_parser->new_revision_record(&rb->wrapped_rb, headers, + pb->wrapped_pb, pool)); + *revision_baton = rb; + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_new_node_record(void **node_baton, + apr_hash_t *headers, + void *revision_baton, + apr_pool_t *pool) +{ + struct filter_revision_baton_t *rb = revision_baton; + struct filter_parse_baton_t *pb = rb->pb; + struct filter_node_baton_t *nb = apr_pcalloc(pool, sizeof (*nb)); + + nb->rb = rb; + SVN_ERR(pb->wrapped_parser->new_node_record(&nb->wrapped_nb, headers, + rb->wrapped_rb, pool)); + *node_baton = nb; + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_set_revision_property(void *revision_baton, + const char *name, + const svn_string_t *value) +{ + struct filter_revision_baton_t *rb = revision_baton; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->set_revision_property(rb->wrapped_rb, + name, value)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_set_node_property(void *node_baton, + const char *name, + const svn_string_t *value) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->set_node_property(nb->wrapped_nb, + name, value)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_delete_node_property(void *node_baton, + const char *name) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->delete_node_property(nb->wrapped_nb, name)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_remove_node_props(void *node_baton) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->remove_node_props(nb->wrapped_nb)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_set_fulltext(svn_stream_t **stream, + void *node_baton) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->set_fulltext(stream, nb->wrapped_nb)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_apply_textdelta(svn_txdelta_window_handler_t *handler, + void **handler_baton, + void *node_baton) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->apply_textdelta(handler, handler_baton, + nb->wrapped_nb)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_close_node(void *node_baton) +{ + struct filter_node_baton_t *nb = node_baton; + struct filter_revision_baton_t *rb = nb->rb; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->close_node(nb->wrapped_nb)); + return SVN_NO_ERROR; +} + +static svn_error_t * +filter_close_revision(void *revision_baton) +{ + struct filter_revision_baton_t *rb = revision_baton; + struct filter_parse_baton_t *pb = rb->pb; + + SVN_ERR(pb->wrapped_parser->close_revision(rb->wrapped_rb)); + return SVN_NO_ERROR; +} + +static svn_error_t * +get_dumpstream_filter(svn_repos_parse_fns3_t **parser_p, + void **parse_baton_p, + const svn_repos_parse_fns3_t *wrapped_parser, + void *wrapped_baton, + apr_pool_t *pool) +{ + svn_repos_parse_fns3_t *parser; + struct filter_parse_baton_t *b; + + parser = apr_pcalloc(pool, sizeof(*parser)); + parser->magic_header_record = filter_magic_header_record; + parser->uuid_record = filter_uuid_record; + parser->new_revision_record = filter_new_revision_record; + parser->new_node_record = filter_new_node_record; + parser->set_revision_property = filter_set_revision_property; + parser->set_node_property = filter_set_node_property; + parser->delete_node_property = filter_delete_node_property; + parser->remove_node_props = filter_remove_node_props; + parser->set_fulltext = filter_set_fulltext; + parser->apply_textdelta = filter_apply_textdelta; + parser->close_node = filter_close_node; + parser->close_revision = filter_close_revision; + + b = apr_pcalloc(pool, sizeof(*b)); + b->wrapped_parser = wrapped_parser; + b->wrapped_pb = wrapped_baton; + + *parser_p = parser; + *parse_baton_p = b; + return SVN_NO_ERROR; +} + +/*----------------------------------------------------------------------*/ + +/* Implements `svn_ra__lock_retry_func_t'. */ +static svn_error_t * +lock_retry_func(void *baton, + const svn_string_t *reposlocktoken, + apr_pool_t *pool) +{ + return svn_cmdline_printf(pool, + _("Failed to get lock on destination " + "repos, currently held by '%s'\n"), + reposlocktoken->data); +} + +/* Acquire a lock (of sorts) on the repository associated with the + * given RA SESSION. This lock is just a revprop change attempt in a + * time-delay loop. This function is duplicated by svnsync in + * svnsync/svnsync.c + * + * ### TODO: Make this function more generic and + * expose it through a header for use by other Subversion + * applications to avoid duplication. + */ +static svn_error_t * +get_lock(const svn_string_t **lock_string_p, + svn_ra_session_t *session, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_boolean_t be_atomic; + + SVN_ERR(svn_ra_has_capability(session, &be_atomic, + SVN_RA_CAPABILITY_ATOMIC_REVPROPS, + pool)); + if (! be_atomic) + { + /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */ + svn_error_t *err = + svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Target server does not support atomic revision " + "property edits; consider upgrading it to 1.7.")); + svn_handle_warning2(stderr, err, "svnrdump: "); + svn_error_clear(err); + } + + return svn_ra__get_operational_lock(lock_string_p, NULL, session, + SVNRDUMP_PROP_LOCK, FALSE, + 10 /* retries */, lock_retry_func, NULL, + cancel_func, cancel_baton, pool); +} + +svn_error_t * +svn_rdump__load_dumpstream(svn_stream_t *stream, + svn_ra_session_t *session, + svn_ra_session_t *aux_session, + svn_boolean_t quiet, + apr_hash_t *skip_revprops, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_repos_parse_fns3_t *parser; + void *parse_baton; + const svn_string_t *lock_string; + svn_error_t *err; + + SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool)); + + SVN_ERR(get_dumpstream_loader(&parser, &parse_baton, + session, aux_session, + quiet, skip_revprops, + pool)); + + /* Interpose a filtering layer: currently doing nothing */ + SVN_ERR(get_dumpstream_filter(&parser, &parse_baton, + parser, parse_baton, + pool)); err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE, cancel_func, cancel_baton, pool);