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 https://issues.apache.org/jira/browse/SVN-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.
159 If NORMALIZE_PROPS is set, attempt to normalize properties before
160 changing them, if that is needed. If VALIDATE_PROPS is set, use
161 functions which perform validation of the property value.
162 Otherwise, bypass those checks. */
164 change_rev_prop(svn_repos_t *repos,
165 svn_revnum_t revision,
167 const svn_string_t *value,
168 svn_boolean_t validate_props,
169 svn_boolean_t normalize_props,
173 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
176 return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
177 NULL, value, FALSE, FALSE,
180 return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
184 /* Change property NAME to VALUE for PATH in TXN_ROOT.
185 If NORMALIZE_PROPS is set, attempt to normalize properties before
186 changing them, if that is needed. If VALIDATE_PROPS is set, use
187 functions which perform validation of the property value.
188 Otherwise, bypass those checks. */
190 change_node_prop(svn_fs_root_t *txn_root,
193 const svn_string_t *value,
194 svn_boolean_t validate_props,
195 svn_boolean_t normalize_props,
199 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
202 return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
204 return svn_fs_change_node_prop(txn_root, path, name, value, pool);
207 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
208 return it in *MERGEINFO_VAL. */
210 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
211 const svn_string_t *mergeinfo_orig,
212 const char *parent_dir,
215 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
216 apr_hash_index_t *hi;
218 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
219 prefixed_mergeinfo = apr_hash_make(pool);
220 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
222 const char *merge_source = apr_hash_this_key(hi);
223 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
224 const char *path, *canonicalized_path;
226 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
227 merge_source, pool, pool));
228 merge_source = canonicalized_path;
230 /* The svn:mergeinfo property syntax demands a repos abspath */
231 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
234 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
236 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
240 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
241 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
242 (allocated from POOL).
244 Adjust any mergeinfo revisions not older than OLDEST_DUMPSTREAM_REV by
245 using REV_MAP which maps (svn_revnum_t) old rev to (svn_revnum_t) new rev.
247 Adjust any mergeinfo revisions older than OLDEST_DUMPSTREAM_REV by
248 (-OLDER_REVS_OFFSET), dropping any that become <= 0.
251 renumber_mergeinfo_revs(svn_string_t **final_val,
252 const svn_string_t *initial_val,
254 svn_revnum_t oldest_dumpstream_rev,
255 apr_int32_t older_revs_offset,
258 apr_pool_t *subpool = svn_pool_create(pool);
259 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
260 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
261 apr_hash_index_t *hi;
263 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
266 https://issues.apache.org/jira/browse/SVN-3020#desc16
267 Remove mergeinfo older than the oldest revision in the dump stream
268 and adjust its revisions by the difference between the head rev of
269 the target repository and the current dump stream rev. */
270 if (oldest_dumpstream_rev > 1)
272 /* predates_stream_mergeinfo := mergeinfo that refers to revs before
273 oldest_dumpstream_rev */
274 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
275 &predates_stream_mergeinfo, mergeinfo,
276 oldest_dumpstream_rev - 1, 0,
277 TRUE, subpool, subpool));
278 /* mergeinfo := mergeinfo that refers to revs >= oldest_dumpstream_rev */
279 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
280 &mergeinfo, mergeinfo,
281 oldest_dumpstream_rev - 1, 0,
282 FALSE, subpool, subpool));
283 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
284 &predates_stream_mergeinfo, predates_stream_mergeinfo,
285 -older_revs_offset, subpool, subpool));
289 predates_stream_mergeinfo = NULL;
292 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
294 const char *merge_source = apr_hash_this_key(hi);
295 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
298 /* Possibly renumber revisions in merge source's rangelist. */
299 for (i = 0; i < rangelist->nelts; i++)
301 svn_revnum_t rev_from_map;
302 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
303 svn_merge_range_t *);
304 rev_from_map = get_revision_mapping(rev_map, range->start);
305 if (SVN_IS_VALID_REVNUM(rev_from_map))
307 range->start = rev_from_map;
309 else if (range->start == oldest_dumpstream_rev - 1)
311 /* Since the start revision of svn_merge_range_t are not
312 inclusive there is one possible valid start revision that
313 won't be found in the REV_MAP mapping of load stream
314 revsions to loaded revisions: The revision immediately
315 preceding the oldest revision from the load stream.
316 This is a valid revision for mergeinfo, but not a valid
317 copy from revision (which REV_MAP also maps for) so it
318 will never be in the mapping.
320 If that is what we have here, then find the mapping for the
321 oldest rev from the load stream and subtract 1 to get the
322 renumbered, non-inclusive, start revision. */
323 rev_from_map = get_revision_mapping(rev_map,
324 oldest_dumpstream_rev);
325 if (SVN_IS_VALID_REVNUM(rev_from_map))
326 range->start = rev_from_map - 1;
330 /* If we can't remap the start revision then don't even bother
331 trying to remap the end revision. It's possible we might
332 actually succeed at the latter, which can result in invalid
333 mergeinfo with a start rev > end rev. If that gets into the
334 repository then a world of bustage breaks loose anytime that
335 bogus mergeinfo is parsed. See
336 https://issues.apache.org/jira/browse/SVN-3020#desc16.
341 rev_from_map = get_revision_mapping(rev_map, range->end);
342 if (SVN_IS_VALID_REVNUM(rev_from_map))
343 range->end = rev_from_map;
345 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
348 if (predates_stream_mergeinfo)
350 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
354 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
356 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
357 svn_pool_destroy(subpool);
362 /*----------------------------------------------------------------------*/
364 /** vtable for doing commits to a fs **/
367 /* Make a node baton, parsing the relevant HEADERS.
369 * If RB->pb->parent_dir:
370 * prefix it to NB->path
371 * prefix it to NB->copyfrom_path (if present)
374 make_node_baton(struct node_baton **node_baton_p,
376 struct revision_baton *rb,
379 struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
382 /* Start with sensible defaults. */
385 nb->kind = svn_node_unknown;
387 /* Then add info from the headers. */
388 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
390 const char *canonicalized_path;
391 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
393 val = canonicalized_path;
394 if (rb->pb->parent_dir)
395 nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
400 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
402 if (! strcmp(val, "file"))
403 nb->kind = svn_node_file;
404 else if (! strcmp(val, "dir"))
405 nb->kind = svn_node_dir;
408 nb->action = (enum svn_node_action)(-1); /* an invalid action code */
409 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
411 if (! strcmp(val, "change"))
412 nb->action = svn_node_action_change;
413 else if (! strcmp(val, "add"))
414 nb->action = svn_node_action_add;
415 else if (! strcmp(val, "delete"))
416 nb->action = svn_node_action_delete;
417 else if (! strcmp(val, "replace"))
418 nb->action = svn_node_action_replace;
421 nb->copyfrom_rev = SVN_INVALID_REVNUM;
422 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
424 nb->copyfrom_rev = SVN_STR_TO_REV(val);
426 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
428 val = svn_relpath_canonicalize(val, pool);
429 if (rb->pb->parent_dir)
430 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
432 nb->copyfrom_path = val;
435 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
437 SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
441 if ((val = svn_hash_gets(headers,
442 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
444 SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
448 if ((val = svn_hash_gets(headers,
449 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
451 SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
452 svn_checksum_md5, val, pool));
455 /* What's cool about this dump format is that the parser just
456 ignores any unrecognized headers. :-) */
462 /* Make a revision baton, parsing the relevant HEADERS.
464 * Set RB->skipped iff the revision number is outside the range given in PB.
466 static struct revision_baton *
467 make_revision_baton(apr_hash_t *headers,
468 struct parse_baton *pb,
471 struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
476 rb->rev = SVN_INVALID_REVNUM;
477 rb->revprops = apr_array_make(rb->pool, 8, sizeof(svn_prop_t));
479 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
481 rb->rev = SVN_STR_TO_REV(val);
483 /* If we're filtering revisions, is this one we'll skip? */
484 rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
485 && ((rb->rev < pb->start_rev) ||
486 (rb->rev > pb->end_rev)));
494 new_revision_record(void **revision_baton,
499 struct parse_baton *pb = parse_baton;
500 struct revision_baton *rb;
501 svn_revnum_t head_rev;
503 rb = make_revision_baton(headers, pb, pool);
505 /* ### If we're filtering revisions, and this is one we've skipped,
506 ### and we've skipped it because it has a revision number younger
507 ### than the youngest in our acceptable range, then should we
508 ### just bail out here? */
510 if (rb->skipped && (rb->rev > pb->end_rev))
511 return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
512 _("Finished processing acceptable load "
516 SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
518 /* FIXME: This is a lame fallback loading multiple segments of dump in
519 several separate operations. It is highly susceptible to race conditions.
520 Calculate the revision 'offset' for finding copyfrom sources.
521 It might be positive or negative. */
522 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
524 if ((rb->rev > 0) && (! rb->skipped))
526 /* Create a new fs txn. */
527 SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev,
528 SVN_FS_TXN_CLIENT_DATE, pool));
529 SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
533 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
534 svn_repos_notify_t *notify = svn_repos_notify_create(
535 svn_repos_notify_load_txn_start,
538 notify->old_revision = rb->rev;
539 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
540 svn_pool_clear(pb->notify_pool);
543 /* Stash the oldest "old" revision committed from the load stream. */
544 if (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))
545 pb->oldest_dumpstream_rev = rb->rev;
548 /* If we're skipping this revision, try to notify someone. */
549 if (rb->skipped && pb->notify_func)
551 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
552 svn_repos_notify_t *notify = svn_repos_notify_create(
553 svn_repos_notify_load_skipped_rev,
556 notify->old_revision = rb->rev;
557 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
558 svn_pool_clear(pb->notify_pool);
561 /* If we're parsing revision 0, only the revision props are (possibly)
562 interesting to us: when loading the stream into an empty
563 filesystem, then we want new filesystem's revision 0 to have the
564 same props. Otherwise, we just ignore revision 0 in the stream. */
566 *revision_baton = rb;
572 /* Perform a copy or a plain add.
574 * For a copy, also adjust the copy-from rev, check any copy-source checksum,
575 * and send a notification.
578 maybe_add_with_history(struct node_baton *nb,
579 struct revision_baton *rb,
582 struct parse_baton *pb = rb->pb;
584 if ((nb->copyfrom_path == NULL) || (! pb->use_history))
586 /* Add empty file or dir, without history. */
587 if (nb->kind == svn_node_file)
588 SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
590 else if (nb->kind == svn_node_dir)
591 SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
595 /* Hunt down the source revision in this fs. */
596 svn_fs_root_t *copy_root;
597 svn_revnum_t copyfrom_rev;
599 /* Try to find the copyfrom revision in the revision map;
600 failing that, fall back to the revision offset approach. */
601 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
602 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
603 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
605 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
606 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
607 _("Relative source revision %ld is not"
608 " available in current repository"),
611 SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool));
613 if (nb->copy_source_checksum)
615 svn_checksum_t *checksum;
616 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
617 nb->copyfrom_path, TRUE, pool));
618 if (!svn_checksum_match(nb->copy_source_checksum, checksum))
619 return svn_checksum_mismatch_err(nb->copy_source_checksum,
621 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
622 "to '%s' in rev based on r%ld"),
623 nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
626 SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
627 rb->txn_root, nb->path, pool));
631 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
632 svn_repos_notify_t *notify = svn_repos_notify_create(
633 svn_repos_notify_load_copied_node,
636 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
637 svn_pool_clear(pb->notify_pool);
645 uuid_record(const char *uuid,
649 struct parse_baton *pb = parse_baton;
650 svn_revnum_t youngest_rev;
652 if (pb->uuid_action == svn_repos_load_uuid_ignore)
655 if (pb->uuid_action != svn_repos_load_uuid_force)
657 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
658 if (youngest_rev != 0)
662 return svn_fs_set_uuid(pb->fs, uuid, pool);
666 new_node_record(void **node_baton,
668 void *revision_baton,
671 struct revision_baton *rb = revision_baton;
672 struct parse_baton *pb = rb->pb;
673 struct node_baton *nb;
676 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
677 _("Malformed dumpstream: "
678 "Revision 0 must not contain node records"));
680 SVN_ERR(make_node_baton(&nb, headers, rb, pool));
682 /* If we're skipping this revision, we're done here. */
689 /* Make sure we have an action we recognize. */
690 if (nb->action < svn_node_action_change
691 || nb->action > svn_node_action_replace)
692 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
693 _("Unrecognized node-action on node '%s'"),
698 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
699 svn_repos_notify_t *notify = svn_repos_notify_create(
700 svn_repos_notify_load_node_start,
703 notify->path = nb->path;
704 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
705 svn_pool_clear(pb->notify_pool);
710 case svn_node_action_change:
713 case svn_node_action_delete:
714 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
717 case svn_node_action_add:
718 SVN_ERR(maybe_add_with_history(nb, rb, pool));
721 case svn_node_action_replace:
722 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
723 SVN_ERR(maybe_add_with_history(nb, rb, pool));
732 set_revision_property(void *baton,
734 const svn_string_t *value)
736 struct revision_baton *rb = baton;
737 struct parse_baton *pb = rb->pb;
738 svn_boolean_t is_date = strcmp(name, SVN_PROP_REVISION_DATE) == 0;
741 /* If we're skipping this revision, we're done here. */
745 /* If we're ignoring dates, and this is one, we're done here. */
746 if (is_date && pb->ignore_dates)
749 /* Collect property changes to apply them in one FS call in
751 prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
752 prop->name = apr_pstrdup(rb->pool, name);
753 prop->value = svn_string_dup(value, rb->pool);
755 /* Remember any datestamp that passes through! (See comment in
756 close_revision() below.) */
758 rb->datestamp = svn_string_dup(value, rb->pool);
765 svn_repos__adjust_mergeinfo_property(svn_string_t **new_value_p,
766 const svn_string_t *old_value,
767 const char *parent_dir,
769 svn_revnum_t oldest_dumpstream_rev,
770 apr_int32_t older_revs_offset,
771 svn_repos_notify_func_t notify_func,
773 apr_pool_t *result_pool,
774 apr_pool_t *scratch_pool)
776 svn_string_t prop_val = *old_value;
778 /* Tolerate mergeinfo with "\r\n" line endings because some
779 dumpstream sources might contain as much. If so normalize
780 the line endings to '\n' and notify that we have made this
782 if (strstr(prop_val.data, "\r"))
784 const char *prop_eol_normalized;
786 SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
787 &prop_eol_normalized,
788 "\n", /* translate to LF */
789 FALSE, /* no repair */
790 NULL, /* no keywords */
791 FALSE, /* no expansion */
793 prop_val.data = prop_eol_normalized;
794 prop_val.len = strlen(prop_eol_normalized);
798 svn_repos_notify_t *notify
799 = svn_repos_notify_create(
800 svn_repos_notify_load_normalized_mergeinfo,
803 notify_func(notify_baton, notify, scratch_pool);
807 /* Renumber mergeinfo as appropriate. */
808 SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val,
809 rev_map, oldest_dumpstream_rev,
815 /* Prefix the merge source paths with PARENT_DIR. */
816 /* ASSUMPTION: All source paths are included in the dump stream. */
817 SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
818 parent_dir, result_pool));
826 set_node_property(void *baton,
828 const svn_string_t *value)
830 struct node_baton *nb = baton;
831 struct revision_baton *rb = nb->rb;
832 struct parse_baton *pb = rb->pb;
834 /* If we're skipping this revision, we're done here. */
838 /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
839 property has an ill-formed value, then we must not fail to load
840 the repository (at least if it's a simple load with no revision
841 offset adjustments, path changes, etc.) so just warn and leave it
843 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
845 svn_string_t *new_value;
848 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
851 pb->oldest_dumpstream_rev,
853 pb->notify_func, pb->notify_baton,
854 nb->pool, pb->notify_pool);
855 svn_pool_clear(pb->notify_pool);
858 if (pb->validate_props)
860 return svn_error_quick_wrap(
862 _("Invalid svn:mergeinfo value"));
866 svn_repos_notify_t *notify
867 = svn_repos_notify_create(svn_repos_notify_warning,
870 notify->warning = svn_repos_notify_warning_invalid_mergeinfo;
871 notify->warning_str = _("Invalid svn:mergeinfo value; "
872 "leaving unchanged");
873 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
874 svn_pool_clear(pb->notify_pool);
876 svn_error_clear(err);
884 return change_node_prop(rb->txn_root, nb->path, name, value,
885 pb->validate_props, rb->pb->normalize_props,
891 delete_node_property(void *baton,
894 struct node_baton *nb = baton;
895 struct revision_baton *rb = nb->rb;
897 /* If we're skipping this revision, we're done here. */
901 return change_node_prop(rb->txn_root, nb->path, name, NULL,
902 rb->pb->validate_props, rb->pb->normalize_props,
908 remove_node_props(void *baton)
910 struct node_baton *nb = baton;
911 struct revision_baton *rb = nb->rb;
912 apr_hash_t *proplist;
913 apr_hash_index_t *hi;
915 /* If we're skipping this revision, we're done here. */
919 SVN_ERR(svn_fs_node_proplist(&proplist,
920 rb->txn_root, nb->path, nb->pool));
922 for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
924 const char *key = apr_hash_this_key(hi);
926 SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
927 rb->pb->validate_props, rb->pb->normalize_props,
936 apply_textdelta(svn_txdelta_window_handler_t *handler,
937 void **handler_baton,
940 struct node_baton *nb = node_baton;
941 struct revision_baton *rb = nb->rb;
943 /* If we're skipping this revision, we're done here. */
950 return svn_fs_apply_textdelta(handler, handler_baton,
951 rb->txn_root, nb->path,
952 svn_checksum_to_cstring(nb->base_checksum,
954 svn_checksum_to_cstring(nb->result_checksum,
961 set_fulltext(svn_stream_t **stream,
964 struct node_baton *nb = node_baton;
965 struct revision_baton *rb = nb->rb;
967 /* If we're skipping this revision, we're done here. */
974 return svn_fs_apply_text(stream,
975 rb->txn_root, nb->path,
976 svn_checksum_to_cstring(nb->result_checksum,
983 close_node(void *baton)
985 struct node_baton *nb = baton;
986 struct revision_baton *rb = nb->rb;
987 struct parse_baton *pb = rb->pb;
989 /* If we're skipping this revision, we're done here. */
995 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
996 svn_repos_notify_t *notify = svn_repos_notify_create(
997 svn_repos_notify_load_node_done,
1000 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1001 svn_pool_clear(pb->notify_pool);
1004 return SVN_NO_ERROR;
1008 static svn_error_t *
1009 close_revision(void *baton)
1011 struct revision_baton *rb = baton;
1012 struct parse_baton *pb = rb->pb;
1013 const char *conflict_msg = NULL;
1014 svn_revnum_t committed_rev;
1016 const char *txn_name = NULL;
1017 apr_hash_t *hooks_env;
1019 /* If we're skipping this revision we're done here. */
1021 return SVN_NO_ERROR;
1025 /* Special case: set revision 0 properties when loading into an
1026 'empty' filesystem. */
1027 svn_revnum_t youngest_rev;
1029 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
1031 if (youngest_rev == 0)
1033 apr_hash_t *orig_props;
1034 apr_hash_t *new_props;
1035 apr_array_header_t *diff;
1038 SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, 0, TRUE,
1039 rb->pool, rb->pool));
1040 new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1041 SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1043 for (i = 0; i < diff->nelts; i++)
1045 const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1047 SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
1048 pb->validate_props, pb->normalize_props,
1053 return SVN_NO_ERROR;
1056 /* If the dumpstream doesn't have an 'svn:date' property and we
1057 aren't ignoring the dates in the dumpstream altogether, remove
1058 any 'svn:date' revision property that was set by FS layer when
1059 the TXN was created. */
1060 if (! (pb->ignore_dates || rb->datestamp))
1062 svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1063 prop->name = SVN_PROP_REVISION_DATE;
1067 if (rb->pb->normalize_props)
1069 apr_pool_t *iterpool;
1072 iterpool = svn_pool_create(rb->pool);
1073 for (i = 0; i < rb->revprops->nelts; i++)
1075 svn_prop_t *prop = &APR_ARRAY_IDX(rb->revprops, i, svn_prop_t);
1077 svn_pool_clear(iterpool);
1078 SVN_ERR(svn_repos__normalize_prop(&prop->value, NULL, prop->name,
1079 prop->value, rb->pool, iterpool));
1081 svn_pool_destroy(iterpool);
1084 /* Apply revision property changes. */
1085 if (rb->pb->validate_props)
1086 SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1088 SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1090 /* Get the txn name and hooks environment if they will be needed. */
1091 if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
1093 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
1094 rb->pool, rb->pool));
1096 err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
1099 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1100 return svn_error_trace(err);
1104 /* Run the pre-commit hook, if so commanded. */
1105 if (pb->use_pre_commit_hook)
1107 err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
1108 txn_name, rb->pool);
1111 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1112 return svn_error_trace(err);
1117 err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
1118 if (SVN_IS_VALID_REVNUM(committed_rev))
1122 /* ### Log any error, but better yet is to rev
1123 ### close_revision()'s API to allow both committed_rev and err
1124 ### to be returned, see #3768. */
1125 svn_error_clear(err);
1130 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1132 return svn_error_quick_wrap(err, conflict_msg);
1134 return svn_error_trace(err);
1137 /* Run post-commit hook, if so commanded. */
1138 if (pb->use_post_commit_hook)
1140 if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
1141 committed_rev, txn_name,
1143 return svn_error_create
1144 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1145 _("Commit succeeded, but post-commit hook failed"));
1148 /* After a successful commit, must record the dump-rev -> in-repos-rev
1149 mapping, so that copyfrom instructions in the dump file can look up the
1150 correct repository revision to copy from. */
1151 set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
1153 /* If the incoming dump stream has non-contiguous revisions (e.g. from
1154 using svndumpfilter --drop-empty-revs without --renumber-revs) then
1155 we must account for the missing gaps in PB->REV_MAP. Otherwise we
1156 might not be able to map all mergeinfo source revisions to the correct
1157 revisions in the target repos. */
1158 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
1159 && (rb->rev != pb->last_rev_mapped + 1))
1163 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
1165 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1169 /* Update our "last revision mapped". */
1170 pb->last_rev_mapped = rb->rev;
1172 /* Deltify the predecessors of paths changed in this revision. */
1173 SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1175 if (pb->notify_func)
1177 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1178 svn_repos_notify_t *notify = svn_repos_notify_create(
1179 svn_repos_notify_load_txn_committed,
1182 notify->new_revision = committed_rev;
1183 notify->old_revision = ((committed_rev == rb->rev)
1184 ? SVN_INVALID_REVNUM
1186 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1187 svn_pool_clear(pb->notify_pool);
1190 return SVN_NO_ERROR;
1194 /*----------------------------------------------------------------------*/
1196 /** The public routines **/
1200 svn_repos_get_fs_build_parser6(const svn_repos_parse_fns3_t **callbacks,
1203 svn_revnum_t start_rev,
1204 svn_revnum_t end_rev,
1205 svn_boolean_t use_history,
1206 svn_boolean_t validate_props,
1207 enum svn_repos_load_uuid uuid_action,
1208 const char *parent_dir,
1209 svn_boolean_t use_pre_commit_hook,
1210 svn_boolean_t use_post_commit_hook,
1211 svn_boolean_t ignore_dates,
1212 svn_boolean_t normalize_props,
1213 svn_repos_notify_func_t notify_func,
1217 svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1218 struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1222 const char *canonicalized_path;
1223 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
1224 parent_dir, pool, pool));
1225 parent_dir = canonicalized_path;
1228 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1229 SVN_IS_VALID_REVNUM(end_rev))
1230 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1231 (! SVN_IS_VALID_REVNUM(end_rev))));
1232 if (SVN_IS_VALID_REVNUM(start_rev))
1233 SVN_ERR_ASSERT(start_rev <= end_rev);
1235 parser->magic_header_record = NULL;
1236 parser->uuid_record = uuid_record;
1237 parser->new_revision_record = new_revision_record;
1238 parser->new_node_record = new_node_record;
1239 parser->set_revision_property = set_revision_property;
1240 parser->set_node_property = set_node_property;
1241 parser->remove_node_props = remove_node_props;
1242 parser->set_fulltext = set_fulltext;
1243 parser->close_node = close_node;
1244 parser->close_revision = close_revision;
1245 parser->delete_node_property = delete_node_property;
1246 parser->apply_textdelta = apply_textdelta;
1249 pb->fs = svn_repos_fs(repos);
1250 pb->use_history = use_history;
1251 pb->validate_props = validate_props;
1252 pb->notify_func = notify_func;
1253 pb->notify_baton = notify_baton;
1254 pb->uuid_action = uuid_action;
1255 pb->parent_dir = parent_dir;
1257 pb->notify_pool = svn_pool_create(pool);
1258 pb->rev_map = apr_hash_make(pool);
1259 pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1260 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1261 pb->start_rev = start_rev;
1262 pb->end_rev = end_rev;
1263 pb->use_pre_commit_hook = use_pre_commit_hook;
1264 pb->use_post_commit_hook = use_post_commit_hook;
1265 pb->ignore_dates = ignore_dates;
1266 pb->normalize_props = normalize_props;
1268 *callbacks = parser;
1270 return SVN_NO_ERROR;
1275 svn_repos_load_fs6(svn_repos_t *repos,
1276 svn_stream_t *dumpstream,
1277 svn_revnum_t start_rev,
1278 svn_revnum_t end_rev,
1279 enum svn_repos_load_uuid uuid_action,
1280 const char *parent_dir,
1281 svn_boolean_t use_pre_commit_hook,
1282 svn_boolean_t use_post_commit_hook,
1283 svn_boolean_t validate_props,
1284 svn_boolean_t ignore_dates,
1285 svn_boolean_t normalize_props,
1286 svn_repos_notify_func_t notify_func,
1288 svn_cancel_func_t cancel_func,
1292 const svn_repos_parse_fns3_t *parser;
1295 /* This is really simple. */
1297 SVN_ERR(svn_repos_get_fs_build_parser6(&parser, &parse_baton,
1300 TRUE, /* look for copyfrom revs */
1304 use_pre_commit_hook,
1305 use_post_commit_hook,
1312 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1313 cancel_func, cancel_baton, pool);
1316 /*----------------------------------------------------------------------*/
1318 /** The same functionality for revprops only **/
1320 /* Implement svn_repos_parse_fns3_t.new_revision_record.
1322 * Because the revision is supposed to already exist, we don't need to
1323 * start transactions etc. */
1324 static svn_error_t *
1325 revprops_new_revision_record(void **revision_baton,
1326 apr_hash_t *headers,
1330 struct parse_baton *pb = parse_baton;
1331 struct revision_baton *rb;
1333 rb = make_revision_baton(headers, pb, pool);
1335 /* If we're skipping this revision, try to notify someone. */
1336 if (rb->skipped && pb->notify_func)
1338 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1339 svn_repos_notify_t *notify = svn_repos_notify_create(
1340 svn_repos_notify_load_skipped_rev,
1343 notify->old_revision = rb->rev;
1344 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1345 svn_pool_clear(pb->notify_pool);
1348 /* If we're parsing revision 0, only the revision props are (possibly)
1349 interesting to us: when loading the stream into an empty
1350 filesystem, then we want new filesystem's revision 0 to have the
1351 same props. Otherwise, we just ignore revision 0 in the stream. */
1353 *revision_baton = rb;
1354 return SVN_NO_ERROR;
1357 /* Implement svn_repos_parse_fns3_t.close_revision.
1359 * Simply set the revprops we previously parsed and send notifications.
1360 * This is the place where we will detect missing revisions. */
1361 static svn_error_t *
1362 revprops_close_revision(void *baton)
1364 struct revision_baton *rb = baton;
1365 struct parse_baton *pb = rb->pb;
1366 apr_hash_t *orig_props;
1367 apr_hash_t *new_props;
1368 apr_array_header_t *diff;
1371 /* If we're skipping this revision we're done here. */
1373 return SVN_NO_ERROR;
1375 /* If the dumpstream doesn't have an 'svn:date' property and we
1376 aren't ignoring the dates in the dumpstream altogether, remove
1377 any 'svn:date' revision property that was set by FS layer when
1378 the TXN was created. */
1379 if (! (pb->ignore_dates || rb->datestamp))
1381 svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1382 prop->name = SVN_PROP_REVISION_DATE;
1386 SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, rb->rev, TRUE,
1387 rb->pool, rb->pool));
1388 new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1389 SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1391 for (i = 0; i < diff->nelts; i++)
1393 const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1395 SVN_ERR(change_rev_prop(pb->repos, rb->rev, prop->name, prop->value,
1396 pb->validate_props, pb->normalize_props,
1400 if (pb->notify_func)
1402 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1403 svn_repos_notify_t *notify = svn_repos_notify_create(
1404 svn_repos_notify_load_revprop_set,
1407 notify->new_revision = rb->rev;
1408 notify->old_revision = SVN_INVALID_REVNUM;
1409 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1410 svn_pool_clear(pb->notify_pool);
1413 return SVN_NO_ERROR;
1416 /* Set *CALLBACKS and *PARSE_BATON to a vtable parser which commits new
1417 * revisions to the fs in REPOS. Allocate the objects in RESULT_POOL.
1419 * START_REV and END_REV act as filters, the lower and upper (inclusive)
1420 * range values of revisions in DUMPSTREAM which will be loaded. Either
1421 * both of these values are #SVN_INVALID_REVNUM (in which case no
1422 * revision-based filtering occurs at all), or both are valid revisions
1423 * (where START_REV is older than or equivalent to END_REV).
1425 * START_REV and END_REV act as filters, the lower and upper (inclusive)
1426 * range values of revisions which will
1427 * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in
1428 * which case no revision-based filtering occurs at all), or both are
1429 * valid revisions (where START_REV is older than or equivalent to
1430 * END_REV). They refer to dump stream revision numbers rather than
1431 * committed revision numbers.
1433 * If VALIDATE_PROPS is set, then validate Subversion revision properties
1434 * (those in the svn: namespace) against established rules for those things.
1436 * If IGNORE_DATES is set, ignore any revision datestamps found in
1437 * DUMPSTREAM, keeping whatever timestamps the revisions currently have.
1439 * If NORMALIZE_PROPS is set, attempt to normalize invalid Subversion
1440 * revision and node properties (those in the svn: namespace) so that
1441 * their values would follow the established rules for them. Currently,
1442 * this means translating non-LF line endings in the property values to LF.
1444 static svn_error_t *
1445 build_revprop_parser(const svn_repos_parse_fns3_t **callbacks,
1448 svn_revnum_t start_rev,
1449 svn_revnum_t end_rev,
1450 svn_boolean_t validate_props,
1451 svn_boolean_t ignore_dates,
1452 svn_boolean_t normalize_props,
1453 svn_repos_notify_func_t notify_func,
1455 apr_pool_t *result_pool)
1457 svn_repos_parse_fns3_t *parser = apr_pcalloc(result_pool, sizeof(*parser));
1458 struct parse_baton *pb = apr_pcalloc(result_pool, sizeof(*pb));
1460 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1461 SVN_IS_VALID_REVNUM(end_rev))
1462 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1463 (! SVN_IS_VALID_REVNUM(end_rev))));
1464 if (SVN_IS_VALID_REVNUM(start_rev))
1465 SVN_ERR_ASSERT(start_rev <= end_rev);
1467 parser->magic_header_record = NULL;
1468 parser->uuid_record = uuid_record;
1469 parser->new_revision_record = revprops_new_revision_record;
1470 parser->new_node_record = NULL;
1471 parser->set_revision_property = set_revision_property;
1472 parser->set_node_property = NULL;
1473 parser->remove_node_props = NULL;
1474 parser->set_fulltext = NULL;
1475 parser->close_node = NULL;
1476 parser->close_revision = revprops_close_revision;
1477 parser->delete_node_property = NULL;
1478 parser->apply_textdelta = NULL;
1481 pb->fs = svn_repos_fs(repos);
1482 pb->use_history = FALSE;
1483 pb->validate_props = validate_props;
1484 pb->notify_func = notify_func;
1485 pb->notify_baton = notify_baton;
1486 pb->uuid_action = svn_repos_load_uuid_ignore; /* Never touch the UUID. */
1487 pb->parent_dir = NULL;
1488 pb->pool = result_pool;
1489 pb->notify_pool = svn_pool_create(result_pool);
1491 pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1492 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1493 pb->start_rev = start_rev;
1494 pb->end_rev = end_rev;
1495 pb->use_pre_commit_hook = FALSE;
1496 pb->use_post_commit_hook = FALSE;
1497 pb->ignore_dates = ignore_dates;
1498 pb->normalize_props = normalize_props;
1500 *callbacks = parser;
1502 return SVN_NO_ERROR;
1507 svn_repos_load_fs_revprops(svn_repos_t *repos,
1508 svn_stream_t *dumpstream,
1509 svn_revnum_t start_rev,
1510 svn_revnum_t end_rev,
1511 svn_boolean_t validate_props,
1512 svn_boolean_t ignore_dates,
1513 svn_boolean_t normalize_props,
1514 svn_repos_notify_func_t notify_func,
1516 svn_cancel_func_t cancel_func,
1518 apr_pool_t *scratch_pool)
1520 const svn_repos_parse_fns3_t *parser;
1523 /* This is really simple. */
1525 SVN_ERR(build_revprop_parser(&parser, &parse_baton,
1535 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1536 cancel_func, cancel_baton, scratch_pool);