1 /* load-fs-vtable.c --- dumpstream loader vtable for committing into a
2 * Subversion filesystem.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
25 #include "svn_private_config.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
30 #include "svn_repos.h"
31 #include "svn_string.h"
32 #include "svn_props.h"
34 #include "svn_private_config.h"
35 #include "svn_mergeinfo.h"
36 #include "svn_checksum.h"
37 #include "svn_subst.h"
38 #include "svn_ctype.h"
39 #include "svn_dirent_uri.h"
43 #include "private/svn_repos_private.h"
44 #include "private/svn_fspath.h"
45 #include "private/svn_dep_compat.h"
46 #include "private/svn_mergeinfo_private.h"
48 /*----------------------------------------------------------------------*/
50 /** Batons used herein **/
57 svn_boolean_t use_history;
58 svn_boolean_t validate_props;
59 svn_boolean_t use_pre_commit_hook;
60 svn_boolean_t use_post_commit_hook;
61 enum svn_repos_load_uuid uuid_action;
62 const char *parent_dir; /* repository relpath, or NULL */
63 svn_repos_notify_func_t notify_func;
65 apr_pool_t *notify_pool; /* scratch pool for notifications */
68 /* Start and end (inclusive) of revision range we'll pay attention
69 to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
71 svn_revnum_t start_rev;
74 /* A hash mapping copy-from revisions and mergeinfo range revisions
75 (svn_revnum_t *) in the dump stream to their corresponding revisions
76 (svn_revnum_t *) in the loaded repository. The hash and its
77 contents are allocated in POOL. */
78 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
79 ### for discussion about improving the memory costs of this mapping. */
82 /* The most recent (youngest) revision from the dump stream mapped in
83 REV_MAP. If no revisions have been mapped yet, this is set to
84 SVN_INVALID_REVNUM. */
85 svn_revnum_t last_rev_mapped;
87 /* The oldest old revision loaded from the dump stream. If no revisions
88 have been loaded yet, this is set to SVN_INVALID_REVNUM. */
89 svn_revnum_t oldest_old_rev;
96 svn_fs_root_t *txn_root;
98 const svn_string_t *datestamp;
100 apr_int32_t rev_offset;
101 svn_boolean_t skipped;
103 struct parse_baton *pb;
110 svn_node_kind_t kind;
111 enum svn_node_action action;
112 svn_checksum_t *base_checksum; /* null, if not available */
113 svn_checksum_t *result_checksum; /* null, if not available */
114 svn_checksum_t *copy_source_checksum; /* null, if not available */
116 svn_revnum_t copyfrom_rev;
117 const char *copyfrom_path;
119 struct revision_baton *rb;
124 /*----------------------------------------------------------------------*/
126 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
127 anything added to the hash is allocated in the hash's pool. */
129 set_revision_mapping(apr_hash_t *rev_map,
130 svn_revnum_t from_rev,
133 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
134 sizeof(svn_revnum_t) * 2);
135 mapped_revs[0] = from_rev;
136 mapped_revs[1] = to_rev;
137 apr_hash_set(rev_map, mapped_revs,
138 sizeof(svn_revnum_t), mapped_revs + 1);
141 /* Return the revision to which FROM_REV maps in REV_MAP, or
142 SVN_INVALID_REVNUM if no such mapping exists. */
144 get_revision_mapping(apr_hash_t *rev_map,
145 svn_revnum_t from_rev)
147 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
149 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
153 /* Change revision property NAME to VALUE for REVISION in REPOS. If
154 VALIDATE_PROPS is set, use functions which perform validation of
155 the property value. Otherwise, bypass those checks. */
157 change_rev_prop(svn_repos_t *repos,
158 svn_revnum_t revision,
160 const svn_string_t *value,
161 svn_boolean_t validate_props,
165 return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
166 NULL, value, FALSE, FALSE,
169 return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
173 /* Change property NAME to VALUE for PATH in TXN_ROOT. If
174 VALIDATE_PROPS is set, use functions which perform validation of
175 the property value. Otherwise, bypass those checks. */
177 change_node_prop(svn_fs_root_t *txn_root,
180 const svn_string_t *value,
181 svn_boolean_t validate_props,
185 return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
187 return svn_fs_change_node_prop(txn_root, path, name, value, pool);
190 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
191 return it in *MERGEINFO_VAL. */
192 /* ### FIXME: Consider somehow sharing code with
193 ### svnrdump/load_editor.c:prefix_mergeinfo_paths() */
195 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
196 const svn_string_t *mergeinfo_orig,
197 const char *parent_dir,
200 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
201 apr_hash_index_t *hi;
204 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
205 prefixed_mergeinfo = apr_hash_make(pool);
206 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
209 const char *path, *merge_source;
211 apr_hash_this(hi, &key, NULL, &rangelist);
212 merge_source = svn_relpath_canonicalize(key, pool);
214 /* The svn:mergeinfo property syntax demands a repos abspath */
215 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
218 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
220 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
224 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
225 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
226 (allocated from POOL). */
227 /* ### FIXME: Consider somehow sharing code with
228 ### svnrdump/load_editor.c:renumber_mergeinfo_revs() */
230 renumber_mergeinfo_revs(svn_string_t **final_val,
231 const svn_string_t *initial_val,
232 struct revision_baton *rb,
235 apr_pool_t *subpool = svn_pool_create(pool);
236 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
237 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
238 apr_hash_index_t *hi;
240 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
243 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
244 Remove mergeinfo older than the oldest revision in the dump stream
245 and adjust its revisions by the difference between the head rev of
246 the target repository and the current dump stream rev. */
247 if (rb->pb->oldest_old_rev > 1)
249 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
250 &predates_stream_mergeinfo, mergeinfo,
251 rb->pb->oldest_old_rev - 1, 0,
252 TRUE, subpool, subpool));
253 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
254 &mergeinfo, mergeinfo,
255 rb->pb->oldest_old_rev - 1, 0,
256 FALSE, subpool, subpool));
257 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
258 &predates_stream_mergeinfo, predates_stream_mergeinfo,
259 -rb->rev_offset, subpool, subpool));
263 predates_stream_mergeinfo = NULL;
266 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
268 const char *merge_source;
269 svn_rangelist_t *rangelist;
270 struct parse_baton *pb = rb->pb;
275 apr_hash_this(hi, &key, NULL, &val);
279 /* Possibly renumber revisions in merge source's rangelist. */
280 for (i = 0; i < rangelist->nelts; i++)
282 svn_revnum_t rev_from_map;
283 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
284 svn_merge_range_t *);
285 rev_from_map = get_revision_mapping(pb->rev_map, range->start);
286 if (SVN_IS_VALID_REVNUM(rev_from_map))
288 range->start = rev_from_map;
290 else if (range->start == pb->oldest_old_rev - 1)
292 /* Since the start revision of svn_merge_range_t are not
293 inclusive there is one possible valid start revision that
294 won't be found in the PB->REV_MAP mapping of load stream
295 revsions to loaded revisions: The revision immediately
296 preceeding the oldest revision from the load stream.
297 This is a valid revision for mergeinfo, but not a valid
298 copy from revision (which PB->REV_MAP also maps for) so it
299 will never be in the mapping.
301 If that is what we have here, then find the mapping for the
302 oldest rev from the load stream and subtract 1 to get the
303 renumbered, non-inclusive, start revision. */
304 rev_from_map = get_revision_mapping(pb->rev_map,
306 if (SVN_IS_VALID_REVNUM(rev_from_map))
307 range->start = rev_from_map - 1;
311 /* If we can't remap the start revision then don't even bother
312 trying to remap the end revision. It's possible we might
313 actually succeed at the latter, which can result in invalid
314 mergeinfo with a start rev > end rev. If that gets into the
315 repository then a world of bustage breaks loose anytime that
316 bogus mergeinfo is parsed. See
317 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
322 rev_from_map = get_revision_mapping(pb->rev_map, range->end);
323 if (SVN_IS_VALID_REVNUM(rev_from_map))
324 range->end = rev_from_map;
326 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
329 if (predates_stream_mergeinfo)
330 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
333 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
335 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
336 svn_pool_destroy(subpool);
341 /*----------------------------------------------------------------------*/
343 /** vtable for doing commits to a fs **/
347 make_node_baton(struct node_baton **node_baton_p,
349 struct revision_baton *rb,
352 struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
355 /* Start with sensible defaults. */
358 nb->kind = svn_node_unknown;
360 /* Then add info from the headers. */
361 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
363 val = svn_relpath_canonicalize(val, pool);
364 if (rb->pb->parent_dir)
365 nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
370 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
372 if (! strcmp(val, "file"))
373 nb->kind = svn_node_file;
374 else if (! strcmp(val, "dir"))
375 nb->kind = svn_node_dir;
378 nb->action = (enum svn_node_action)(-1); /* an invalid action code */
379 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
381 if (! strcmp(val, "change"))
382 nb->action = svn_node_action_change;
383 else if (! strcmp(val, "add"))
384 nb->action = svn_node_action_add;
385 else if (! strcmp(val, "delete"))
386 nb->action = svn_node_action_delete;
387 else if (! strcmp(val, "replace"))
388 nb->action = svn_node_action_replace;
391 nb->copyfrom_rev = SVN_INVALID_REVNUM;
392 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
394 nb->copyfrom_rev = SVN_STR_TO_REV(val);
396 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
398 val = svn_relpath_canonicalize(val, pool);
399 if (rb->pb->parent_dir)
400 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
402 nb->copyfrom_path = val;
405 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
407 SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
411 if ((val = svn_hash_gets(headers,
412 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
414 SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
418 if ((val = svn_hash_gets(headers,
419 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
421 SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
422 svn_checksum_md5, val, pool));
425 /* What's cool about this dump format is that the parser just
426 ignores any unrecognized headers. :-) */
432 static struct revision_baton *
433 make_revision_baton(apr_hash_t *headers,
434 struct parse_baton *pb,
437 struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
442 rb->rev = SVN_INVALID_REVNUM;
444 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
446 rb->rev = SVN_STR_TO_REV(val);
448 /* If we're filtering revisions, is this one we'll skip? */
449 rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
450 && ((rb->rev < pb->start_rev) ||
451 (rb->rev > pb->end_rev)));
459 new_revision_record(void **revision_baton,
464 struct parse_baton *pb = parse_baton;
465 struct revision_baton *rb;
466 svn_revnum_t head_rev;
468 rb = make_revision_baton(headers, pb, pool);
470 /* ### If we're filtering revisions, and this is one we've skipped,
471 ### and we've skipped it because it has a revision number younger
472 ### than the youngest in our acceptable range, then should we
473 ### just bail out here? */
475 if (rb->skipped && (rb->rev > pb->end_rev))
476 return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
477 _("Finished processing acceptable load "
481 SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
483 /* FIXME: This is a lame fallback loading multiple segments of dump in
484 several separate operations. It is highly susceptible to race conditions.
485 Calculate the revision 'offset' for finding copyfrom sources.
486 It might be positive or negative. */
487 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
489 if ((rb->rev > 0) && (! rb->skipped))
491 /* Create a new fs txn. */
492 SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool));
493 SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
497 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
498 svn_repos_notify_t *notify = svn_repos_notify_create(
499 svn_repos_notify_load_txn_start,
502 notify->old_revision = rb->rev;
503 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
504 svn_pool_clear(pb->notify_pool);
507 /* Stash the oldest "old" revision committed from the load stream. */
508 if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev))
509 pb->oldest_old_rev = rb->rev;
512 /* If we're skipping this revision, try to notify someone. */
513 if (rb->skipped && pb->notify_func)
515 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
516 svn_repos_notify_t *notify = svn_repos_notify_create(
517 svn_repos_notify_load_skipped_rev,
520 notify->old_revision = rb->rev;
521 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
522 svn_pool_clear(pb->notify_pool);
525 /* If we're parsing revision 0, only the revision are (possibly)
526 interesting to us: when loading the stream into an empty
527 filesystem, then we want new filesystem's revision 0 to have the
528 same props. Otherwise, we just ignore revision 0 in the stream. */
530 *revision_baton = rb;
536 /* Factorized helper func for new_node_record() */
538 maybe_add_with_history(struct node_baton *nb,
539 struct revision_baton *rb,
542 struct parse_baton *pb = rb->pb;
544 if ((nb->copyfrom_path == NULL) || (! pb->use_history))
546 /* Add empty file or dir, without history. */
547 if (nb->kind == svn_node_file)
548 SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
550 else if (nb->kind == svn_node_dir)
551 SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
555 /* Hunt down the source revision in this fs. */
556 svn_fs_root_t *copy_root;
557 svn_revnum_t copyfrom_rev;
559 /* Try to find the copyfrom revision in the revision map;
560 failing that, fall back to the revision offset approach. */
561 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
562 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
563 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
565 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
566 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
567 _("Relative source revision %ld is not"
568 " available in current repository"),
571 SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool));
573 if (nb->copy_source_checksum)
575 svn_checksum_t *checksum;
576 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
577 nb->copyfrom_path, TRUE, pool));
578 if (!svn_checksum_match(nb->copy_source_checksum, checksum))
579 return svn_checksum_mismatch_err(nb->copy_source_checksum,
581 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
582 "to '%s' in rev based on r%ld"),
583 nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
586 SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
587 rb->txn_root, nb->path, pool));
591 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
592 svn_repos_notify_t *notify = svn_repos_notify_create(
593 svn_repos_notify_load_copied_node,
596 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
597 svn_pool_clear(pb->notify_pool);
605 magic_header_record(int version,
613 uuid_record(const char *uuid,
617 struct parse_baton *pb = parse_baton;
618 svn_revnum_t youngest_rev;
620 if (pb->uuid_action == svn_repos_load_uuid_ignore)
623 if (pb->uuid_action != svn_repos_load_uuid_force)
625 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
626 if (youngest_rev != 0)
630 return svn_fs_set_uuid(pb->fs, uuid, pool);
634 new_node_record(void **node_baton,
636 void *revision_baton,
639 struct revision_baton *rb = revision_baton;
640 struct parse_baton *pb = rb->pb;
641 struct node_baton *nb;
644 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
645 _("Malformed dumpstream: "
646 "Revision 0 must not contain node records"));
648 SVN_ERR(make_node_baton(&nb, headers, rb, pool));
650 /* If we're skipping this revision, we're done here. */
657 /* Make sure we have an action we recognize. */
658 if (nb->action < svn_node_action_change
659 || nb->action > svn_node_action_replace)
660 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
661 _("Unrecognized node-action on node '%s'"),
666 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
667 svn_repos_notify_t *notify = svn_repos_notify_create(
668 svn_repos_notify_load_node_start,
671 notify->path = nb->path;
672 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
673 svn_pool_clear(pb->notify_pool);
678 case svn_node_action_change:
681 case svn_node_action_delete:
682 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
685 case svn_node_action_add:
686 SVN_ERR(maybe_add_with_history(nb, rb, pool));
689 case svn_node_action_replace:
690 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
691 SVN_ERR(maybe_add_with_history(nb, rb, pool));
700 set_revision_property(void *baton,
702 const svn_string_t *value)
704 struct revision_baton *rb = baton;
706 /* If we're skipping this revision, we're done here. */
712 if (rb->pb->validate_props)
713 SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool));
715 SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool));
717 /* Remember any datestamp that passes through! (See comment in
718 close_revision() below.) */
719 if (! strcmp(name, SVN_PROP_REVISION_DATE))
720 rb->datestamp = svn_string_dup(value, rb->pool);
722 else if (rb->rev == 0)
724 /* Special case: set revision 0 properties when loading into an
725 'empty' filesystem. */
726 struct parse_baton *pb = rb->pb;
727 svn_revnum_t youngest_rev;
729 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
731 if (youngest_rev == 0)
732 SVN_ERR(change_rev_prop(pb->repos, 0, name, value,
733 pb->validate_props, rb->pool));
741 * - normalize line endings (if all CRLF, change to LF; but error if mixed);
742 * - adjust revision numbers (see renumber_mergeinfo_revs());
743 * - adjust paths (see prefix_mergeinfo_paths()).
746 adjust_mergeinfo_property(struct revision_baton *rb,
747 svn_string_t **new_value_p,
748 const svn_string_t *old_value,
749 apr_pool_t *result_pool)
751 struct parse_baton *pb = rb->pb;
752 svn_string_t prop_val = *old_value;
754 /* Tolerate mergeinfo with "\r\n" line endings because some
755 dumpstream sources might contain as much. If so normalize
756 the line endings to '\n' and make a notification to
757 PARSE_BATON->FEEDBACK_STREAM that we have made this
759 if (strstr(prop_val.data, "\r"))
761 const char *prop_eol_normalized;
763 SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
764 &prop_eol_normalized,
765 "\n", /* translate to LF */
766 FALSE, /* no repair */
767 NULL, /* no keywords */
768 FALSE, /* no expansion */
770 prop_val.data = prop_eol_normalized;
771 prop_val.len = strlen(prop_eol_normalized);
775 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
776 svn_repos_notify_t *notify
777 = svn_repos_notify_create(
778 svn_repos_notify_load_normalized_mergeinfo,
781 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
782 svn_pool_clear(pb->notify_pool);
786 /* Renumber mergeinfo as appropriate. */
787 SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val, rb,
791 /* Prefix the merge source paths with PB->parent_dir. */
792 /* ASSUMPTION: All source paths are included in the dump stream. */
793 SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
794 pb->parent_dir, result_pool));
802 set_node_property(void *baton,
804 const svn_string_t *value)
806 struct node_baton *nb = baton;
807 struct revision_baton *rb = nb->rb;
808 struct parse_baton *pb = rb->pb;
810 /* If we're skipping this revision, we're done here. */
814 /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
815 property has an ill-formed value, then we must not fail to load
816 the repository (at least if it's a simple load with no revision
817 offset adjustments, path changes, etc.) so just warn and leave it
819 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
821 svn_string_t *new_value;
824 err = adjust_mergeinfo_property(rb, &new_value, value, nb->pool);
827 if (pb->validate_props)
829 return svn_error_quick_wrap(
831 _("Invalid svn:mergeinfo value"));
835 svn_repos_notify_t *notify
836 = svn_repos_notify_create(svn_repos_notify_warning,
839 notify->warning = svn_repos__notify_warning_invalid_mergeinfo;
840 notify->warning_str = _("Invalid svn:mergeinfo value; "
841 "leaving unchanged");
842 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
843 svn_pool_clear(pb->notify_pool);
845 svn_error_clear(err);
853 return change_node_prop(rb->txn_root, nb->path, name, value,
854 pb->validate_props, nb->pool);
859 delete_node_property(void *baton,
862 struct node_baton *nb = baton;
863 struct revision_baton *rb = nb->rb;
865 /* If we're skipping this revision, we're done here. */
869 return change_node_prop(rb->txn_root, nb->path, name, NULL,
870 rb->pb->validate_props, nb->pool);
875 remove_node_props(void *baton)
877 struct node_baton *nb = baton;
878 struct revision_baton *rb = nb->rb;
879 apr_hash_t *proplist;
880 apr_hash_index_t *hi;
882 /* If we're skipping this revision, we're done here. */
886 SVN_ERR(svn_fs_node_proplist(&proplist,
887 rb->txn_root, nb->path, nb->pool));
889 for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
893 apr_hash_this(hi, &key, NULL, NULL);
894 SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
895 rb->pb->validate_props, nb->pool));
903 apply_textdelta(svn_txdelta_window_handler_t *handler,
904 void **handler_baton,
907 struct node_baton *nb = node_baton;
908 struct revision_baton *rb = nb->rb;
910 /* If we're skipping this revision, we're done here. */
917 return svn_fs_apply_textdelta(handler, handler_baton,
918 rb->txn_root, nb->path,
919 svn_checksum_to_cstring(nb->base_checksum,
921 svn_checksum_to_cstring(nb->result_checksum,
928 set_fulltext(svn_stream_t **stream,
931 struct node_baton *nb = node_baton;
932 struct revision_baton *rb = nb->rb;
934 /* If we're skipping this revision, we're done here. */
941 return svn_fs_apply_text(stream,
942 rb->txn_root, nb->path,
943 svn_checksum_to_cstring(nb->result_checksum,
950 close_node(void *baton)
952 struct node_baton *nb = baton;
953 struct revision_baton *rb = nb->rb;
954 struct parse_baton *pb = rb->pb;
956 /* If we're skipping this revision, we're done here. */
962 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
963 svn_repos_notify_t *notify = svn_repos_notify_create(
964 svn_repos_notify_load_node_done,
967 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
968 svn_pool_clear(pb->notify_pool);
976 close_revision(void *baton)
978 struct revision_baton *rb = baton;
979 struct parse_baton *pb = rb->pb;
980 const char *conflict_msg = NULL;
981 svn_revnum_t committed_rev;
983 const char *txn_name = NULL;
984 apr_hash_t *hooks_env;
986 /* If we're skipping this revision or it has an invalid revision
987 number, we're done here. */
988 if (rb->skipped || (rb->rev <= 0))
991 /* Get the txn name and hooks environment if they will be needed. */
992 if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
994 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
995 rb->pool, rb->pool));
997 err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
1000 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1001 return svn_error_trace(err);
1005 /* Run the pre-commit hook, if so commanded. */
1006 if (pb->use_pre_commit_hook)
1008 err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
1009 txn_name, rb->pool);
1012 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1013 return svn_error_trace(err);
1018 err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
1019 if (SVN_IS_VALID_REVNUM(committed_rev))
1023 /* ### Log any error, but better yet is to rev
1024 ### close_revision()'s API to allow both committed_rev and err
1025 ### to be returned, see #3768. */
1026 svn_error_clear(err);
1031 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1033 return svn_error_quick_wrap(err, conflict_msg);
1035 return svn_error_trace(err);
1038 /* Run post-commit hook, if so commanded. */
1039 if (pb->use_post_commit_hook)
1041 if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
1042 committed_rev, txn_name,
1044 return svn_error_create
1045 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1046 _("Commit succeeded, but post-commit hook failed"));
1049 /* After a successful commit, must record the dump-rev -> in-repos-rev
1050 mapping, so that copyfrom instructions in the dump file can look up the
1051 correct repository revision to copy from. */
1052 set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
1054 /* If the incoming dump stream has non-contiguous revisions (e.g. from
1055 using svndumpfilter --drop-empty-revs without --renumber-revs) then
1056 we must account for the missing gaps in PB->REV_MAP. Otherwise we
1057 might not be able to map all mergeinfo source revisions to the correct
1058 revisions in the target repos. */
1059 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
1060 && (rb->rev != pb->last_rev_mapped + 1))
1064 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
1066 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1070 /* Update our "last revision mapped". */
1071 pb->last_rev_mapped = rb->rev;
1073 /* Deltify the predecessors of paths changed in this revision. */
1074 SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1076 /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
1077 current clock-time. We don't want that, we want to preserve
1078 history exactly. Good thing revision props aren't versioned!
1079 Note that if rb->datestamp is NULL, that's fine -- if the dump
1080 data doesn't carry a datestamp, we want to preserve that fact in
1082 SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
1083 rb->datestamp, pb->validate_props, rb->pool));
1085 if (pb->notify_func)
1087 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1088 svn_repos_notify_t *notify = svn_repos_notify_create(
1089 svn_repos_notify_load_txn_committed,
1092 notify->new_revision = committed_rev;
1093 notify->old_revision = ((committed_rev == rb->rev)
1094 ? SVN_INVALID_REVNUM
1096 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1097 svn_pool_clear(pb->notify_pool);
1100 return SVN_NO_ERROR;
1104 /*----------------------------------------------------------------------*/
1106 /** The public routines **/
1110 svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
1113 svn_revnum_t start_rev,
1114 svn_revnum_t end_rev,
1115 svn_boolean_t use_history,
1116 svn_boolean_t validate_props,
1117 enum svn_repos_load_uuid uuid_action,
1118 const char *parent_dir,
1119 svn_repos_notify_func_t notify_func,
1123 svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1124 struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1127 parent_dir = svn_relpath_canonicalize(parent_dir, pool);
1129 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1130 SVN_IS_VALID_REVNUM(end_rev))
1131 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1132 (! SVN_IS_VALID_REVNUM(end_rev))));
1133 if (SVN_IS_VALID_REVNUM(start_rev))
1134 SVN_ERR_ASSERT(start_rev <= end_rev);
1136 parser->magic_header_record = magic_header_record;
1137 parser->uuid_record = uuid_record;
1138 parser->new_revision_record = new_revision_record;
1139 parser->new_node_record = new_node_record;
1140 parser->set_revision_property = set_revision_property;
1141 parser->set_node_property = set_node_property;
1142 parser->remove_node_props = remove_node_props;
1143 parser->set_fulltext = set_fulltext;
1144 parser->close_node = close_node;
1145 parser->close_revision = close_revision;
1146 parser->delete_node_property = delete_node_property;
1147 parser->apply_textdelta = apply_textdelta;
1150 pb->fs = svn_repos_fs(repos);
1151 pb->use_history = use_history;
1152 pb->validate_props = validate_props;
1153 pb->notify_func = notify_func;
1154 pb->notify_baton = notify_baton;
1155 pb->uuid_action = uuid_action;
1156 pb->parent_dir = parent_dir;
1158 pb->notify_pool = svn_pool_create(pool);
1159 pb->rev_map = apr_hash_make(pool);
1160 pb->oldest_old_rev = SVN_INVALID_REVNUM;
1161 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1162 pb->start_rev = start_rev;
1163 pb->end_rev = end_rev;
1165 *callbacks = parser;
1167 return SVN_NO_ERROR;
1173 svn_repos_load_fs4(svn_repos_t *repos,
1174 svn_stream_t *dumpstream,
1175 svn_revnum_t start_rev,
1176 svn_revnum_t end_rev,
1177 enum svn_repos_load_uuid uuid_action,
1178 const char *parent_dir,
1179 svn_boolean_t use_pre_commit_hook,
1180 svn_boolean_t use_post_commit_hook,
1181 svn_boolean_t validate_props,
1182 svn_repos_notify_func_t notify_func,
1184 svn_cancel_func_t cancel_func,
1188 const svn_repos_parse_fns3_t *parser;
1190 struct parse_baton *pb;
1192 /* This is really simple. */
1194 SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
1197 TRUE, /* look for copyfrom revs */
1205 /* Heh. We know this is a parse_baton. This file made it. So
1206 cast away, and set our hook booleans. */
1208 pb->use_pre_commit_hook = use_pre_commit_hook;
1209 pb->use_post_commit_hook = use_post_commit_hook;
1211 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1212 cancel_func, cancel_baton, pool);