aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_repos/dump_editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_repos/dump_editor.c')
-rw-r--r--subversion/libsvn_repos/dump_editor.c1040
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;
+}