diff options
Diffstat (limited to 'subversion/libsvn_repos/dump_editor.c')
-rw-r--r-- | subversion/libsvn_repos/dump_editor.c | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/dump_editor.c b/subversion/libsvn_repos/dump_editor.c new file mode 100644 index 000000000000..c1ece570a499 --- /dev/null +++ b/subversion/libsvn_repos/dump_editor.c @@ -0,0 +1,1040 @@ +/* + * dump_editor.c: A svn_delta_editor_t editor used to dump revisions. + * + * ==================================================================== + * 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_repos.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_subst.h" +#include "svn_dirent_uri.h" + +#include "private/svn_repos_private.h" + +#include <assert.h> + +#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) + + +/* Normalize the line ending style of the values of properties in PROPS + * that "need translation" (according to svn_prop_needs_translation(), + * currently all svn:* props) so that they contain only LF (\n) line endings. + * + * Put the normalized props into NORMAL_PROPS, allocated in RESULT_POOL. + */ +static svn_error_t * +normalize_props(apr_hash_t **normal_props, + apr_hash_t *props, + apr_pool_t *result_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + *normal_props = apr_hash_make(result_pool); + + iterpool = svn_pool_create(result_pool); + for (hi = apr_hash_first(result_pool, props); hi; hi = apr_hash_next(hi)) + { + const char *key = apr_hash_this_key(hi); + const svn_string_t *value = apr_hash_this_val(hi); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_repos__normalize_prop(&value, NULL, key, value, + iterpool, iterpool)); + svn_hash_sets(*normal_props, key, svn_string_dup(value, result_pool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* 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; + + /* 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 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 svn_error_t * +make_dir_baton(struct dir_baton **dbp, + 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) + SVN_ERR(svn_relpath_canonicalize_safe(&repos_relpath, NULL, path, + pool, 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); + + *dbp = new_db; + return SVN_NO_ERROR; +} + +/* 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(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. */ + SVN_ERR(make_dir_baton(&new_db, 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) + { + SVN_ERR(make_dir_baton(&new_db, 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)); + + SVN_ERR(make_dir_baton(&new_db, 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; + } + + SVN_ERR(make_dir_baton(&new_db, 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, + svn_stream_disown(eb->stream, pool), + 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; +} + +svn_error_t * +svn_repos__get_dump_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_stream_t *stream, + const char *update_anchor_relpath, + apr_pool_t *pool) +{ + struct dump_edit_baton *eb; + svn_delta_editor_t *de; + + eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); + eb->stream = stream; + eb->update_anchor_relpath = update_anchor_relpath; + 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; + + return SVN_NO_ERROR; +} |