diff options
Diffstat (limited to 'subversion/libsvn_repos/load-fs-vtable.c')
-rw-r--r-- | subversion/libsvn_repos/load-fs-vtable.c | 1140 |
1 files changed, 1140 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/load-fs-vtable.c b/subversion/libsvn_repos/load-fs-vtable.c new file mode 100644 index 0000000..c8c5e95 --- /dev/null +++ b/subversion/libsvn_repos/load-fs-vtable.c @@ -0,0 +1,1140 @@ +/* load-fs-vtable.c --- dumpstream loader vtable for committing into a + * Subversion filesystem. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_fs.h" +#include "svn_repos.h" +#include "svn_string.h" +#include "svn_props.h" +#include "repos.h" +#include "svn_private_config.h" +#include "svn_mergeinfo.h" +#include "svn_checksum.h" +#include "svn_subst.h" +#include "svn_ctype.h" +#include "svn_dirent_uri.h" + +#include <apr_lib.h> + +#include "private/svn_fspath.h" +#include "private/svn_dep_compat.h" +#include "private/svn_mergeinfo_private.h" + +/*----------------------------------------------------------------------*/ + +/** Batons used herein **/ + +struct parse_baton +{ + svn_repos_t *repos; + svn_fs_t *fs; + + svn_boolean_t use_history; + svn_boolean_t validate_props; + svn_boolean_t use_pre_commit_hook; + svn_boolean_t use_post_commit_hook; + enum svn_repos_load_uuid uuid_action; + const char *parent_dir; /* repository relpath, or NULL */ + svn_repos_notify_func_t notify_func; + void *notify_baton; + svn_repos_notify_t *notify; + apr_pool_t *pool; + + /* Start and end (inclusive) of revision range we'll pay attention + to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by + revisions. */ + svn_revnum_t start_rev; + svn_revnum_t end_rev; + + /* A hash mapping copy-from revisions and mergeinfo range revisions + (svn_revnum_t *) in the dump stream to their corresponding revisions + (svn_revnum_t *) in the loaded repository. The hash and its + contents are allocated in POOL. */ + /* ### 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. If no revisions have been mapped yet, this is set to + SVN_INVALID_REVNUM. */ + svn_revnum_t last_rev_mapped; + + /* The oldest old revision loaded from the dump stream. If no revisions + have been loaded yet, this is set to SVN_INVALID_REVNUM. */ + svn_revnum_t oldest_old_rev; +}; + +struct revision_baton +{ + svn_revnum_t rev; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + + const svn_string_t *datestamp; + + apr_int32_t rev_offset; + svn_boolean_t skipped; + + struct parse_baton *pb; + apr_pool_t *pool; +}; + +struct node_baton +{ + const char *path; + svn_node_kind_t kind; + enum svn_node_action action; + svn_checksum_t *base_checksum; /* null, if not available */ + svn_checksum_t *result_checksum; /* null, if not available */ + svn_checksum_t *copy_source_checksum; /* null, if not available */ + + svn_revnum_t copyfrom_rev; + const char *copyfrom_path; + + struct revision_baton *rb; + 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; +} + + +/* Change revision property NAME to VALUE for REVISION in REPOS. If + VALIDATE_PROPS is set, use functions which perform validation of + the property value. Otherwise, bypass those checks. */ +static svn_error_t * +change_rev_prop(svn_repos_t *repos, + svn_revnum_t revision, + const char *name, + const svn_string_t *value, + svn_boolean_t validate_props, + apr_pool_t *pool) +{ + if (validate_props) + return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name, + NULL, value, FALSE, FALSE, + NULL, NULL, pool); + else + return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name, + NULL, value, pool); +} + +/* Change property NAME to VALUE for PATH in TXN_ROOT. If + VALIDATE_PROPS is set, use functions which perform validation of + the property value. Otherwise, bypass those checks. */ +static svn_error_t * +change_node_prop(svn_fs_root_t *txn_root, + const char *path, + const char *name, + const svn_string_t *value, + svn_boolean_t validate_props, + apr_pool_t *pool) +{ + if (validate_props) + return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool); + else + return svn_fs_change_node_prop(txn_root, path, name, value, pool); +} + +/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and + return it in *MERGEINFO_VAL. */ +/* ### FIXME: Consider somehow sharing code with + ### svnrdump/load_editor.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 + ### svnrdump/load_editor.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_old_rev > 1) + { + SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( + &predates_stream_mergeinfo, mergeinfo, + rb->pb->oldest_old_rev - 1, 0, + TRUE, subpool, subpool)); + SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( + &mergeinfo, mergeinfo, + rb->pb->oldest_old_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)) + { + const char *merge_source; + svn_rangelist_t *rangelist; + struct parse_baton *pb = rb->pb; + int i; + const void *key; + void *val; + + apr_hash_this(hi, &key, NULL, &val); + merge_source = key; + 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_old_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_old_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; + } + svn_hash_sets(final_mergeinfo, merge_source, 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; +} + +/*----------------------------------------------------------------------*/ + +/** vtable for doing commits to a fs **/ + + +static svn_error_t * +make_node_baton(struct node_baton **node_baton_p, + apr_hash_t *headers, + struct revision_baton *rb, + apr_pool_t *pool) +{ + struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb)); + const char *val; + + /* Start with sensible defaults. */ + nb->rb = rb; + nb->pool = pool; + nb->kind = svn_node_unknown; + + /* Then add info from the headers. */ + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))) + { + val = svn_relpath_canonicalize(val, pool); + if (rb->pb->parent_dir) + nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool); + else + nb->path = val; + } + + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND))) + { + if (! strcmp(val, "file")) + nb->kind = svn_node_file; + else if (! strcmp(val, "dir")) + nb->kind = svn_node_dir; + } + + nb->action = (enum svn_node_action)(-1); /* an invalid action code */ + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION))) + { + if (! strcmp(val, "change")) + nb->action = svn_node_action_change; + else if (! strcmp(val, "add")) + nb->action = svn_node_action_add; + else if (! strcmp(val, "delete")) + nb->action = svn_node_action_delete; + else if (! strcmp(val, "replace")) + nb->action = svn_node_action_replace; + } + + nb->copyfrom_rev = SVN_INVALID_REVNUM; + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV))) + { + nb->copyfrom_rev = SVN_STR_TO_REV(val); + } + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH))) + { + val = svn_relpath_canonicalize(val, pool); + if (rb->pb->parent_dir) + nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool); + else + nb->copyfrom_path = val; + } + + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM))) + { + SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5, + val, pool)); + } + + if ((val = svn_hash_gets(headers, + SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM))) + { + SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val, + pool)); + } + + if ((val = svn_hash_gets(headers, + SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM))) + { + SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum, + svn_checksum_md5, val, pool)); + } + + /* What's cool about this dump format is that the parser just + ignores any unrecognized headers. :-) */ + + *node_baton_p = nb; + return SVN_NO_ERROR; +} + +static struct revision_baton * +make_revision_baton(apr_hash_t *headers, + struct parse_baton *pb, + apr_pool_t *pool) +{ + struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb)); + const char *val; + + rb->pb = pb; + rb->pool = pool; + rb->rev = SVN_INVALID_REVNUM; + + if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))) + { + rb->rev = SVN_STR_TO_REV(val); + + /* If we're filtering revisions, is this one we'll skip? */ + rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev) + && ((rb->rev < pb->start_rev) || + (rb->rev > pb->end_rev))); + } + + return rb; +} + + +static svn_error_t * +new_revision_record(void **revision_baton, + apr_hash_t *headers, + void *parse_baton, + apr_pool_t *pool) +{ + struct parse_baton *pb = parse_baton; + struct revision_baton *rb; + svn_revnum_t head_rev; + + rb = make_revision_baton(headers, pb, pool); + + /* ### If we're filtering revisions, and this is one we've skipped, + ### and we've skipped it because it has a revision number younger + ### than the youngest in our acceptable range, then should we + ### just bail out here? */ + /* + if (rb->skipped && (rb->rev > pb->end_rev)) + return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0, + _("Finished processing acceptable load " + "revision range")); + */ + + SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, 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)); + + if ((rb->rev > 0) && (! rb->skipped)) + { + /* Create a new fs txn. */ + SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool)); + SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool)); + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_txn_start; + pb->notify->old_revision = rb->rev; + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + + /* Stash the oldest "old" revision committed from the load stream. */ + if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev)) + pb->oldest_old_rev = rb->rev; + } + + /* If we're skipping this revision, try to notify someone. */ + if (rb->skipped && pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_skipped_rev; + pb->notify->old_revision = rb->rev; + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + + /* If we're parsing revision 0, only the revision are (possibly) + interesting to us: when loading the stream into an empty + filesystem, then we want new filesystem's revision 0 to have the + same props. Otherwise, we just ignore revision 0 in the stream. */ + + *revision_baton = rb; + return SVN_NO_ERROR; +} + + + +/* Factorized helper func for new_node_record() */ +static svn_error_t * +maybe_add_with_history(struct node_baton *nb, + struct revision_baton *rb, + apr_pool_t *pool) +{ + struct parse_baton *pb = rb->pb; + + if ((nb->copyfrom_path == NULL) || (! pb->use_history)) + { + /* Add empty file or dir, without history. */ + if (nb->kind == svn_node_file) + SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool)); + + else if (nb->kind == svn_node_dir) + SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool)); + } + else + { + /* Hunt down the source revision in this fs. */ + svn_fs_root_t *copy_root; + 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); + + SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool)); + + if (nb->copy_source_checksum) + { + svn_checksum_t *checksum; + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root, + nb->copyfrom_path, TRUE, pool)); + if (!svn_checksum_match(nb->copy_source_checksum, checksum)) + return svn_checksum_mismatch_err(nb->copy_source_checksum, + checksum, pool, + _("Copy source checksum mismatch on copy from '%s'@%ld\n" + "to '%s' in rev based on r%ld"), + nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev); + } + + SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path, + rb->txn_root, nb->path, pool)); + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_copied_node; + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + } + + 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 = parse_baton; + svn_revnum_t youngest_rev; + + if (pb->uuid_action == svn_repos_load_uuid_ignore) + return SVN_NO_ERROR; + + if (pb->uuid_action != svn_repos_load_uuid_force) + { + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool)); + if (youngest_rev != 0) + return SVN_NO_ERROR; + } + + return svn_fs_set_uuid(pb->fs, uuid, pool); +} + +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; + struct parse_baton *pb = rb->pb; + struct node_baton *nb; + + if (rb->rev == 0) + return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, + _("Malformed dumpstream: " + "Revision 0 must not contain node records")); + + SVN_ERR(make_node_baton(&nb, headers, rb, pool)); + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + { + *node_baton = nb; + return SVN_NO_ERROR; + } + + /* Make sure we have an action we recognize. */ + if (nb->action < svn_node_action_change + || nb->action > svn_node_action_replace) + return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL, + _("Unrecognized node-action on node '%s'"), + nb->path); + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_node_start; + pb->notify->node_action = nb->action; + pb->notify->path = nb->path; + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + + switch (nb->action) + { + case svn_node_action_change: + break; + + case svn_node_action_delete: + SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool)); + break; + + case svn_node_action_add: + SVN_ERR(maybe_add_with_history(nb, rb, pool)); + break; + + case svn_node_action_replace: + SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool)); + SVN_ERR(maybe_add_with_history(nb, rb, pool)); + 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; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + return SVN_NO_ERROR; + + if (rb->rev > 0) + { + if (rb->pb->validate_props) + SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool)); + else + SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool)); + + /* Remember any datestamp that passes through! (See comment in + close_revision() below.) */ + if (! strcmp(name, SVN_PROP_REVISION_DATE)) + rb->datestamp = svn_string_dup(value, rb->pool); + } + else if (rb->rev == 0) + { + /* Special case: set revision 0 properties when loading into an + 'empty' filesystem. */ + struct parse_baton *pb = rb->pb; + svn_revnum_t youngest_rev; + + SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool)); + + if (youngest_rev == 0) + SVN_ERR(change_rev_prop(pb->repos, 0, name, value, + pb->validate_props, 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; + struct revision_baton *rb = nb->rb; + struct parse_baton *pb = rb->pb; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + return SVN_NO_ERROR; + + if (strcmp(name, SVN_PROP_MERGEINFO) == 0) + { + svn_string_t *renumbered_mergeinfo; + /* ### Need to cast away const. We cannot change the declaration of + * ### this function since it is part of svn_repos_parse_fns2_t. */ + svn_string_t *prop_val = (svn_string_t *)value; + + /* 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(prop_val->data, "\r")) + { + const char *prop_eol_normalized; + + SVN_ERR(svn_subst_translate_cstring2(prop_val->data, + &prop_eol_normalized, + "\n", /* translate to LF */ + FALSE, /* no repair */ + NULL, /* no keywords */ + FALSE, /* no expansion */ + nb->pool)); + prop_val->data = prop_eol_normalized; + prop_val->len = strlen(prop_eol_normalized); + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_normalized_mergeinfo; + pb->notify_func(pb->notify_baton, pb->notify, nb->pool); + } + } + + /* Renumber mergeinfo as appropriate. */ + SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, prop_val, rb, + nb->pool)); + value = renumbered_mergeinfo; + if (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, + pb->parent_dir, nb->pool)); + value = mergeinfo_val; + } + } + + return change_node_prop(rb->txn_root, nb->path, name, value, + pb->validate_props, nb->pool); +} + + +static svn_error_t * +delete_node_property(void *baton, + const char *name) +{ + struct node_baton *nb = baton; + struct revision_baton *rb = nb->rb; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + return SVN_NO_ERROR; + + return change_node_prop(rb->txn_root, nb->path, name, NULL, + rb->pb->validate_props, nb->pool); +} + + +static svn_error_t * +remove_node_props(void *baton) +{ + struct node_baton *nb = baton; + struct revision_baton *rb = nb->rb; + apr_hash_t *proplist; + apr_hash_index_t *hi; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + return SVN_NO_ERROR; + + SVN_ERR(svn_fs_node_proplist(&proplist, + rb->txn_root, nb->path, nb->pool)); + + for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi)) + { + const void *key; + + apr_hash_this(hi, &key, NULL, NULL); + SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL, + rb->pb->validate_props, nb->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; + struct revision_baton *rb = nb->rb; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + { + *handler = NULL; + return SVN_NO_ERROR; + } + + return svn_fs_apply_textdelta(handler, handler_baton, + rb->txn_root, nb->path, + svn_checksum_to_cstring(nb->base_checksum, + nb->pool), + svn_checksum_to_cstring(nb->result_checksum, + nb->pool), + nb->pool); +} + + +static svn_error_t * +set_fulltext(svn_stream_t **stream, + void *node_baton) +{ + struct node_baton *nb = node_baton; + struct revision_baton *rb = nb->rb; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + { + *stream = NULL; + return SVN_NO_ERROR; + } + + return svn_fs_apply_text(stream, + rb->txn_root, nb->path, + svn_checksum_to_cstring(nb->result_checksum, + nb->pool), + nb->pool); +} + + +static svn_error_t * +close_node(void *baton) +{ + struct node_baton *nb = baton; + struct revision_baton *rb = nb->rb; + struct parse_baton *pb = rb->pb; + + /* If we're skipping this revision, we're done here. */ + if (rb->skipped) + return SVN_NO_ERROR; + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_node_done; + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +close_revision(void *baton) +{ + struct revision_baton *rb = baton; + struct parse_baton *pb = rb->pb; + const char *conflict_msg = NULL; + svn_revnum_t committed_rev; + svn_error_t *err; + const char *txn_name = NULL; + apr_hash_t *hooks_env; + + /* If we're skipping this revision or it has an invalid revision + number, we're done here. */ + if (rb->skipped || (rb->rev <= 0)) + return SVN_NO_ERROR; + + /* Get the txn name and hooks environment if they will be needed. */ + if (pb->use_pre_commit_hook || pb->use_post_commit_hook) + { + SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path, + rb->pool, rb->pool)); + + err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool); + if (err) + { + svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); + return svn_error_trace(err); + } + } + + /* Run the pre-commit hook, if so commanded. */ + if (pb->use_pre_commit_hook) + { + err = svn_repos__hooks_pre_commit(pb->repos, hooks_env, + txn_name, rb->pool); + if (err) + { + svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); + return svn_error_trace(err); + } + } + + /* Commit. */ + err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool); + if (SVN_IS_VALID_REVNUM(committed_rev)) + { + if (err) + { + /* ### Log any error, but better yet is to rev + ### close_revision()'s API to allow both committed_rev and err + ### to be returned, see #3768. */ + svn_error_clear(err); + } + } + else + { + svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool)); + if (conflict_msg) + return svn_error_quick_wrap(err, conflict_msg); + else + return svn_error_trace(err); + } + + /* Run post-commit hook, if so commanded. */ + if (pb->use_post_commit_hook) + { + if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env, + committed_rev, txn_name, + rb->pool))) + return svn_error_create + (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, + _("Commit succeeded, but post-commit hook failed")); + } + + /* After a successful commit, must record the dump-rev -> in-repos-rev + mapping, so that copyfrom instructions in the dump file can look up the + correct repository revision to copy from. */ + set_revision_mapping(pb->rev_map, rb->rev, committed_rev); + + /* 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; + + /* Deltify the predecessors of paths changed in this revision. */ + SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool)); + + /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the + current clock-time. We don't want that, we want to preserve + history exactly. Good thing revision props aren't versioned! + Note that if rb->datestamp is NULL, that's fine -- if the dump + data doesn't carry a datestamp, we want to preserve that fact in + the load. */ + SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE, + rb->datestamp, pb->validate_props, rb->pool)); + + if (pb->notify_func) + { + pb->notify->action = svn_repos_notify_load_txn_committed; + pb->notify->new_revision = committed_rev; + pb->notify->old_revision = ((committed_rev == rb->rev) + ? SVN_INVALID_REVNUM + : rb->rev); + pb->notify_func(pb->notify_baton, pb->notify, rb->pool); + } + + return SVN_NO_ERROR; +} + + +/*----------------------------------------------------------------------*/ + +/** The public routines **/ + + +svn_error_t * +svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks, + void **parse_baton, + svn_repos_t *repos, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_boolean_t use_history, + svn_boolean_t validate_props, + enum svn_repos_load_uuid uuid_action, + const char *parent_dir, + svn_repos_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser)); + struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb)); + + if (parent_dir) + parent_dir = svn_relpath_canonicalize(parent_dir, pool); + + SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) && + SVN_IS_VALID_REVNUM(end_rev)) + || ((! SVN_IS_VALID_REVNUM(start_rev)) && + (! SVN_IS_VALID_REVNUM(end_rev)))); + if (SVN_IS_VALID_REVNUM(start_rev)) + SVN_ERR_ASSERT(start_rev <= end_rev); + + 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->remove_node_props = remove_node_props; + parser->set_fulltext = set_fulltext; + parser->close_node = close_node; + parser->close_revision = close_revision; + parser->delete_node_property = delete_node_property; + parser->apply_textdelta = apply_textdelta; + + pb->repos = repos; + pb->fs = svn_repos_fs(repos); + pb->use_history = use_history; + pb->validate_props = validate_props; + pb->notify_func = notify_func; + pb->notify_baton = notify_baton; + pb->notify = svn_repos_notify_create(svn_repos_notify_load_txn_start, pool); + pb->uuid_action = uuid_action; + pb->parent_dir = parent_dir; + pb->pool = pool; + pb->rev_map = apr_hash_make(pool); + pb->oldest_old_rev = SVN_INVALID_REVNUM; + pb->last_rev_mapped = SVN_INVALID_REVNUM; + pb->start_rev = start_rev; + pb->end_rev = end_rev; + + *callbacks = parser; + *parse_baton = pb; + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_repos_load_fs4(svn_repos_t *repos, + svn_stream_t *dumpstream, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + enum svn_repos_load_uuid uuid_action, + const char *parent_dir, + svn_boolean_t use_pre_commit_hook, + svn_boolean_t use_post_commit_hook, + svn_boolean_t validate_props, + svn_repos_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + const svn_repos_parse_fns3_t *parser; + void *parse_baton; + struct parse_baton *pb; + + /* This is really simple. */ + + SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton, + repos, + start_rev, end_rev, + TRUE, /* look for copyfrom revs */ + validate_props, + uuid_action, + parent_dir, + notify_func, + notify_baton, + pool)); + + /* Heh. We know this is a parse_baton. This file made it. So + cast away, and set our hook booleans. */ + pb = parse_baton; + pb->use_pre_commit_hook = use_pre_commit_hook; + pb->use_post_commit_hook = use_post_commit_hook; + + return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE, + cancel_func, cancel_baton, pool); +} |