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_mergeinfo.h"
35 #include "svn_checksum.h"
36 #include "svn_subst.h"
37 #include "svn_dirent_uri.h"
41 #include "private/svn_fspath.h"
42 #include "private/svn_dep_compat.h"
43 #include "private/svn_mergeinfo_private.h"
44 #include "private/svn_repos_private.h"
46 /*----------------------------------------------------------------------*/
48 /** Batons used herein **/
55 svn_boolean_t use_history;
56 svn_boolean_t validate_props;
57 svn_boolean_t ignore_dates;
58 svn_boolean_t normalize_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 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_dumpstream_rev;
94 /* rev num from dump file */
97 svn_fs_root_t *txn_root;
99 const svn_string_t *datestamp;
101 /* (rev num from dump file) minus (rev num to be committed) */
102 apr_int32_t rev_offset;
103 svn_boolean_t skipped;
105 /* Array of svn_prop_t with revision properties. */
106 apr_array_header_t *revprops;
108 struct parse_baton *pb;
115 svn_node_kind_t kind;
116 enum svn_node_action action;
117 svn_checksum_t *base_checksum; /* null, if not available */
118 svn_checksum_t *result_checksum; /* null, if not available */
119 svn_checksum_t *copy_source_checksum; /* null, if not available */
121 svn_revnum_t copyfrom_rev;
122 const char *copyfrom_path;
124 struct revision_baton *rb;
129 /*----------------------------------------------------------------------*/
131 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
132 anything added to the hash is allocated in the hash's pool. */
134 set_revision_mapping(apr_hash_t *rev_map,
135 svn_revnum_t from_rev,
138 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
139 sizeof(svn_revnum_t) * 2);
140 mapped_revs[0] = from_rev;
141 mapped_revs[1] = to_rev;
142 apr_hash_set(rev_map, mapped_revs,
143 sizeof(svn_revnum_t), mapped_revs + 1);
146 /* Return the revision to which FROM_REV maps in REV_MAP, or
147 SVN_INVALID_REVNUM if no such mapping exists. */
149 get_revision_mapping(apr_hash_t *rev_map,
150 svn_revnum_t from_rev)
152 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
154 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
158 /* Change revision property NAME to VALUE for REVISION in REPOS. If
159 VALIDATE_PROPS is set, use functions which perform validation of
160 the property value. Otherwise, bypass those checks. */
162 change_rev_prop(svn_repos_t *repos,
163 svn_revnum_t revision,
165 const svn_string_t *value,
166 svn_boolean_t validate_props,
167 svn_boolean_t normalize_props,
171 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
174 return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
175 NULL, value, FALSE, FALSE,
178 return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
182 /* Change property NAME to VALUE for PATH in TXN_ROOT. If
183 VALIDATE_PROPS is set, use functions which perform validation of
184 the property value. Otherwise, bypass those checks. */
186 change_node_prop(svn_fs_root_t *txn_root,
189 const svn_string_t *value,
190 svn_boolean_t validate_props,
194 return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
196 return svn_fs_change_node_prop(txn_root, path, name, value, pool);
199 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
200 return it in *MERGEINFO_VAL. */
202 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
203 const svn_string_t *mergeinfo_orig,
204 const char *parent_dir,
207 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
208 apr_hash_index_t *hi;
210 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
211 prefixed_mergeinfo = apr_hash_make(pool);
212 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
214 const char *merge_source = apr_hash_this_key(hi);
215 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
218 merge_source = svn_relpath_canonicalize(merge_source, pool);
220 /* The svn:mergeinfo property syntax demands a repos abspath */
221 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
224 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
226 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
230 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
231 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
232 (allocated from POOL).
234 Adjust any mergeinfo revisions not older than OLDEST_DUMPSTREAM_REV by
235 using REV_MAP which maps (svn_revnum_t) old rev to (svn_revnum_t) new rev.
237 Adjust any mergeinfo revisions older than OLDEST_DUMPSTREAM_REV by
238 (-OLDER_REVS_OFFSET), dropping any that become <= 0.
241 renumber_mergeinfo_revs(svn_string_t **final_val,
242 const svn_string_t *initial_val,
244 svn_revnum_t oldest_dumpstream_rev,
245 apr_int32_t older_revs_offset,
248 apr_pool_t *subpool = svn_pool_create(pool);
249 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
250 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
251 apr_hash_index_t *hi;
253 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
256 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
257 Remove mergeinfo older than the oldest revision in the dump stream
258 and adjust its revisions by the difference between the head rev of
259 the target repository and the current dump stream rev. */
260 if (oldest_dumpstream_rev > 1)
262 /* predates_stream_mergeinfo := mergeinfo that refers to revs before
263 oldest_dumpstream_rev */
264 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
265 &predates_stream_mergeinfo, mergeinfo,
266 oldest_dumpstream_rev - 1, 0,
267 TRUE, subpool, subpool));
268 /* mergeinfo := mergeinfo that refers to revs >= oldest_dumpstream_rev */
269 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
270 &mergeinfo, mergeinfo,
271 oldest_dumpstream_rev - 1, 0,
272 FALSE, subpool, subpool));
273 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
274 &predates_stream_mergeinfo, predates_stream_mergeinfo,
275 -older_revs_offset, subpool, subpool));
279 predates_stream_mergeinfo = NULL;
282 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
284 const char *merge_source = apr_hash_this_key(hi);
285 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
288 /* Possibly renumber revisions in merge source's rangelist. */
289 for (i = 0; i < rangelist->nelts; i++)
291 svn_revnum_t rev_from_map;
292 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
293 svn_merge_range_t *);
294 rev_from_map = get_revision_mapping(rev_map, range->start);
295 if (SVN_IS_VALID_REVNUM(rev_from_map))
297 range->start = rev_from_map;
299 else if (range->start == oldest_dumpstream_rev - 1)
301 /* Since the start revision of svn_merge_range_t are not
302 inclusive there is one possible valid start revision that
303 won't be found in the REV_MAP mapping of load stream
304 revsions to loaded revisions: The revision immediately
305 preceding the oldest revision from the load stream.
306 This is a valid revision for mergeinfo, but not a valid
307 copy from revision (which REV_MAP also maps for) so it
308 will never be in the mapping.
310 If that is what we have here, then find the mapping for the
311 oldest rev from the load stream and subtract 1 to get the
312 renumbered, non-inclusive, start revision. */
313 rev_from_map = get_revision_mapping(rev_map,
314 oldest_dumpstream_rev);
315 if (SVN_IS_VALID_REVNUM(rev_from_map))
316 range->start = rev_from_map - 1;
320 /* If we can't remap the start revision then don't even bother
321 trying to remap the end revision. It's possible we might
322 actually succeed at the latter, which can result in invalid
323 mergeinfo with a start rev > end rev. If that gets into the
324 repository then a world of bustage breaks loose anytime that
325 bogus mergeinfo is parsed. See
326 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
331 rev_from_map = get_revision_mapping(rev_map, range->end);
332 if (SVN_IS_VALID_REVNUM(rev_from_map))
333 range->end = rev_from_map;
335 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
338 if (predates_stream_mergeinfo)
340 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
344 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
346 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
347 svn_pool_destroy(subpool);
352 /*----------------------------------------------------------------------*/
354 /** vtable for doing commits to a fs **/
357 /* Make a node baton, parsing the relevant HEADERS.
359 * If RB->pb->parent_dir:
360 * prefix it to NB->path
361 * prefix it to NB->copyfrom_path (if present)
364 make_node_baton(struct node_baton **node_baton_p,
366 struct revision_baton *rb,
369 struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
372 /* Start with sensible defaults. */
375 nb->kind = svn_node_unknown;
377 /* Then add info from the headers. */
378 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
380 val = svn_relpath_canonicalize(val, pool);
381 if (rb->pb->parent_dir)
382 nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
387 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
389 if (! strcmp(val, "file"))
390 nb->kind = svn_node_file;
391 else if (! strcmp(val, "dir"))
392 nb->kind = svn_node_dir;
395 nb->action = (enum svn_node_action)(-1); /* an invalid action code */
396 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
398 if (! strcmp(val, "change"))
399 nb->action = svn_node_action_change;
400 else if (! strcmp(val, "add"))
401 nb->action = svn_node_action_add;
402 else if (! strcmp(val, "delete"))
403 nb->action = svn_node_action_delete;
404 else if (! strcmp(val, "replace"))
405 nb->action = svn_node_action_replace;
408 nb->copyfrom_rev = SVN_INVALID_REVNUM;
409 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
411 nb->copyfrom_rev = SVN_STR_TO_REV(val);
413 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
415 val = svn_relpath_canonicalize(val, pool);
416 if (rb->pb->parent_dir)
417 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
419 nb->copyfrom_path = val;
422 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
424 SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
428 if ((val = svn_hash_gets(headers,
429 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
431 SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
435 if ((val = svn_hash_gets(headers,
436 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
438 SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
439 svn_checksum_md5, val, pool));
442 /* What's cool about this dump format is that the parser just
443 ignores any unrecognized headers. :-) */
449 /* Make a revision baton, parsing the relevant HEADERS.
451 * Set RB->skipped iff the revision number is outside the range given in PB.
453 static struct revision_baton *
454 make_revision_baton(apr_hash_t *headers,
455 struct parse_baton *pb,
458 struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
463 rb->rev = SVN_INVALID_REVNUM;
464 rb->revprops = apr_array_make(rb->pool, 8, sizeof(svn_prop_t));
466 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
468 rb->rev = SVN_STR_TO_REV(val);
470 /* If we're filtering revisions, is this one we'll skip? */
471 rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
472 && ((rb->rev < pb->start_rev) ||
473 (rb->rev > pb->end_rev)));
481 new_revision_record(void **revision_baton,
486 struct parse_baton *pb = parse_baton;
487 struct revision_baton *rb;
488 svn_revnum_t head_rev;
490 rb = make_revision_baton(headers, pb, pool);
492 /* ### If we're filtering revisions, and this is one we've skipped,
493 ### and we've skipped it because it has a revision number younger
494 ### than the youngest in our acceptable range, then should we
495 ### just bail out here? */
497 if (rb->skipped && (rb->rev > pb->end_rev))
498 return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
499 _("Finished processing acceptable load "
503 SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
505 /* FIXME: This is a lame fallback loading multiple segments of dump in
506 several separate operations. It is highly susceptible to race conditions.
507 Calculate the revision 'offset' for finding copyfrom sources.
508 It might be positive or negative. */
509 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
511 if ((rb->rev > 0) && (! rb->skipped))
513 /* Create a new fs txn. */
514 SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev,
515 SVN_FS_TXN_CLIENT_DATE, pool));
516 SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
520 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
521 svn_repos_notify_t *notify = svn_repos_notify_create(
522 svn_repos_notify_load_txn_start,
525 notify->old_revision = rb->rev;
526 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
527 svn_pool_clear(pb->notify_pool);
530 /* Stash the oldest "old" revision committed from the load stream. */
531 if (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))
532 pb->oldest_dumpstream_rev = rb->rev;
535 /* If we're skipping this revision, try to notify someone. */
536 if (rb->skipped && pb->notify_func)
538 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
539 svn_repos_notify_t *notify = svn_repos_notify_create(
540 svn_repos_notify_load_skipped_rev,
543 notify->old_revision = rb->rev;
544 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
545 svn_pool_clear(pb->notify_pool);
548 /* If we're parsing revision 0, only the revision props are (possibly)
549 interesting to us: when loading the stream into an empty
550 filesystem, then we want new filesystem's revision 0 to have the
551 same props. Otherwise, we just ignore revision 0 in the stream. */
553 *revision_baton = rb;
559 /* Perform a copy or a plain add.
561 * For a copy, also adjust the copy-from rev, check any copy-source checksum,
562 * and send a notification.
565 maybe_add_with_history(struct node_baton *nb,
566 struct revision_baton *rb,
569 struct parse_baton *pb = rb->pb;
571 if ((nb->copyfrom_path == NULL) || (! pb->use_history))
573 /* Add empty file or dir, without history. */
574 if (nb->kind == svn_node_file)
575 SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
577 else if (nb->kind == svn_node_dir)
578 SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
582 /* Hunt down the source revision in this fs. */
583 svn_fs_root_t *copy_root;
584 svn_revnum_t copyfrom_rev;
586 /* Try to find the copyfrom revision in the revision map;
587 failing that, fall back to the revision offset approach. */
588 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
589 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
590 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
592 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
593 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
594 _("Relative source revision %ld is not"
595 " available in current repository"),
598 SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool));
600 if (nb->copy_source_checksum)
602 svn_checksum_t *checksum;
603 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
604 nb->copyfrom_path, TRUE, pool));
605 if (!svn_checksum_match(nb->copy_source_checksum, checksum))
606 return svn_checksum_mismatch_err(nb->copy_source_checksum,
608 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
609 "to '%s' in rev based on r%ld"),
610 nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
613 SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
614 rb->txn_root, nb->path, pool));
618 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
619 svn_repos_notify_t *notify = svn_repos_notify_create(
620 svn_repos_notify_load_copied_node,
623 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
624 svn_pool_clear(pb->notify_pool);
632 uuid_record(const char *uuid,
636 struct parse_baton *pb = parse_baton;
637 svn_revnum_t youngest_rev;
639 if (pb->uuid_action == svn_repos_load_uuid_ignore)
642 if (pb->uuid_action != svn_repos_load_uuid_force)
644 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
645 if (youngest_rev != 0)
649 return svn_fs_set_uuid(pb->fs, uuid, pool);
653 new_node_record(void **node_baton,
655 void *revision_baton,
658 struct revision_baton *rb = revision_baton;
659 struct parse_baton *pb = rb->pb;
660 struct node_baton *nb;
663 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
664 _("Malformed dumpstream: "
665 "Revision 0 must not contain node records"));
667 SVN_ERR(make_node_baton(&nb, headers, rb, pool));
669 /* If we're skipping this revision, we're done here. */
676 /* Make sure we have an action we recognize. */
677 if (nb->action < svn_node_action_change
678 || nb->action > svn_node_action_replace)
679 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
680 _("Unrecognized node-action on node '%s'"),
685 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
686 svn_repos_notify_t *notify = svn_repos_notify_create(
687 svn_repos_notify_load_node_start,
690 notify->path = nb->path;
691 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
692 svn_pool_clear(pb->notify_pool);
697 case svn_node_action_change:
700 case svn_node_action_delete:
701 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
704 case svn_node_action_add:
705 SVN_ERR(maybe_add_with_history(nb, rb, pool));
708 case svn_node_action_replace:
709 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
710 SVN_ERR(maybe_add_with_history(nb, rb, pool));
719 set_revision_property(void *baton,
721 const svn_string_t *value)
723 struct revision_baton *rb = baton;
724 struct parse_baton *pb = rb->pb;
725 svn_boolean_t is_date = strcmp(name, SVN_PROP_REVISION_DATE) == 0;
728 /* If we're skipping this revision, we're done here. */
732 /* If we're ignoring dates, and this is one, we're done here. */
733 if (is_date && pb->ignore_dates)
736 /* Collect property changes to apply them in one FS call in
738 prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
739 prop->name = apr_pstrdup(rb->pool, name);
740 prop->value = svn_string_dup(value, rb->pool);
742 /* Remember any datestamp that passes through! (See comment in
743 close_revision() below.) */
745 rb->datestamp = svn_string_dup(value, rb->pool);
752 svn_repos__adjust_mergeinfo_property(svn_string_t **new_value_p,
753 const svn_string_t *old_value,
754 const char *parent_dir,
756 svn_revnum_t oldest_dumpstream_rev,
757 apr_int32_t older_revs_offset,
758 svn_repos_notify_func_t notify_func,
760 apr_pool_t *result_pool,
761 apr_pool_t *scratch_pool)
763 svn_string_t prop_val = *old_value;
765 /* Tolerate mergeinfo with "\r\n" line endings because some
766 dumpstream sources might contain as much. If so normalize
767 the line endings to '\n' and notify that we have made this
769 if (strstr(prop_val.data, "\r"))
771 const char *prop_eol_normalized;
773 SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
774 &prop_eol_normalized,
775 "\n", /* translate to LF */
776 FALSE, /* no repair */
777 NULL, /* no keywords */
778 FALSE, /* no expansion */
780 prop_val.data = prop_eol_normalized;
781 prop_val.len = strlen(prop_eol_normalized);
785 svn_repos_notify_t *notify
786 = svn_repos_notify_create(
787 svn_repos_notify_load_normalized_mergeinfo,
790 notify_func(notify_baton, notify, scratch_pool);
794 /* Renumber mergeinfo as appropriate. */
795 SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val,
796 rev_map, oldest_dumpstream_rev,
802 /* Prefix the merge source paths with PARENT_DIR. */
803 /* ASSUMPTION: All source paths are included in the dump stream. */
804 SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
805 parent_dir, result_pool));
813 set_node_property(void *baton,
815 const svn_string_t *value)
817 struct node_baton *nb = baton;
818 struct revision_baton *rb = nb->rb;
819 struct parse_baton *pb = rb->pb;
821 /* If we're skipping this revision, we're done here. */
825 /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
826 property has an ill-formed value, then we must not fail to load
827 the repository (at least if it's a simple load with no revision
828 offset adjustments, path changes, etc.) so just warn and leave it
830 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
832 svn_string_t *new_value;
835 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
838 pb->oldest_dumpstream_rev,
840 pb->notify_func, pb->notify_baton,
841 nb->pool, pb->notify_pool);
842 svn_pool_clear(pb->notify_pool);
845 if (pb->validate_props)
847 return svn_error_quick_wrap(
849 _("Invalid svn:mergeinfo value"));
853 svn_repos_notify_t *notify
854 = svn_repos_notify_create(svn_repos_notify_warning,
857 notify->warning = svn_repos_notify_warning_invalid_mergeinfo;
858 notify->warning_str = _("Invalid svn:mergeinfo value; "
859 "leaving unchanged");
860 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
861 svn_pool_clear(pb->notify_pool);
863 svn_error_clear(err);
871 return change_node_prop(rb->txn_root, nb->path, name, value,
872 pb->validate_props, nb->pool);
877 delete_node_property(void *baton,
880 struct node_baton *nb = baton;
881 struct revision_baton *rb = nb->rb;
883 /* If we're skipping this revision, we're done here. */
887 return change_node_prop(rb->txn_root, nb->path, name, NULL,
888 rb->pb->validate_props, nb->pool);
893 remove_node_props(void *baton)
895 struct node_baton *nb = baton;
896 struct revision_baton *rb = nb->rb;
897 apr_hash_t *proplist;
898 apr_hash_index_t *hi;
900 /* If we're skipping this revision, we're done here. */
904 SVN_ERR(svn_fs_node_proplist(&proplist,
905 rb->txn_root, nb->path, nb->pool));
907 for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
909 const char *key = apr_hash_this_key(hi);
911 SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
912 rb->pb->validate_props, nb->pool));
920 apply_textdelta(svn_txdelta_window_handler_t *handler,
921 void **handler_baton,
924 struct node_baton *nb = node_baton;
925 struct revision_baton *rb = nb->rb;
927 /* If we're skipping this revision, we're done here. */
934 return svn_fs_apply_textdelta(handler, handler_baton,
935 rb->txn_root, nb->path,
936 svn_checksum_to_cstring(nb->base_checksum,
938 svn_checksum_to_cstring(nb->result_checksum,
945 set_fulltext(svn_stream_t **stream,
948 struct node_baton *nb = node_baton;
949 struct revision_baton *rb = nb->rb;
951 /* If we're skipping this revision, we're done here. */
958 return svn_fs_apply_text(stream,
959 rb->txn_root, nb->path,
960 svn_checksum_to_cstring(nb->result_checksum,
967 close_node(void *baton)
969 struct node_baton *nb = baton;
970 struct revision_baton *rb = nb->rb;
971 struct parse_baton *pb = rb->pb;
973 /* If we're skipping this revision, we're done here. */
979 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
980 svn_repos_notify_t *notify = svn_repos_notify_create(
981 svn_repos_notify_load_node_done,
984 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
985 svn_pool_clear(pb->notify_pool);
993 close_revision(void *baton)
995 struct revision_baton *rb = baton;
996 struct parse_baton *pb = rb->pb;
997 const char *conflict_msg = NULL;
998 svn_revnum_t committed_rev;
1000 const char *txn_name = NULL;
1001 apr_hash_t *hooks_env;
1003 /* If we're skipping this revision we're done here. */
1005 return SVN_NO_ERROR;
1009 /* Special case: set revision 0 properties when loading into an
1010 'empty' filesystem. */
1011 svn_revnum_t youngest_rev;
1013 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
1015 if (youngest_rev == 0)
1017 apr_hash_t *orig_props;
1018 apr_hash_t *new_props;
1019 apr_array_header_t *diff;
1022 SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, 0, TRUE,
1023 rb->pool, rb->pool));
1024 new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1025 SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1027 for (i = 0; i < diff->nelts; i++)
1029 const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1031 SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
1032 pb->validate_props, pb->normalize_props,
1037 return SVN_NO_ERROR;
1040 /* If the dumpstream doesn't have an 'svn:date' property and we
1041 aren't ignoring the dates in the dumpstream altogether, remove
1042 any 'svn:date' revision property that was set by FS layer when
1043 the TXN was created. */
1044 if (! (pb->ignore_dates || rb->datestamp))
1046 svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1047 prop->name = SVN_PROP_REVISION_DATE;
1051 if (rb->pb->normalize_props)
1053 apr_pool_t *iterpool;
1056 iterpool = svn_pool_create(rb->pool);
1057 for (i = 0; i < rb->revprops->nelts; i++)
1059 svn_prop_t *prop = &APR_ARRAY_IDX(rb->revprops, i, svn_prop_t);
1061 svn_pool_clear(iterpool);
1062 SVN_ERR(svn_repos__normalize_prop(&prop->value, NULL, prop->name,
1063 prop->value, rb->pool, iterpool));
1065 svn_pool_destroy(iterpool);
1068 /* Apply revision property changes. */
1069 if (rb->pb->validate_props)
1070 SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1072 SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1074 /* Get the txn name and hooks environment if they will be needed. */
1075 if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
1077 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
1078 rb->pool, rb->pool));
1080 err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
1083 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1084 return svn_error_trace(err);
1088 /* Run the pre-commit hook, if so commanded. */
1089 if (pb->use_pre_commit_hook)
1091 err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
1092 txn_name, rb->pool);
1095 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1096 return svn_error_trace(err);
1101 err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
1102 if (SVN_IS_VALID_REVNUM(committed_rev))
1106 /* ### Log any error, but better yet is to rev
1107 ### close_revision()'s API to allow both committed_rev and err
1108 ### to be returned, see #3768. */
1109 svn_error_clear(err);
1114 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1116 return svn_error_quick_wrap(err, conflict_msg);
1118 return svn_error_trace(err);
1121 /* Run post-commit hook, if so commanded. */
1122 if (pb->use_post_commit_hook)
1124 if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
1125 committed_rev, txn_name,
1127 return svn_error_create
1128 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1129 _("Commit succeeded, but post-commit hook failed"));
1132 /* After a successful commit, must record the dump-rev -> in-repos-rev
1133 mapping, so that copyfrom instructions in the dump file can look up the
1134 correct repository revision to copy from. */
1135 set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
1137 /* If the incoming dump stream has non-contiguous revisions (e.g. from
1138 using svndumpfilter --drop-empty-revs without --renumber-revs) then
1139 we must account for the missing gaps in PB->REV_MAP. Otherwise we
1140 might not be able to map all mergeinfo source revisions to the correct
1141 revisions in the target repos. */
1142 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
1143 && (rb->rev != pb->last_rev_mapped + 1))
1147 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
1149 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1153 /* Update our "last revision mapped". */
1154 pb->last_rev_mapped = rb->rev;
1156 /* Deltify the predecessors of paths changed in this revision. */
1157 SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1159 if (pb->notify_func)
1161 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1162 svn_repos_notify_t *notify = svn_repos_notify_create(
1163 svn_repos_notify_load_txn_committed,
1166 notify->new_revision = committed_rev;
1167 notify->old_revision = ((committed_rev == rb->rev)
1168 ? SVN_INVALID_REVNUM
1170 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1171 svn_pool_clear(pb->notify_pool);
1174 return SVN_NO_ERROR;
1178 /*----------------------------------------------------------------------*/
1180 /** The public routines **/
1184 svn_repos_get_fs_build_parser6(const svn_repos_parse_fns3_t **callbacks,
1187 svn_revnum_t start_rev,
1188 svn_revnum_t end_rev,
1189 svn_boolean_t use_history,
1190 svn_boolean_t validate_props,
1191 enum svn_repos_load_uuid uuid_action,
1192 const char *parent_dir,
1193 svn_boolean_t use_pre_commit_hook,
1194 svn_boolean_t use_post_commit_hook,
1195 svn_boolean_t ignore_dates,
1196 svn_boolean_t normalize_props,
1197 svn_repos_notify_func_t notify_func,
1201 svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1202 struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1205 parent_dir = svn_relpath_canonicalize(parent_dir, pool);
1207 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1208 SVN_IS_VALID_REVNUM(end_rev))
1209 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1210 (! SVN_IS_VALID_REVNUM(end_rev))));
1211 if (SVN_IS_VALID_REVNUM(start_rev))
1212 SVN_ERR_ASSERT(start_rev <= end_rev);
1214 parser->magic_header_record = NULL;
1215 parser->uuid_record = uuid_record;
1216 parser->new_revision_record = new_revision_record;
1217 parser->new_node_record = new_node_record;
1218 parser->set_revision_property = set_revision_property;
1219 parser->set_node_property = set_node_property;
1220 parser->remove_node_props = remove_node_props;
1221 parser->set_fulltext = set_fulltext;
1222 parser->close_node = close_node;
1223 parser->close_revision = close_revision;
1224 parser->delete_node_property = delete_node_property;
1225 parser->apply_textdelta = apply_textdelta;
1228 pb->fs = svn_repos_fs(repos);
1229 pb->use_history = use_history;
1230 pb->validate_props = validate_props;
1231 pb->notify_func = notify_func;
1232 pb->notify_baton = notify_baton;
1233 pb->uuid_action = uuid_action;
1234 pb->parent_dir = parent_dir;
1236 pb->notify_pool = svn_pool_create(pool);
1237 pb->rev_map = apr_hash_make(pool);
1238 pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1239 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1240 pb->start_rev = start_rev;
1241 pb->end_rev = end_rev;
1242 pb->use_pre_commit_hook = use_pre_commit_hook;
1243 pb->use_post_commit_hook = use_post_commit_hook;
1244 pb->ignore_dates = ignore_dates;
1245 pb->normalize_props = normalize_props;
1247 *callbacks = parser;
1249 return SVN_NO_ERROR;
1254 svn_repos_load_fs6(svn_repos_t *repos,
1255 svn_stream_t *dumpstream,
1256 svn_revnum_t start_rev,
1257 svn_revnum_t end_rev,
1258 enum svn_repos_load_uuid uuid_action,
1259 const char *parent_dir,
1260 svn_boolean_t use_pre_commit_hook,
1261 svn_boolean_t use_post_commit_hook,
1262 svn_boolean_t validate_props,
1263 svn_boolean_t ignore_dates,
1264 svn_boolean_t normalize_props,
1265 svn_repos_notify_func_t notify_func,
1267 svn_cancel_func_t cancel_func,
1271 const svn_repos_parse_fns3_t *parser;
1274 /* This is really simple. */
1276 SVN_ERR(svn_repos_get_fs_build_parser6(&parser, &parse_baton,
1279 TRUE, /* look for copyfrom revs */
1283 use_pre_commit_hook,
1284 use_post_commit_hook,
1291 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1292 cancel_func, cancel_baton, pool);
1295 /*----------------------------------------------------------------------*/
1297 /** The same functionality for revprops only **/
1299 /* Implement svn_repos_parse_fns3_t.new_revision_record.
1301 * Because the revision is supposed to already exist, we don't need to
1302 * start transactions etc. */
1303 static svn_error_t *
1304 revprops_new_revision_record(void **revision_baton,
1305 apr_hash_t *headers,
1309 struct parse_baton *pb = parse_baton;
1310 struct revision_baton *rb;
1312 rb = make_revision_baton(headers, pb, pool);
1314 /* If we're skipping this revision, try to notify someone. */
1315 if (rb->skipped && pb->notify_func)
1317 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1318 svn_repos_notify_t *notify = svn_repos_notify_create(
1319 svn_repos_notify_load_skipped_rev,
1322 notify->old_revision = rb->rev;
1323 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1324 svn_pool_clear(pb->notify_pool);
1327 /* If we're parsing revision 0, only the revision props are (possibly)
1328 interesting to us: when loading the stream into an empty
1329 filesystem, then we want new filesystem's revision 0 to have the
1330 same props. Otherwise, we just ignore revision 0 in the stream. */
1332 *revision_baton = rb;
1333 return SVN_NO_ERROR;
1336 /* Implement svn_repos_parse_fns3_t.close_revision.
1338 * Simply set the revprops we previously parsed and send notifications.
1339 * This is the place where we will detect missing revisions. */
1340 static svn_error_t *
1341 revprops_close_revision(void *baton)
1343 struct revision_baton *rb = baton;
1344 struct parse_baton *pb = rb->pb;
1345 apr_hash_t *orig_props;
1346 apr_hash_t *new_props;
1347 apr_array_header_t *diff;
1350 /* If we're skipping this revision we're done here. */
1352 return SVN_NO_ERROR;
1354 /* If the dumpstream doesn't have an 'svn:date' property and we
1355 aren't ignoring the dates in the dumpstream altogether, remove
1356 any 'svn:date' revision property that was set by FS layer when
1357 the TXN was created. */
1358 if (! (pb->ignore_dates || rb->datestamp))
1360 svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1361 prop->name = SVN_PROP_REVISION_DATE;
1365 SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, rb->rev, TRUE,
1366 rb->pool, rb->pool));
1367 new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1368 SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1370 for (i = 0; i < diff->nelts; i++)
1372 const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1374 SVN_ERR(change_rev_prop(pb->repos, rb->rev, prop->name, prop->value,
1375 pb->validate_props, pb->normalize_props,
1379 if (pb->notify_func)
1381 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1382 svn_repos_notify_t *notify = svn_repos_notify_create(
1383 svn_repos_notify_load_revprop_set,
1386 notify->new_revision = rb->rev;
1387 notify->old_revision = SVN_INVALID_REVNUM;
1388 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1389 svn_pool_clear(pb->notify_pool);
1392 return SVN_NO_ERROR;
1395 /* Set *CALLBACKS and *PARSE_BATON to a vtable parser which commits new
1396 * revisions to the fs in REPOS. Allocate the objects in RESULT_POOL.
1398 * START_REV and END_REV act as filters, the lower and upper (inclusive)
1399 * range values of revisions in DUMPSTREAM which will be loaded. Either
1400 * both of these values are #SVN_INVALID_REVNUM (in which case no
1401 * revision-based filtering occurs at all), or both are valid revisions
1402 * (where START_REV is older than or equivalent to END_REV).
1404 * START_REV and END_REV act as filters, the lower and upper (inclusive)
1405 * range values of revisions which will
1406 * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in
1407 * which case no revision-based filtering occurs at all), or both are
1408 * valid revisions (where START_REV is older than or equivalent to
1409 * END_REV). They refer to dump stream revision numbers rather than
1410 * committed revision numbers.
1412 * If VALIDATE_PROPS is set, then validate Subversion revision properties
1413 * (those in the svn: namespace) against established rules for those things.
1415 * If IGNORE_DATES is set, ignore any revision datestamps found in
1416 * DUMPSTREAM, keeping whatever timestamps the revisions currently have.
1418 * If NORMALIZE_PROPS is set, attempt to normalize invalid Subversion
1419 * revision and node properties (those in the svn: namespace) so that
1420 * their values would follow the established rules for them. Currently,
1421 * this means translating non-LF line endings in the property values to LF.
1423 static svn_error_t *
1424 build_revprop_parser(const svn_repos_parse_fns3_t **callbacks,
1427 svn_revnum_t start_rev,
1428 svn_revnum_t end_rev,
1429 svn_boolean_t validate_props,
1430 svn_boolean_t ignore_dates,
1431 svn_boolean_t normalize_props,
1432 svn_repos_notify_func_t notify_func,
1434 apr_pool_t *result_pool)
1436 svn_repos_parse_fns3_t *parser = apr_pcalloc(result_pool, sizeof(*parser));
1437 struct parse_baton *pb = apr_pcalloc(result_pool, sizeof(*pb));
1439 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1440 SVN_IS_VALID_REVNUM(end_rev))
1441 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1442 (! SVN_IS_VALID_REVNUM(end_rev))));
1443 if (SVN_IS_VALID_REVNUM(start_rev))
1444 SVN_ERR_ASSERT(start_rev <= end_rev);
1446 parser->magic_header_record = NULL;
1447 parser->uuid_record = uuid_record;
1448 parser->new_revision_record = revprops_new_revision_record;
1449 parser->new_node_record = NULL;
1450 parser->set_revision_property = set_revision_property;
1451 parser->set_node_property = NULL;
1452 parser->remove_node_props = NULL;
1453 parser->set_fulltext = NULL;
1454 parser->close_node = NULL;
1455 parser->close_revision = revprops_close_revision;
1456 parser->delete_node_property = NULL;
1457 parser->apply_textdelta = NULL;
1460 pb->fs = svn_repos_fs(repos);
1461 pb->use_history = FALSE;
1462 pb->validate_props = validate_props;
1463 pb->notify_func = notify_func;
1464 pb->notify_baton = notify_baton;
1465 pb->uuid_action = svn_repos_load_uuid_ignore; /* Never touch the UUID. */
1466 pb->parent_dir = NULL;
1467 pb->pool = result_pool;
1468 pb->notify_pool = svn_pool_create(result_pool);
1470 pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1471 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1472 pb->start_rev = start_rev;
1473 pb->end_rev = end_rev;
1474 pb->use_pre_commit_hook = FALSE;
1475 pb->use_post_commit_hook = FALSE;
1476 pb->ignore_dates = ignore_dates;
1477 pb->normalize_props = normalize_props;
1479 *callbacks = parser;
1481 return SVN_NO_ERROR;
1486 svn_repos_load_fs_revprops(svn_repos_t *repos,
1487 svn_stream_t *dumpstream,
1488 svn_revnum_t start_rev,
1489 svn_revnum_t end_rev,
1490 svn_boolean_t validate_props,
1491 svn_boolean_t ignore_dates,
1492 svn_boolean_t normalize_props,
1493 svn_repos_notify_func_t notify_func,
1495 svn_cancel_func_t cancel_func,
1497 apr_pool_t *scratch_pool)
1499 const svn_repos_parse_fns3_t *parser;
1502 /* This is really simple. */
1504 SVN_ERR(build_revprop_parser(&parser, &parse_baton,
1514 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1515 cancel_func, cancel_baton, scratch_pool);