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 use_pre_commit_hook;
59 svn_boolean_t use_post_commit_hook;
60 enum svn_repos_load_uuid uuid_action;
61 const char *parent_dir; /* repository relpath, or NULL */
62 svn_repos_notify_func_t notify_func;
64 apr_pool_t *notify_pool; /* scratch pool for notifications */
67 /* Start and end (inclusive) of revision range we'll pay attention
68 to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
70 svn_revnum_t start_rev;
73 /* A hash mapping copy-from revisions and mergeinfo range revisions
74 (svn_revnum_t *) in the dump stream to their corresponding revisions
75 (svn_revnum_t *) in the loaded repository. The hash and its
76 contents are allocated in POOL. */
77 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
78 ### for discussion about improving the memory costs of this mapping. */
81 /* The most recent (youngest) revision from the dump stream mapped in
82 REV_MAP. If no revisions have been mapped yet, this is set to
83 SVN_INVALID_REVNUM. */
84 svn_revnum_t last_rev_mapped;
86 /* The oldest revision loaded from the dump stream. If no revisions
87 have been loaded yet, this is set to SVN_INVALID_REVNUM. */
88 svn_revnum_t oldest_dumpstream_rev;
93 /* rev num from dump file */
96 svn_fs_root_t *txn_root;
98 const svn_string_t *datestamp;
100 /* (rev num from dump file) minus (rev num to be committed) */
101 apr_int32_t rev_offset;
102 svn_boolean_t skipped;
104 /* Array of svn_prop_t with revision properties. */
105 apr_array_header_t *revprops;
107 struct parse_baton *pb;
114 svn_node_kind_t kind;
115 enum svn_node_action action;
116 svn_checksum_t *base_checksum; /* null, if not available */
117 svn_checksum_t *result_checksum; /* null, if not available */
118 svn_checksum_t *copy_source_checksum; /* null, if not available */
120 svn_revnum_t copyfrom_rev;
121 const char *copyfrom_path;
123 struct revision_baton *rb;
128 /*----------------------------------------------------------------------*/
130 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
131 anything added to the hash is allocated in the hash's pool. */
133 set_revision_mapping(apr_hash_t *rev_map,
134 svn_revnum_t from_rev,
137 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
138 sizeof(svn_revnum_t) * 2);
139 mapped_revs[0] = from_rev;
140 mapped_revs[1] = to_rev;
141 apr_hash_set(rev_map, mapped_revs,
142 sizeof(svn_revnum_t), mapped_revs + 1);
145 /* Return the revision to which FROM_REV maps in REV_MAP, or
146 SVN_INVALID_REVNUM if no such mapping exists. */
148 get_revision_mapping(apr_hash_t *rev_map,
149 svn_revnum_t from_rev)
151 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
153 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
157 /* Change revision property NAME to VALUE for REVISION in REPOS. If
158 VALIDATE_PROPS is set, use functions which perform validation of
159 the property value. Otherwise, bypass those checks. */
161 change_rev_prop(svn_repos_t *repos,
162 svn_revnum_t revision,
164 const svn_string_t *value,
165 svn_boolean_t validate_props,
169 return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
170 NULL, value, FALSE, FALSE,
173 return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
177 /* Change property NAME to VALUE for PATH in TXN_ROOT. If
178 VALIDATE_PROPS is set, use functions which perform validation of
179 the property value. Otherwise, bypass those checks. */
181 change_node_prop(svn_fs_root_t *txn_root,
184 const svn_string_t *value,
185 svn_boolean_t validate_props,
189 return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
191 return svn_fs_change_node_prop(txn_root, path, name, value, pool);
194 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
195 return it in *MERGEINFO_VAL. */
197 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
198 const svn_string_t *mergeinfo_orig,
199 const char *parent_dir,
202 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
203 apr_hash_index_t *hi;
205 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
206 prefixed_mergeinfo = apr_hash_make(pool);
207 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
209 const char *merge_source = apr_hash_this_key(hi);
210 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
213 merge_source = svn_relpath_canonicalize(merge_source, pool);
215 /* The svn:mergeinfo property syntax demands a repos abspath */
216 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
219 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
221 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
225 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
226 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
227 (allocated from POOL).
229 Adjust any mergeinfo revisions not older than OLDEST_DUMPSTREAM_REV by
230 using REV_MAP which maps (svn_revnum_t) old rev to (svn_revnum_t) new rev.
232 Adjust any mergeinfo revisions older than OLDEST_DUMPSTREAM_REV by
233 (-OLDER_REVS_OFFSET), dropping any that become <= 0.
236 renumber_mergeinfo_revs(svn_string_t **final_val,
237 const svn_string_t *initial_val,
239 svn_revnum_t oldest_dumpstream_rev,
240 apr_int32_t older_revs_offset,
243 apr_pool_t *subpool = svn_pool_create(pool);
244 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
245 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
246 apr_hash_index_t *hi;
248 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
251 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
252 Remove mergeinfo older than the oldest revision in the dump stream
253 and adjust its revisions by the difference between the head rev of
254 the target repository and the current dump stream rev. */
255 if (oldest_dumpstream_rev > 1)
257 /* predates_stream_mergeinfo := mergeinfo that refers to revs before
258 oldest_dumpstream_rev */
259 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
260 &predates_stream_mergeinfo, mergeinfo,
261 oldest_dumpstream_rev - 1, 0,
262 TRUE, subpool, subpool));
263 /* mergeinfo := mergeinfo that refers to revs >= oldest_dumpstream_rev */
264 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
265 &mergeinfo, mergeinfo,
266 oldest_dumpstream_rev - 1, 0,
267 FALSE, subpool, subpool));
268 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
269 &predates_stream_mergeinfo, predates_stream_mergeinfo,
270 -older_revs_offset, subpool, subpool));
274 predates_stream_mergeinfo = NULL;
277 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
279 const char *merge_source = apr_hash_this_key(hi);
280 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
283 /* Possibly renumber revisions in merge source's rangelist. */
284 for (i = 0; i < rangelist->nelts; i++)
286 svn_revnum_t rev_from_map;
287 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
288 svn_merge_range_t *);
289 rev_from_map = get_revision_mapping(rev_map, range->start);
290 if (SVN_IS_VALID_REVNUM(rev_from_map))
292 range->start = rev_from_map;
294 else if (range->start == oldest_dumpstream_rev - 1)
296 /* Since the start revision of svn_merge_range_t are not
297 inclusive there is one possible valid start revision that
298 won't be found in the REV_MAP mapping of load stream
299 revsions to loaded revisions: The revision immediately
300 preceding the oldest revision from the load stream.
301 This is a valid revision for mergeinfo, but not a valid
302 copy from revision (which REV_MAP also maps for) so it
303 will never be in the mapping.
305 If that is what we have here, then find the mapping for the
306 oldest rev from the load stream and subtract 1 to get the
307 renumbered, non-inclusive, start revision. */
308 rev_from_map = get_revision_mapping(rev_map,
309 oldest_dumpstream_rev);
310 if (SVN_IS_VALID_REVNUM(rev_from_map))
311 range->start = rev_from_map - 1;
315 /* If we can't remap the start revision then don't even bother
316 trying to remap the end revision. It's possible we might
317 actually succeed at the latter, which can result in invalid
318 mergeinfo with a start rev > end rev. If that gets into the
319 repository then a world of bustage breaks loose anytime that
320 bogus mergeinfo is parsed. See
321 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
326 rev_from_map = get_revision_mapping(rev_map, range->end);
327 if (SVN_IS_VALID_REVNUM(rev_from_map))
328 range->end = rev_from_map;
330 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
333 if (predates_stream_mergeinfo)
335 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
339 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
341 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
342 svn_pool_destroy(subpool);
347 /*----------------------------------------------------------------------*/
349 /** vtable for doing commits to a fs **/
352 /* Make a node baton, parsing the relevant HEADERS.
354 * If RB->pb->parent_dir:
355 * prefix it to NB->path
356 * prefix it to NB->copyfrom_path (if present)
359 make_node_baton(struct node_baton **node_baton_p,
361 struct revision_baton *rb,
364 struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
367 /* Start with sensible defaults. */
370 nb->kind = svn_node_unknown;
372 /* Then add info from the headers. */
373 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
375 val = svn_relpath_canonicalize(val, pool);
376 if (rb->pb->parent_dir)
377 nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
382 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
384 if (! strcmp(val, "file"))
385 nb->kind = svn_node_file;
386 else if (! strcmp(val, "dir"))
387 nb->kind = svn_node_dir;
390 nb->action = (enum svn_node_action)(-1); /* an invalid action code */
391 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
393 if (! strcmp(val, "change"))
394 nb->action = svn_node_action_change;
395 else if (! strcmp(val, "add"))
396 nb->action = svn_node_action_add;
397 else if (! strcmp(val, "delete"))
398 nb->action = svn_node_action_delete;
399 else if (! strcmp(val, "replace"))
400 nb->action = svn_node_action_replace;
403 nb->copyfrom_rev = SVN_INVALID_REVNUM;
404 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
406 nb->copyfrom_rev = SVN_STR_TO_REV(val);
408 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
410 val = svn_relpath_canonicalize(val, pool);
411 if (rb->pb->parent_dir)
412 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
414 nb->copyfrom_path = val;
417 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
419 SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
423 if ((val = svn_hash_gets(headers,
424 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
426 SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
430 if ((val = svn_hash_gets(headers,
431 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
433 SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
434 svn_checksum_md5, val, pool));
437 /* What's cool about this dump format is that the parser just
438 ignores any unrecognized headers. :-) */
444 /* Make a revision baton, parsing the relevant HEADERS.
446 * Set RB->skipped iff the revision number is outside the range given in PB.
448 static struct revision_baton *
449 make_revision_baton(apr_hash_t *headers,
450 struct parse_baton *pb,
453 struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
458 rb->rev = SVN_INVALID_REVNUM;
459 rb->revprops = apr_array_make(rb->pool, 8, sizeof(svn_prop_t));
461 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
463 rb->rev = SVN_STR_TO_REV(val);
465 /* If we're filtering revisions, is this one we'll skip? */
466 rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
467 && ((rb->rev < pb->start_rev) ||
468 (rb->rev > pb->end_rev)));
476 new_revision_record(void **revision_baton,
481 struct parse_baton *pb = parse_baton;
482 struct revision_baton *rb;
483 svn_revnum_t head_rev;
485 rb = make_revision_baton(headers, pb, pool);
487 /* ### If we're filtering revisions, and this is one we've skipped,
488 ### and we've skipped it because it has a revision number younger
489 ### than the youngest in our acceptable range, then should we
490 ### just bail out here? */
492 if (rb->skipped && (rb->rev > pb->end_rev))
493 return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
494 _("Finished processing acceptable load "
498 SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
500 /* FIXME: This is a lame fallback loading multiple segments of dump in
501 several separate operations. It is highly susceptible to race conditions.
502 Calculate the revision 'offset' for finding copyfrom sources.
503 It might be positive or negative. */
504 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
506 if ((rb->rev > 0) && (! rb->skipped))
508 /* Create a new fs txn. */
509 SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev,
510 SVN_FS_TXN_CLIENT_DATE, pool));
511 SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
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_txn_start,
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 /* Stash the oldest "old" revision committed from the load stream. */
526 if (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))
527 pb->oldest_dumpstream_rev = rb->rev;
530 /* If we're skipping this revision, try to notify someone. */
531 if (rb->skipped && pb->notify_func)
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_skipped_rev,
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 /* If we're parsing revision 0, only the revision props are (possibly)
544 interesting to us: when loading the stream into an empty
545 filesystem, then we want new filesystem's revision 0 to have the
546 same props. Otherwise, we just ignore revision 0 in the stream. */
548 *revision_baton = rb;
554 /* Perform a copy or a plain add.
556 * For a copy, also adjust the copy-from rev, check any copy-source checksum,
557 * and send a notification.
560 maybe_add_with_history(struct node_baton *nb,
561 struct revision_baton *rb,
564 struct parse_baton *pb = rb->pb;
566 if ((nb->copyfrom_path == NULL) || (! pb->use_history))
568 /* Add empty file or dir, without history. */
569 if (nb->kind == svn_node_file)
570 SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
572 else if (nb->kind == svn_node_dir)
573 SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
577 /* Hunt down the source revision in this fs. */
578 svn_fs_root_t *copy_root;
579 svn_revnum_t copyfrom_rev;
581 /* Try to find the copyfrom revision in the revision map;
582 failing that, fall back to the revision offset approach. */
583 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
584 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
585 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
587 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
588 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
589 _("Relative source revision %ld is not"
590 " available in current repository"),
593 SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool));
595 if (nb->copy_source_checksum)
597 svn_checksum_t *checksum;
598 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
599 nb->copyfrom_path, TRUE, pool));
600 if (!svn_checksum_match(nb->copy_source_checksum, checksum))
601 return svn_checksum_mismatch_err(nb->copy_source_checksum,
603 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
604 "to '%s' in rev based on r%ld"),
605 nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
608 SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
609 rb->txn_root, nb->path, pool));
613 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
614 svn_repos_notify_t *notify = svn_repos_notify_create(
615 svn_repos_notify_load_copied_node,
618 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
619 svn_pool_clear(pb->notify_pool);
627 magic_header_record(int version,
635 uuid_record(const char *uuid,
639 struct parse_baton *pb = parse_baton;
640 svn_revnum_t youngest_rev;
642 if (pb->uuid_action == svn_repos_load_uuid_ignore)
645 if (pb->uuid_action != svn_repos_load_uuid_force)
647 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
648 if (youngest_rev != 0)
652 return svn_fs_set_uuid(pb->fs, uuid, pool);
656 new_node_record(void **node_baton,
658 void *revision_baton,
661 struct revision_baton *rb = revision_baton;
662 struct parse_baton *pb = rb->pb;
663 struct node_baton *nb;
666 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
667 _("Malformed dumpstream: "
668 "Revision 0 must not contain node records"));
670 SVN_ERR(make_node_baton(&nb, headers, rb, pool));
672 /* If we're skipping this revision, we're done here. */
679 /* Make sure we have an action we recognize. */
680 if (nb->action < svn_node_action_change
681 || nb->action > svn_node_action_replace)
682 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
683 _("Unrecognized node-action on node '%s'"),
688 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
689 svn_repos_notify_t *notify = svn_repos_notify_create(
690 svn_repos_notify_load_node_start,
693 notify->path = nb->path;
694 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
695 svn_pool_clear(pb->notify_pool);
700 case svn_node_action_change:
703 case svn_node_action_delete:
704 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
707 case svn_node_action_add:
708 SVN_ERR(maybe_add_with_history(nb, rb, pool));
711 case svn_node_action_replace:
712 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
713 SVN_ERR(maybe_add_with_history(nb, rb, pool));
722 set_revision_property(void *baton,
724 const svn_string_t *value)
726 struct revision_baton *rb = baton;
727 struct parse_baton *pb = rb->pb;
728 svn_boolean_t is_date = strcmp(name, SVN_PROP_REVISION_DATE) == 0;
731 /* If we're skipping this revision, we're done here. */
735 /* If we're ignoring dates, and this is one, we're done here. */
736 if (is_date && pb->ignore_dates)
739 /* Collect property changes to apply them in one FS call in
741 prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
742 prop->name = apr_pstrdup(rb->pool, name);
743 prop->value = svn_string_dup(value, rb->pool);
745 /* Remember any datestamp that passes through! (See comment in
746 close_revision() below.) */
748 rb->datestamp = svn_string_dup(value, rb->pool);
755 svn_repos__adjust_mergeinfo_property(svn_string_t **new_value_p,
756 const svn_string_t *old_value,
757 const char *parent_dir,
759 svn_revnum_t oldest_dumpstream_rev,
760 apr_int32_t older_revs_offset,
761 svn_repos_notify_func_t notify_func,
763 apr_pool_t *result_pool,
764 apr_pool_t *scratch_pool)
766 svn_string_t prop_val = *old_value;
768 /* Tolerate mergeinfo with "\r\n" line endings because some
769 dumpstream sources might contain as much. If so normalize
770 the line endings to '\n' and notify that we have made this
772 if (strstr(prop_val.data, "\r"))
774 const char *prop_eol_normalized;
776 SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
777 &prop_eol_normalized,
778 "\n", /* translate to LF */
779 FALSE, /* no repair */
780 NULL, /* no keywords */
781 FALSE, /* no expansion */
783 prop_val.data = prop_eol_normalized;
784 prop_val.len = strlen(prop_eol_normalized);
788 svn_repos_notify_t *notify
789 = svn_repos_notify_create(
790 svn_repos_notify_load_normalized_mergeinfo,
793 notify_func(notify_baton, notify, scratch_pool);
797 /* Renumber mergeinfo as appropriate. */
798 SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val,
799 rev_map, oldest_dumpstream_rev,
805 /* Prefix the merge source paths with PARENT_DIR. */
806 /* ASSUMPTION: All source paths are included in the dump stream. */
807 SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
808 parent_dir, result_pool));
816 set_node_property(void *baton,
818 const svn_string_t *value)
820 struct node_baton *nb = baton;
821 struct revision_baton *rb = nb->rb;
822 struct parse_baton *pb = rb->pb;
824 /* If we're skipping this revision, we're done here. */
828 /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
829 property has an ill-formed value, then we must not fail to load
830 the repository (at least if it's a simple load with no revision
831 offset adjustments, path changes, etc.) so just warn and leave it
833 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
835 svn_string_t *new_value;
838 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
841 pb->oldest_dumpstream_rev,
843 pb->notify_func, pb->notify_baton,
844 nb->pool, pb->notify_pool);
845 svn_pool_clear(pb->notify_pool);
848 if (pb->validate_props)
850 return svn_error_quick_wrap(
852 _("Invalid svn:mergeinfo value"));
856 svn_repos_notify_t *notify
857 = svn_repos_notify_create(svn_repos_notify_warning,
860 notify->warning = svn_repos_notify_warning_invalid_mergeinfo;
861 notify->warning_str = _("Invalid svn:mergeinfo value; "
862 "leaving unchanged");
863 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
864 svn_pool_clear(pb->notify_pool);
866 svn_error_clear(err);
874 return change_node_prop(rb->txn_root, nb->path, name, value,
875 pb->validate_props, nb->pool);
880 delete_node_property(void *baton,
883 struct node_baton *nb = baton;
884 struct revision_baton *rb = nb->rb;
886 /* If we're skipping this revision, we're done here. */
890 return change_node_prop(rb->txn_root, nb->path, name, NULL,
891 rb->pb->validate_props, nb->pool);
896 remove_node_props(void *baton)
898 struct node_baton *nb = baton;
899 struct revision_baton *rb = nb->rb;
900 apr_hash_t *proplist;
901 apr_hash_index_t *hi;
903 /* If we're skipping this revision, we're done here. */
907 SVN_ERR(svn_fs_node_proplist(&proplist,
908 rb->txn_root, nb->path, nb->pool));
910 for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
912 const char *key = apr_hash_this_key(hi);
914 SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
915 rb->pb->validate_props, nb->pool));
923 apply_textdelta(svn_txdelta_window_handler_t *handler,
924 void **handler_baton,
927 struct node_baton *nb = node_baton;
928 struct revision_baton *rb = nb->rb;
930 /* If we're skipping this revision, we're done here. */
937 return svn_fs_apply_textdelta(handler, handler_baton,
938 rb->txn_root, nb->path,
939 svn_checksum_to_cstring(nb->base_checksum,
941 svn_checksum_to_cstring(nb->result_checksum,
948 set_fulltext(svn_stream_t **stream,
951 struct node_baton *nb = node_baton;
952 struct revision_baton *rb = nb->rb;
954 /* If we're skipping this revision, we're done here. */
961 return svn_fs_apply_text(stream,
962 rb->txn_root, nb->path,
963 svn_checksum_to_cstring(nb->result_checksum,
970 close_node(void *baton)
972 struct node_baton *nb = baton;
973 struct revision_baton *rb = nb->rb;
974 struct parse_baton *pb = rb->pb;
976 /* If we're skipping this revision, we're done here. */
982 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
983 svn_repos_notify_t *notify = svn_repos_notify_create(
984 svn_repos_notify_load_node_done,
987 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
988 svn_pool_clear(pb->notify_pool);
996 close_revision(void *baton)
998 struct revision_baton *rb = baton;
999 struct parse_baton *pb = rb->pb;
1000 const char *conflict_msg = NULL;
1001 svn_revnum_t committed_rev;
1003 const char *txn_name = NULL;
1004 apr_hash_t *hooks_env;
1006 /* If we're skipping this revision we're done here. */
1008 return SVN_NO_ERROR;
1012 /* Special case: set revision 0 properties when loading into an
1013 'empty' filesystem. */
1014 svn_revnum_t youngest_rev;
1016 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
1018 if (youngest_rev == 0)
1020 apr_hash_t *orig_props;
1021 apr_hash_t *new_props;
1022 apr_array_header_t *diff;
1025 SVN_ERR(svn_fs_revision_proplist(&orig_props, pb->fs, 0, rb->pool));
1026 new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1027 SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1029 for (i = 0; i < diff->nelts; i++)
1031 const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1033 SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
1034 pb->validate_props, rb->pool));
1038 return SVN_NO_ERROR;
1041 /* If the dumpstream doesn't have an 'svn:date' property and we
1042 aren't ignoring the dates in the dumpstream altogether, remove
1043 any 'svn:date' revision property that was set by FS layer when
1044 the TXN was created. */
1045 if (! (pb->ignore_dates || rb->datestamp))
1047 svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1048 prop->name = SVN_PROP_REVISION_DATE;
1052 /* Apply revision property changes. */
1053 if (rb->pb->validate_props)
1054 SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1056 SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1058 /* Get the txn name and hooks environment if they will be needed. */
1059 if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
1061 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
1062 rb->pool, rb->pool));
1064 err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
1067 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1068 return svn_error_trace(err);
1072 /* Run the pre-commit hook, if so commanded. */
1073 if (pb->use_pre_commit_hook)
1075 err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
1076 txn_name, rb->pool);
1079 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1080 return svn_error_trace(err);
1085 err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
1086 if (SVN_IS_VALID_REVNUM(committed_rev))
1090 /* ### Log any error, but better yet is to rev
1091 ### close_revision()'s API to allow both committed_rev and err
1092 ### to be returned, see #3768. */
1093 svn_error_clear(err);
1098 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1100 return svn_error_quick_wrap(err, conflict_msg);
1102 return svn_error_trace(err);
1105 /* Run post-commit hook, if so commanded. */
1106 if (pb->use_post_commit_hook)
1108 if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
1109 committed_rev, txn_name,
1111 return svn_error_create
1112 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1113 _("Commit succeeded, but post-commit hook failed"));
1116 /* After a successful commit, must record the dump-rev -> in-repos-rev
1117 mapping, so that copyfrom instructions in the dump file can look up the
1118 correct repository revision to copy from. */
1119 set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
1121 /* If the incoming dump stream has non-contiguous revisions (e.g. from
1122 using svndumpfilter --drop-empty-revs without --renumber-revs) then
1123 we must account for the missing gaps in PB->REV_MAP. Otherwise we
1124 might not be able to map all mergeinfo source revisions to the correct
1125 revisions in the target repos. */
1126 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
1127 && (rb->rev != pb->last_rev_mapped + 1))
1131 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
1133 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1137 /* Update our "last revision mapped". */
1138 pb->last_rev_mapped = rb->rev;
1140 /* Deltify the predecessors of paths changed in this revision. */
1141 SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1143 if (pb->notify_func)
1145 /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1146 svn_repos_notify_t *notify = svn_repos_notify_create(
1147 svn_repos_notify_load_txn_committed,
1150 notify->new_revision = committed_rev;
1151 notify->old_revision = ((committed_rev == rb->rev)
1152 ? SVN_INVALID_REVNUM
1154 pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1155 svn_pool_clear(pb->notify_pool);
1158 return SVN_NO_ERROR;
1162 /*----------------------------------------------------------------------*/
1164 /** The public routines **/
1168 svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **callbacks,
1171 svn_revnum_t start_rev,
1172 svn_revnum_t end_rev,
1173 svn_boolean_t use_history,
1174 svn_boolean_t validate_props,
1175 enum svn_repos_load_uuid uuid_action,
1176 const char *parent_dir,
1177 svn_boolean_t use_pre_commit_hook,
1178 svn_boolean_t use_post_commit_hook,
1179 svn_boolean_t ignore_dates,
1180 svn_repos_notify_func_t notify_func,
1184 svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1185 struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1188 parent_dir = svn_relpath_canonicalize(parent_dir, pool);
1190 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1191 SVN_IS_VALID_REVNUM(end_rev))
1192 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1193 (! SVN_IS_VALID_REVNUM(end_rev))));
1194 if (SVN_IS_VALID_REVNUM(start_rev))
1195 SVN_ERR_ASSERT(start_rev <= end_rev);
1197 parser->magic_header_record = magic_header_record;
1198 parser->uuid_record = uuid_record;
1199 parser->new_revision_record = new_revision_record;
1200 parser->new_node_record = new_node_record;
1201 parser->set_revision_property = set_revision_property;
1202 parser->set_node_property = set_node_property;
1203 parser->remove_node_props = remove_node_props;
1204 parser->set_fulltext = set_fulltext;
1205 parser->close_node = close_node;
1206 parser->close_revision = close_revision;
1207 parser->delete_node_property = delete_node_property;
1208 parser->apply_textdelta = apply_textdelta;
1211 pb->fs = svn_repos_fs(repos);
1212 pb->use_history = use_history;
1213 pb->validate_props = validate_props;
1214 pb->notify_func = notify_func;
1215 pb->notify_baton = notify_baton;
1216 pb->uuid_action = uuid_action;
1217 pb->parent_dir = parent_dir;
1219 pb->notify_pool = svn_pool_create(pool);
1220 pb->rev_map = apr_hash_make(pool);
1221 pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1222 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1223 pb->start_rev = start_rev;
1224 pb->end_rev = end_rev;
1225 pb->use_pre_commit_hook = use_pre_commit_hook;
1226 pb->use_post_commit_hook = use_post_commit_hook;
1227 pb->ignore_dates = ignore_dates;
1229 *callbacks = parser;
1231 return SVN_NO_ERROR;
1236 svn_repos_load_fs5(svn_repos_t *repos,
1237 svn_stream_t *dumpstream,
1238 svn_revnum_t start_rev,
1239 svn_revnum_t end_rev,
1240 enum svn_repos_load_uuid uuid_action,
1241 const char *parent_dir,
1242 svn_boolean_t use_pre_commit_hook,
1243 svn_boolean_t use_post_commit_hook,
1244 svn_boolean_t validate_props,
1245 svn_boolean_t ignore_dates,
1246 svn_repos_notify_func_t notify_func,
1248 svn_cancel_func_t cancel_func,
1252 const svn_repos_parse_fns3_t *parser;
1255 /* This is really simple. */
1257 SVN_ERR(svn_repos_get_fs_build_parser5(&parser, &parse_baton,
1260 TRUE, /* look for copyfrom revs */
1264 use_pre_commit_hook,
1265 use_post_commit_hook,
1271 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1272 cancel_func, cancel_baton, pool);