summaryrefslogtreecommitdiffstats
path: root/subversion/svnrdump/load_editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnrdump/load_editor.c')
-rw-r--r--subversion/svnrdump/load_editor.c1211
1 files changed, 1211 insertions, 0 deletions
diff --git a/subversion/svnrdump/load_editor.c b/subversion/svnrdump/load_editor.c
new file mode 100644
index 0000000..1b053f2
--- /dev/null
+++ b/subversion/svnrdump/load_editor.c
@@ -0,0 +1,1211 @@
+/*
+ * load_editor.c: The svn_delta_editor_t editor used by svnrdump to
+ * load 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_cmdline.h"
+#include "svn_pools.h"
+#include "svn_delta.h"
+#include "svn_repos.h"
+#include "svn_props.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_subst.h"
+#include "svn_io.h"
+#include "svn_private_config.h"
+#include "private/svn_repos_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_mergeinfo_private.h"
+#include "private/svn_fspath.h"
+
+#include "svnrdump.h"
+
+#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
+
+#if 0
+#define LDR_DBG(x) SVN_DBG(x)
+#else
+#define LDR_DBG(x) while(0)
+#endif
+
+
+
+/**
+ * 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;
+
+ /* To bleep, or not to bleep? (What kind of question is that?) */
+ svn_boolean_t quiet;
+
+ /* UUID found in the dumpstream, if any; NULL otherwise. */
+ const char *uuid;
+
+ /* Root URL of the target repository. */
+ const char *root_url;
+
+ /* The "parent directory" of the target repository in which to load.
+ (This is essentially the difference between ROOT_URL and
+ SESSION's url, and roughly equivalent to the 'svnadmin load
+ --parent-dir' option.) */
+ const char *parent_dir;
+
+ /* 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
+ ### for discussion about improving the memory costs of this mapping. */
+ apr_hash_t *rev_map;
+
+ /* The most recent (youngest) revision from the dump stream mapped in
+ REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
+ svn_revnum_t last_rev_mapped;
+
+ /* The oldest revision loaded from the dump stream, or
+ SVN_INVALID_REVNUM if none have been loaded. */
+ svn_revnum_t oldest_dumpstream_rev;
+};
+
+/**
+ * Use to wrap the dir_context_t in commit.c so we can keep track of
+ * depth, relpath and parent for open_directory and close_directory.
+ */
+struct directory_baton
+{
+ void *baton;
+ const char *relpath;
+ int depth;
+ struct directory_baton *parent;
+};
+
+/**
+ * Baton used to represent a node; to be used by the parser
+ * functions. Contains a link to the revision baton.
+ */
+struct node_baton
+{
+ const char *path;
+ svn_node_kind_t kind;
+ enum svn_node_action action;
+
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_path;
+
+ void *file_baton;
+ const char *base_checksum;
+
+ struct revision_baton *rb;
+};
+
+/**
+ * Baton used to represet a revision; used by the parser
+ * functions. Contains a link to the parser baton.
+ */
+struct revision_baton
+{
+ svn_revnum_t rev;
+ apr_hash_t *revprop_table;
+ apr_int32_t rev_offset;
+
+ const svn_string_t *datestamp;
+ const svn_string_t *author;
+
+ struct parse_baton *pb;
+ struct directory_baton *db;
+ apr_pool_t *pool;
+};
+
+
+
+/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
+ anything added to the hash is allocated in the hash's pool. */
+static void
+set_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev,
+ svn_revnum_t to_rev)
+{
+ svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
+ sizeof(svn_revnum_t) * 2);
+ mapped_revs[0] = from_rev;
+ mapped_revs[1] = to_rev;
+ apr_hash_set(rev_map, mapped_revs,
+ sizeof(svn_revnum_t), mapped_revs + 1);
+}
+
+/* Return the revision to which FROM_REV maps in REV_MAP, or
+ SVN_INVALID_REVNUM if no such mapping exists. */
+static svn_revnum_t
+get_revision_mapping(apr_hash_t *rev_map,
+ svn_revnum_t from_rev)
+{
+ svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
+ sizeof(from_rev));
+ return to_rev ? *to_rev : SVN_INVALID_REVNUM;
+}
+
+
+/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with
+ PARENT_DIR, and return it in *MERGEINFO_VAL. */
+/* ### FIXME: Consider somehow sharing code with
+ ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */
+static svn_error_t *
+prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
+ const svn_string_t *mergeinfo_orig,
+ const char *parent_dir,
+ apr_pool_t *pool)
+{
+ apr_hash_t *prefixed_mergeinfo, *mergeinfo;
+ apr_hash_index_t *hi;
+ void *rangelist;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
+ prefixed_mergeinfo = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *path, *merge_source;
+
+ apr_hash_this(hi, &key, NULL, &rangelist);
+ merge_source = svn_relpath_canonicalize(key, pool);
+
+ /* The svn:mergeinfo property syntax demands a repos abspath */
+ path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
+ merge_source, pool),
+ pool);
+ svn_hash_sets(prefixed_mergeinfo, path, rangelist);
+ }
+ return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
+}
+
+
+/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
+ as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
+ (allocated from POOL). */
+/* ### FIXME: Consider somehow sharing code with
+ ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */
+static svn_error_t *
+renumber_mergeinfo_revs(svn_string_t **final_val,
+ const svn_string_t *initial_val,
+ struct revision_baton *rb,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
+ svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
+
+ /* Issue #3020
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
+ Remove mergeinfo older than the oldest revision in the dump stream
+ and adjust its revisions by the difference between the head rev of
+ the target repository and the current dump stream rev. */
+ if (rb->pb->oldest_dumpstream_rev > 1)
+ {
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &predates_stream_mergeinfo, mergeinfo,
+ rb->pb->oldest_dumpstream_rev - 1, 0,
+ TRUE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &mergeinfo, mergeinfo,
+ rb->pb->oldest_dumpstream_rev - 1, 0,
+ FALSE, subpool, subpool));
+ SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
+ &predates_stream_mergeinfo,
+ predates_stream_mergeinfo,
+ -rb->rev_offset, subpool, subpool));
+ }
+ else
+ {
+ predates_stream_mergeinfo = NULL;
+ }
+
+ for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ svn_rangelist_t *rangelist;
+ struct parse_baton *pb = rb->pb;
+ int i;
+ const void *path;
+ apr_ssize_t pathlen;
+ void *val;
+
+ apr_hash_this(hi, &path, &pathlen, &val);
+ rangelist = val;
+
+ /* Possibly renumber revisions in merge source's rangelist. */
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_revnum_t rev_from_map;
+ svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
+ svn_merge_range_t *);
+ rev_from_map = get_revision_mapping(pb->rev_map, range->start);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ {
+ range->start = rev_from_map;
+ }
+ else if (range->start == pb->oldest_dumpstream_rev - 1)
+ {
+ /* Since the start revision of svn_merge_range_t are not
+ inclusive there is one possible valid start revision that
+ won't be found in the PB->REV_MAP mapping of load stream
+ revsions to loaded revisions: The revision immediately
+ preceeding the oldest revision from the load stream.
+ This is a valid revision for mergeinfo, but not a valid
+ copy from revision (which PB->REV_MAP also maps for) so it
+ will never be in the mapping.
+
+ If that is what we have here, then find the mapping for the
+ oldest rev from the load stream and subtract 1 to get the
+ renumbered, non-inclusive, start revision. */
+ rev_from_map = get_revision_mapping(pb->rev_map,
+ pb->oldest_dumpstream_rev);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->start = rev_from_map - 1;
+ }
+ else
+ {
+ /* If we can't remap the start revision then don't even bother
+ trying to remap the end revision. It's possible we might
+ actually succeed at the latter, which can result in invalid
+ mergeinfo with a start rev > end rev. If that gets into the
+ repository then a world of bustage breaks loose anytime that
+ bogus mergeinfo is parsed. See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
+ */
+ continue;
+ }
+
+ rev_from_map = get_revision_mapping(pb->rev_map, range->end);
+ if (SVN_IS_VALID_REVNUM(rev_from_map))
+ range->end = rev_from_map;
+ }
+ apr_hash_set(final_mergeinfo, path, pathlen, rangelist);
+ }
+
+ if (predates_stream_mergeinfo)
+ {
+ SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
+ subpool, subpool));
+ }
+
+ SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
+
+ /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
+ or r1. However, svndumpfilter can be abused to produce r1 merge source
+ revs. So if we encounter any, then strip them out, no need to put them
+ into the load target. */
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
+ final_mergeinfo,
+ 1, 0, FALSE,
+ subpool, subpool));
+
+ SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+commit_callback(const svn_commit_info_t *commit_info,
+ void *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)
+{
+ 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)
+{
+ struct revision_baton *rb = baton;
+ svn_node_kind_t node_kind;
+
+ 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,
+ &node_kind, scratch_pool));
+
+ 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)
+ {
+ apr_array_header_t *tmp_props;
+
+ 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);
+ }
+
+ 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;
+}
+
+/* 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);
+}
+
+static svn_error_t *
+new_revision_record(void **revision_baton,
+ apr_hash_t *headers,
+ void *parse_baton,
+ apr_pool_t *pool)
+{
+ struct revision_baton *rb;
+ struct parse_baton *pb;
+ apr_hash_index_t *hi;
+ svn_revnum_t head_rev;
+
+ rb = apr_pcalloc(pool, sizeof(*rb));
+ pb = parse_baton;
+ rb->pool = svn_pool_create(pool);
+ rb->pb = pb;
+
+ for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *hname = svn__apr_hash_index_key(hi);
+ const char *hval = svn__apr_hash_index_val(hi);
+
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
+ rb->rev = atoi(hval);
+ }
+
+ SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, 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));
+
+ /* Stash the oldest (non-zero) dumpstream revision seen. */
+ if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
+ pb->oldest_dumpstream_rev = rb->rev;
+
+ /* 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->revprop_table = apr_hash_make(rb->pool);
+
+ *revision_baton = rb;
+ return SVN_NO_ERROR;
+}
+
+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)
+{
+ struct parse_baton *pb;
+ pb = parse_baton;
+ pb->uuid = apr_pstrdup(pool, uuid);
+ return SVN_NO_ERROR;
+}
+
+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;
+ struct node_baton *nb;
+ struct directory_baton *child_db;
+ apr_hash_index_t *hi;
+ void *child_baton;
+ char *relpath_compose;
+ const char *nb_dirname;
+
+ nb = apr_pcalloc(rb->pool, sizeof(*nb));
+ nb->rb = rb;
+
+ nb->copyfrom_path = NULL;
+ nb->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* 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
+ information like svn:log in set_revision_property. We can now
+ use it all this information to create our commit_editor. But
+ first, clear revprops that we aren't allowed to set with the
+ commit_editor. We'll set them separately using the RA API
+ after closing the editor (see close_revision). */
+
+ 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,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+
+ LDR_DBG(("Opened root %p\n", child_baton));
+
+ /* child_db corresponds to the root directory baton here */
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = 0;
+ child_db->relpath = "";
+ child_db->parent = NULL;
+ rb->db = child_db;
+ }
+
+ for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
+ {
+ const char *hname = svn__apr_hash_index_key(hi);
+ const char *hval = svn__apr_hash_index_val(hi);
+
+ /* Parse the different kinds of headers we can encounter and
+ stuff them into the node_baton for writing later */
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
+ nb->path = apr_pstrdup(rb->pool, hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
+ nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
+ {
+ if (strcmp(hval, "add") == 0)
+ nb->action = svn_node_action_add;
+ if (strcmp(hval, "change") == 0)
+ nb->action = svn_node_action_change;
+ if (strcmp(hval, "delete") == 0)
+ nb->action = svn_node_action_delete;
+ if (strcmp(hval, "replace") == 0)
+ nb->action = svn_node_action_replace;
+ }
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
+ nb->base_checksum = apr_pstrdup(rb->pool, hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
+ nb->copyfrom_rev = atoi(hval);
+ if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
+ nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
+ }
+
+ nb_dirname = svn_relpath_dirname(nb->path, pool);
+ if (svn_path_compare_paths(nb_dirname,
+ rb->db->relpath) != 0)
+ {
+ char *ancestor_path;
+ apr_size_t residual_close_count;
+ apr_array_header_t *residual_open_path;
+ int i;
+ apr_size_t n;
+
+ /* Before attempting to handle the action, call open_directory
+ for all the path components and set the directory baton
+ accordingly */
+ ancestor_path =
+ svn_relpath_get_longest_ancestor(nb_dirname,
+ rb->db->relpath, pool);
+ residual_close_count =
+ svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
+ rb->db->relpath));
+ residual_open_path =
+ svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
+ nb_dirname), pool);
+
+ /* First close all as many directories as there are after
+ skip_ancestor, and then open fresh directories */
+ for (n = 0; n < residual_close_count; n ++)
+ {
+ /* Don't worry about destroying the actual rb->db object,
+ since the pool we're using has the lifetime of one
+ revision anyway */
+ LDR_DBG(("Closing dir %p\n", rb->db->baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ rb->db = rb->db->parent;
+ }
+
+ for (i = 0; i < residual_open_path->nelts; i ++)
+ {
+ relpath_compose =
+ svn_relpath_join(rb->db->relpath,
+ APR_ARRAY_IDX(residual_open_path, i, const char *),
+ rb->pool);
+ SVN_ERR(commit_editor->open_directory(relpath_compose,
+ rb->db->baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+ LDR_DBG(("Opened dir %p\n", child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = relpath_compose;
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ }
+ }
+
+ /* Fix up the copyfrom information in light of mapped revisions and
+ non-root load targets, and convert copyfrom path into a full
+ URL. */
+ if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
+ {
+ svn_revnum_t copyfrom_rev;
+
+ /* Try to find the copyfrom revision in the revision map;
+ failing that, fall back to the revision offset approach. */
+ copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
+
+ if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Relative source revision %ld is not"
+ " available in current repository"),
+ copyfrom_rev);
+
+ nb->copyfrom_rev = copyfrom_rev;
+
+ if (rb->pb->parent_dir)
+ nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
+ nb->copyfrom_path, rb->pool);
+ nb->copyfrom_path = svn_path_url_add_component2(rb->pb->root_url,
+ nb->copyfrom_path,
+ rb->pool);
+ }
+
+
+ switch (nb->action)
+ {
+ case svn_node_action_delete:
+ case svn_node_action_replace:
+ LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton));
+ SVN_ERR(commit_editor->delete_entry(nb->path, rb->rev - rb->rev_offset,
+ rb->db->baton, rb->pool));
+ if (nb->action == svn_node_action_delete)
+ break;
+ else
+ /* FALL THROUGH */;
+ case svn_node_action_add:
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
+ nb->copyfrom_path,
+ nb->copyfrom_rev,
+ rb->pool, &(nb->file_baton)));
+ LDR_DBG(("Added file %s to dir %p as %p\n",
+ nb->path, rb->db->baton, nb->file_baton));
+ break;
+ case svn_node_dir:
+ SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
+ nb->copyfrom_path,
+ nb->copyfrom_rev,
+ rb->pool, &child_baton));
+ LDR_DBG(("Added dir %s to dir %p as %p\n",
+ nb->path, rb->db->baton, child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = apr_pstrdup(rb->pool, nb->path);
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ break;
+ default:
+ break;
+ }
+ break;
+ case svn_node_action_change:
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
+ SVN_INVALID_REVNUM, rb->pool,
+ &(nb->file_baton)));
+ break;
+ default:
+ SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+ child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
+ child_db->baton = child_baton;
+ child_db->depth = rb->db->depth + 1;
+ child_db->relpath = apr_pstrdup(rb->pool, nb->path);
+ child_db->parent = rb->db;
+ rb->db = child_db;
+ break;
+ }
+ break;
+ }
+
+ *node_baton = nb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_revision_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct revision_baton *rb = baton;
+
+ SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
+
+ SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
+
+ if (rb->rev > 0)
+ {
+ svn_hash_sets(rb->revprop_table,
+ apr_pstrdup(rb->pool, name),
+ svn_string_dup(value, rb->pool));
+ }
+ else if (rb->rev_offset == -1)
+ {
+ /* Special case: set revision 0 properties directly (which is
+ safe because the commit_editor hasn't been created yet), but
+ only when loading into an 'empty' filesystem. */
+ SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
+ name, NULL, value, rb->pool));
+ }
+
+ /* Remember any datestamp/ author that passes through (see comment
+ in close_revision). */
+ if (!strcmp(name, SVN_PROP_REVISION_DATE))
+ rb->datestamp = svn_string_dup(value, rb->pool);
+ if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
+ rb->author = svn_string_dup(value, rb->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_node_property(void *baton,
+ const char *name,
+ const svn_string_t *value)
+{
+ struct node_baton *nb = baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
+ {
+ svn_string_t *renumbered_mergeinfo;
+ svn_string_t prop_val;
+
+ /* Tolerate mergeinfo with "\r\n" line endings because some
+ dumpstream sources might contain as much. If so normalize
+ the line endings to '\n' and make a notification to
+ PARSE_BATON->FEEDBACK_STREAM that we have made this
+ correction. */
+ if (strstr(value->data, "\r"))
+ {
+ const char *prop_eol_normalized;
+
+ SVN_ERR(svn_subst_translate_cstring2(value->data,
+ &prop_eol_normalized,
+ "\n", /* translate to LF */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ pool));
+ prop_val.data = prop_eol_normalized;
+ prop_val.len = strlen(prop_eol_normalized);
+ value = &prop_val;
+
+ /* ### TODO: notify? */
+ }
+
+ /* Renumber mergeinfo as appropriate. */
+ SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value,
+ nb->rb, pool));
+ value = renumbered_mergeinfo;
+
+ if (nb->rb->pb->parent_dir)
+ {
+ /* Prefix the merge source paths with PB->parent_dir. */
+ /* ASSUMPTION: All source paths are included in the dump stream. */
+ svn_string_t *mergeinfo_val;
+ SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
+ nb->rb->pb->parent_dir, pool));
+ value = mergeinfo_val;
+ }
+ }
+
+ SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
+
+ SVN_ERR(svn_repos__validate_prop(name, value, pool));
+
+ switch (nb->kind)
+ {
+ case svn_node_file:
+ LDR_DBG(("Applying properties on %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
+ value, pool));
+ break;
+ case svn_node_dir:
+ LDR_DBG(("Applying properties on %p\n", nb->rb->db->baton));
+ SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
+ value, pool));
+ break;
+ default:
+ break;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_node_property(void *baton,
+ const char *name)
+{
+ struct node_baton *nb = baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
+
+ if (nb->kind == svn_node_file)
+ SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
+ NULL, pool));
+ else
+ SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+remove_node_props(void *baton)
+{
+ struct node_baton *nb = baton;
+ apr_pool_t *pool = nb->rb->pool;
+ apr_hash_index_t *hi;
+ apr_hash_t *props;
+
+ if ((nb->action == svn_node_action_add
+ || nb->action == svn_node_action_replace)
+ && ! SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
+ /* 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, nb->path,
+ SVN_INVALID_REVNUM, NULL, NULL, &props, pool));
+ }
+ else /* nb->kind == svn_node_dir */
+ {
+ SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
+ nb->path, SVN_INVALID_REVNUM, 0, pool));
+ }
+
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_prop_kind_t kind = svn_property_kind2(name);
+
+ if (kind == svn_prop_regular_kind)
+ SVN_ERR(set_node_property(nb, name, NULL));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+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;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ apr_pool_t *pool = nb->rb->pool;
+
+ LDR_DBG(("Setting fulltext for %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
+ pool, &handler, &handler_baton));
+ *stream = svn_txdelta_target_push(handler, handler_baton,
+ svn_stream_empty(pool), pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ void *node_baton)
+{
+ struct node_baton *nb = node_baton;
+ const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
+ apr_pool_t *pool = nb->rb->pool;
+
+ LDR_DBG(("Applying textdelta to %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
+ pool, handler, handler_baton));
+
+ return SVN_NO_ERROR;
+}
+
+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;
+
+ /* Pass a file node closure through to the editor *unless* we
+ deleted the file (which doesn't require us to open it). */
+ if ((nb->kind == svn_node_file) && (nb->file_baton))
+ {
+ LDR_DBG(("Closing file %p\n", nb->file_baton));
+ SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
+ }
+
+ /* The svn_node_dir case is handled in close_revision */
+
+ return SVN_NO_ERROR;
+}
+
+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;
+ svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
+
+ /* Fake revision 0 */
+ if (rb->rev == 0)
+ {
+ /* ### Don't print directly; generate a notification. */
+ if (! rb->pb->quiet)
+ SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
+ }
+ else if (commit_editor)
+ {
+ /* Close all pending open directories, and then close the edit
+ session itself */
+ while (rb->db && rb->db->parent)
+ {
+ LDR_DBG(("Closing dir %p\n", rb->db->baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ rb->db = rb->db->parent;
+ }
+ /* root dir's baton */
+ LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
+ SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
+ SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
+ }
+ else
+ {
+ 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,
+ rb->rev - rb->rev_offset - 1,
+ rb->pool, &child_baton));
+
+ LDR_DBG(("Opened root %p\n", child_baton));
+ LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
+ SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
+ SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
+ }
+
+ /* svn_fs_commit_txn() rewrites the datestamp and author properties;
+ we'll rewrite them again by hand after closing the commit_editor.
+ The only time we don't do this is for revision 0 when loaded into
+ a non-empty repository. */
+ if (rb->rev > 0)
+ {
+ committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
+ }
+ else if (rb->rev_offset == -1)
+ {
+ committed_rev = 0;
+ }
+
+ if (SVN_IS_VALID_REVNUM(committed_rev))
+ {
+ 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));
+ 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));
+ }
+
+ svn_pool_destroy(rb->pool);
+
+ 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,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ 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;
+
+ 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,
+ session_url, pool));
+
+ parser = apr_pcalloc(pool, sizeof(*parser));
+ parser->magic_header_record = magic_header_record;
+ parser->uuid_record = uuid_record;
+ parser->new_revision_record = new_revision_record;
+ parser->new_node_record = new_node_record;
+ parser->set_revision_property = set_revision_property;
+ parser->set_node_property = set_node_property;
+ parser->delete_node_property = delete_node_property;
+ parser->remove_node_props = remove_node_props;
+ parser->set_fulltext = set_fulltext;
+ parser->apply_textdelta = apply_textdelta;
+ parser->close_node = close_node;
+ parser->close_revision = close_revision;
+
+ parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
+ parse_baton->session = session;
+ parse_baton->aux_session = aux_session;
+ parse_baton->quiet = quiet;
+ parse_baton->root_url = root_url;
+ parse_baton->parent_dir = parent_dir;
+ parse_baton->rev_map = apr_hash_make(pool);
+ parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
+ parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
+
+ err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
+ cancel_func, cancel_baton, pool);
+
+ /* If all goes well, or if we're cancelled cleanly, don't leave a
+ stray lock behind. */
+ if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
+ err = svn_error_compose_create(
+ svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,
+ lock_string, pool),
+ err);
+ return err;
+}
OpenPOWER on IntegriCloud