1 /* load-fs-vtable.c --- dumpstream loader vtable for committing into a
2 * Subversion filesystem.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
25 #include "svn_private_config.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
30 #include "svn_repos.h"
31 #include "svn_string.h"
32 #include "svn_props.h"
34 #include "svn_private_config.h"
35 #include "svn_mergeinfo.h"
36 #include "svn_checksum.h"
37 #include "svn_subst.h"
38 #include "svn_ctype.h"
39 #include "svn_dirent_uri.h"
43 #include "private/svn_fspath.h"
44 #include "private/svn_dep_compat.h"
45 #include "private/svn_mergeinfo_private.h"
47 /*----------------------------------------------------------------------*/
49 /** Batons used herein **/
56 svn_boolean_t use_history;
57 svn_boolean_t validate_props;
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 svn_repos_notify_t *notify;
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 old 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_old_rev;
95 svn_fs_root_t *txn_root;
97 const svn_string_t *datestamp;
99 apr_int32_t rev_offset;
100 svn_boolean_t skipped;
102 struct parse_baton *pb;
109 svn_node_kind_t kind;
110 enum svn_node_action action;
111 svn_checksum_t *base_checksum; /* null, if not available */
112 svn_checksum_t *result_checksum; /* null, if not available */
113 svn_checksum_t *copy_source_checksum; /* null, if not available */
115 svn_revnum_t copyfrom_rev;
116 const char *copyfrom_path;
118 struct revision_baton *rb;
123 /*----------------------------------------------------------------------*/
125 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
126 anything added to the hash is allocated in the hash's pool. */
128 set_revision_mapping(apr_hash_t *rev_map,
129 svn_revnum_t from_rev,
132 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
133 sizeof(svn_revnum_t) * 2);
134 mapped_revs[0] = from_rev;
135 mapped_revs[1] = to_rev;
136 apr_hash_set(rev_map, mapped_revs,
137 sizeof(svn_revnum_t), mapped_revs + 1);
140 /* Return the revision to which FROM_REV maps in REV_MAP, or
141 SVN_INVALID_REVNUM if no such mapping exists. */
143 get_revision_mapping(apr_hash_t *rev_map,
144 svn_revnum_t from_rev)
146 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
148 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
152 /* Change revision property NAME to VALUE for REVISION in REPOS. If
153 VALIDATE_PROPS is set, use functions which perform validation of
154 the property value. Otherwise, bypass those checks. */
156 change_rev_prop(svn_repos_t *repos,
157 svn_revnum_t revision,
159 const svn_string_t *value,
160 svn_boolean_t validate_props,
164 return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
165 NULL, value, FALSE, FALSE,
168 return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
172 /* Change property NAME to VALUE for PATH in TXN_ROOT. If
173 VALIDATE_PROPS is set, use functions which perform validation of
174 the property value. Otherwise, bypass those checks. */
176 change_node_prop(svn_fs_root_t *txn_root,
179 const svn_string_t *value,
180 svn_boolean_t validate_props,
184 return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
186 return svn_fs_change_node_prop(txn_root, path, name, value, pool);
189 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
190 return it in *MERGEINFO_VAL. */
191 /* ### FIXME: Consider somehow sharing code with
192 ### svnrdump/load_editor.c:prefix_mergeinfo_paths() */
194 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
195 const svn_string_t *mergeinfo_orig,
196 const char *parent_dir,
199 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
200 apr_hash_index_t *hi;
203 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
204 prefixed_mergeinfo = apr_hash_make(pool);
205 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
208 const char *path, *merge_source;
210 apr_hash_this(hi, &key, NULL, &rangelist);
211 merge_source = svn_relpath_canonicalize(key, pool);
213 /* The svn:mergeinfo property syntax demands a repos abspath */
214 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
217 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
219 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
223 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
224 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
225 (allocated from POOL). */
226 /* ### FIXME: Consider somehow sharing code with
227 ### svnrdump/load_editor.c:renumber_mergeinfo_revs() */
229 renumber_mergeinfo_revs(svn_string_t **final_val,
230 const svn_string_t *initial_val,
231 struct revision_baton *rb,
234 apr_pool_t *subpool = svn_pool_create(pool);
235 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
236 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
237 apr_hash_index_t *hi;
239 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
242 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
243 Remove mergeinfo older than the oldest revision in the dump stream
244 and adjust its revisions by the difference between the head rev of
245 the target repository and the current dump stream rev. */
246 if (rb->pb->oldest_old_rev > 1)
248 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
249 &predates_stream_mergeinfo, mergeinfo,
250 rb->pb->oldest_old_rev - 1, 0,
251 TRUE, subpool, subpool));
252 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
253 &mergeinfo, mergeinfo,
254 rb->pb->oldest_old_rev - 1, 0,
255 FALSE, subpool, subpool));
256 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
257 &predates_stream_mergeinfo, predates_stream_mergeinfo,
258 -rb->rev_offset, subpool, subpool));
262 predates_stream_mergeinfo = NULL;
265 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
267 const char *merge_source;
268 svn_rangelist_t *rangelist;
269 struct parse_baton *pb = rb->pb;
274 apr_hash_this(hi, &key, NULL, &val);
278 /* Possibly renumber revisions in merge source's rangelist. */
279 for (i = 0; i < rangelist->nelts; i++)
281 svn_revnum_t rev_from_map;
282 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
283 svn_merge_range_t *);
284 rev_from_map = get_revision_mapping(pb->rev_map, range->start);
285 if (SVN_IS_VALID_REVNUM(rev_from_map))
287 range->start = rev_from_map;
289 else if (range->start == pb->oldest_old_rev - 1)
291 /* Since the start revision of svn_merge_range_t are not
292 inclusive there is one possible valid start revision that
293 won't be found in the PB->REV_MAP mapping of load stream
294 revsions to loaded revisions: The revision immediately
295 preceeding the oldest revision from the load stream.
296 This is a valid revision for mergeinfo, but not a valid
297 copy from revision (which PB->REV_MAP also maps for) so it
298 will never be in the mapping.
300 If that is what we have here, then find the mapping for the
301 oldest rev from the load stream and subtract 1 to get the
302 renumbered, non-inclusive, start revision. */
303 rev_from_map = get_revision_mapping(pb->rev_map,
305 if (SVN_IS_VALID_REVNUM(rev_from_map))
306 range->start = rev_from_map - 1;
310 /* If we can't remap the start revision then don't even bother
311 trying to remap the end revision. It's possible we might
312 actually succeed at the latter, which can result in invalid
313 mergeinfo with a start rev > end rev. If that gets into the
314 repository then a world of bustage breaks loose anytime that
315 bogus mergeinfo is parsed. See
316 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
321 rev_from_map = get_revision_mapping(pb->rev_map, range->end);
322 if (SVN_IS_VALID_REVNUM(rev_from_map))
323 range->end = rev_from_map;
325 svn_hash_sets(final_mergeinfo, merge_source, rangelist);
328 if (predates_stream_mergeinfo)
329 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
332 SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
334 /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
335 or r1. However, svndumpfilter can be abused to produce r1 merge source
336 revs. So if we encounter any, then strip them out, no need to put them
337 into the load target. */
338 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
343 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
344 svn_pool_destroy(subpool);
349 /*----------------------------------------------------------------------*/
351 /** vtable for doing commits to a fs **/
355 make_node_baton(struct node_baton **node_baton_p,
357 struct revision_baton *rb,
360 struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
363 /* Start with sensible defaults. */
366 nb->kind = svn_node_unknown;
368 /* Then add info from the headers. */
369 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
371 val = svn_relpath_canonicalize(val, pool);
372 if (rb->pb->parent_dir)
373 nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
378 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
380 if (! strcmp(val, "file"))
381 nb->kind = svn_node_file;
382 else if (! strcmp(val, "dir"))
383 nb->kind = svn_node_dir;
386 nb->action = (enum svn_node_action)(-1); /* an invalid action code */
387 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
389 if (! strcmp(val, "change"))
390 nb->action = svn_node_action_change;
391 else if (! strcmp(val, "add"))
392 nb->action = svn_node_action_add;
393 else if (! strcmp(val, "delete"))
394 nb->action = svn_node_action_delete;
395 else if (! strcmp(val, "replace"))
396 nb->action = svn_node_action_replace;
399 nb->copyfrom_rev = SVN_INVALID_REVNUM;
400 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
402 nb->copyfrom_rev = SVN_STR_TO_REV(val);
404 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
406 val = svn_relpath_canonicalize(val, pool);
407 if (rb->pb->parent_dir)
408 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
410 nb->copyfrom_path = val;
413 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
415 SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
419 if ((val = svn_hash_gets(headers,
420 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
422 SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
426 if ((val = svn_hash_gets(headers,
427 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
429 SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
430 svn_checksum_md5, val, pool));
433 /* What's cool about this dump format is that the parser just
434 ignores any unrecognized headers. :-) */
440 static struct revision_baton *
441 make_revision_baton(apr_hash_t *headers,
442 struct parse_baton *pb,
445 struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
450 rb->rev = SVN_INVALID_REVNUM;
452 if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
454 rb->rev = SVN_STR_TO_REV(val);
456 /* If we're filtering revisions, is this one we'll skip? */
457 rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
458 && ((rb->rev < pb->start_rev) ||
459 (rb->rev > pb->end_rev)));
467 new_revision_record(void **revision_baton,
472 struct parse_baton *pb = parse_baton;
473 struct revision_baton *rb;
474 svn_revnum_t head_rev;
476 rb = make_revision_baton(headers, pb, pool);
478 /* ### If we're filtering revisions, and this is one we've skipped,
479 ### and we've skipped it because it has a revision number younger
480 ### than the youngest in our acceptable range, then should we
481 ### just bail out here? */
483 if (rb->skipped && (rb->rev > pb->end_rev))
484 return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
485 _("Finished processing acceptable load "
489 SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
491 /* FIXME: This is a lame fallback loading multiple segments of dump in
492 several separate operations. It is highly susceptible to race conditions.
493 Calculate the revision 'offset' for finding copyfrom sources.
494 It might be positive or negative. */
495 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
497 if ((rb->rev > 0) && (! rb->skipped))
499 /* Create a new fs txn. */
500 SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool));
501 SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
505 pb->notify->action = svn_repos_notify_load_txn_start;
506 pb->notify->old_revision = rb->rev;
507 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
510 /* Stash the oldest "old" revision committed from the load stream. */
511 if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev))
512 pb->oldest_old_rev = rb->rev;
515 /* If we're skipping this revision, try to notify someone. */
516 if (rb->skipped && pb->notify_func)
518 pb->notify->action = svn_repos_notify_load_skipped_rev;
519 pb->notify->old_revision = rb->rev;
520 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
523 /* If we're parsing revision 0, only the revision are (possibly)
524 interesting to us: when loading the stream into an empty
525 filesystem, then we want new filesystem's revision 0 to have the
526 same props. Otherwise, we just ignore revision 0 in the stream. */
528 *revision_baton = rb;
534 /* Factorized helper func for new_node_record() */
536 maybe_add_with_history(struct node_baton *nb,
537 struct revision_baton *rb,
540 struct parse_baton *pb = rb->pb;
542 if ((nb->copyfrom_path == NULL) || (! pb->use_history))
544 /* Add empty file or dir, without history. */
545 if (nb->kind == svn_node_file)
546 SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
548 else if (nb->kind == svn_node_dir)
549 SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
553 /* Hunt down the source revision in this fs. */
554 svn_fs_root_t *copy_root;
555 svn_revnum_t copyfrom_rev;
557 /* Try to find the copyfrom revision in the revision map;
558 failing that, fall back to the revision offset approach. */
559 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
560 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
561 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
563 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
564 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
565 _("Relative source revision %ld is not"
566 " available in current repository"),
569 SVN_ERR(svn_fs_revision_root(©_root, pb->fs, copyfrom_rev, pool));
571 if (nb->copy_source_checksum)
573 svn_checksum_t *checksum;
574 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
575 nb->copyfrom_path, TRUE, pool));
576 if (!svn_checksum_match(nb->copy_source_checksum, checksum))
577 return svn_checksum_mismatch_err(nb->copy_source_checksum,
579 _("Copy source checksum mismatch on copy from '%s'@%ld\n"
580 "to '%s' in rev based on r%ld"),
581 nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
584 SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
585 rb->txn_root, nb->path, pool));
589 pb->notify->action = svn_repos_notify_load_copied_node;
590 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
598 magic_header_record(int version,
606 uuid_record(const char *uuid,
610 struct parse_baton *pb = parse_baton;
611 svn_revnum_t youngest_rev;
613 if (pb->uuid_action == svn_repos_load_uuid_ignore)
616 if (pb->uuid_action != svn_repos_load_uuid_force)
618 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
619 if (youngest_rev != 0)
623 return svn_fs_set_uuid(pb->fs, uuid, pool);
627 new_node_record(void **node_baton,
629 void *revision_baton,
632 struct revision_baton *rb = revision_baton;
633 struct parse_baton *pb = rb->pb;
634 struct node_baton *nb;
637 return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
638 _("Malformed dumpstream: "
639 "Revision 0 must not contain node records"));
641 SVN_ERR(make_node_baton(&nb, headers, rb, pool));
643 /* If we're skipping this revision, we're done here. */
650 /* Make sure we have an action we recognize. */
651 if (nb->action < svn_node_action_change
652 || nb->action > svn_node_action_replace)
653 return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
654 _("Unrecognized node-action on node '%s'"),
659 pb->notify->action = svn_repos_notify_load_node_start;
660 pb->notify->node_action = nb->action;
661 pb->notify->path = nb->path;
662 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
667 case svn_node_action_change:
670 case svn_node_action_delete:
671 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
674 case svn_node_action_add:
675 SVN_ERR(maybe_add_with_history(nb, rb, pool));
678 case svn_node_action_replace:
679 SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
680 SVN_ERR(maybe_add_with_history(nb, rb, pool));
689 set_revision_property(void *baton,
691 const svn_string_t *value)
693 struct revision_baton *rb = baton;
695 /* If we're skipping this revision, we're done here. */
701 if (rb->pb->validate_props)
702 SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool));
704 SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool));
706 /* Remember any datestamp that passes through! (See comment in
707 close_revision() below.) */
708 if (! strcmp(name, SVN_PROP_REVISION_DATE))
709 rb->datestamp = svn_string_dup(value, rb->pool);
711 else if (rb->rev == 0)
713 /* Special case: set revision 0 properties when loading into an
714 'empty' filesystem. */
715 struct parse_baton *pb = rb->pb;
716 svn_revnum_t youngest_rev;
718 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
720 if (youngest_rev == 0)
721 SVN_ERR(change_rev_prop(pb->repos, 0, name, value,
722 pb->validate_props, rb->pool));
730 set_node_property(void *baton,
732 const svn_string_t *value)
734 struct node_baton *nb = baton;
735 struct revision_baton *rb = nb->rb;
736 struct parse_baton *pb = rb->pb;
738 /* If we're skipping this revision, we're done here. */
742 if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
744 svn_string_t *renumbered_mergeinfo;
745 /* ### Need to cast away const. We cannot change the declaration of
746 * ### this function since it is part of svn_repos_parse_fns2_t. */
747 svn_string_t *prop_val = (svn_string_t *)value;
749 /* Tolerate mergeinfo with "\r\n" line endings because some
750 dumpstream sources might contain as much. If so normalize
751 the line endings to '\n' and make a notification to
752 PARSE_BATON->FEEDBACK_STREAM that we have made this
754 if (strstr(prop_val->data, "\r"))
756 const char *prop_eol_normalized;
758 SVN_ERR(svn_subst_translate_cstring2(prop_val->data,
759 &prop_eol_normalized,
760 "\n", /* translate to LF */
761 FALSE, /* no repair */
762 NULL, /* no keywords */
763 FALSE, /* no expansion */
765 prop_val->data = prop_eol_normalized;
766 prop_val->len = strlen(prop_eol_normalized);
770 pb->notify->action = svn_repos_notify_load_normalized_mergeinfo;
771 pb->notify_func(pb->notify_baton, pb->notify, nb->pool);
775 /* Renumber mergeinfo as appropriate. */
776 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, prop_val, rb,
778 value = renumbered_mergeinfo;
781 /* Prefix the merge source paths with PB->parent_dir. */
782 /* ASSUMPTION: All source paths are included in the dump stream. */
783 svn_string_t *mergeinfo_val;
784 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
785 pb->parent_dir, nb->pool));
786 value = mergeinfo_val;
790 return change_node_prop(rb->txn_root, nb->path, name, value,
791 pb->validate_props, nb->pool);
796 delete_node_property(void *baton,
799 struct node_baton *nb = baton;
800 struct revision_baton *rb = nb->rb;
802 /* If we're skipping this revision, we're done here. */
806 return change_node_prop(rb->txn_root, nb->path, name, NULL,
807 rb->pb->validate_props, nb->pool);
812 remove_node_props(void *baton)
814 struct node_baton *nb = baton;
815 struct revision_baton *rb = nb->rb;
816 apr_hash_t *proplist;
817 apr_hash_index_t *hi;
819 /* If we're skipping this revision, we're done here. */
823 SVN_ERR(svn_fs_node_proplist(&proplist,
824 rb->txn_root, nb->path, nb->pool));
826 for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
830 apr_hash_this(hi, &key, NULL, NULL);
831 SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
832 rb->pb->validate_props, nb->pool));
840 apply_textdelta(svn_txdelta_window_handler_t *handler,
841 void **handler_baton,
844 struct node_baton *nb = node_baton;
845 struct revision_baton *rb = nb->rb;
847 /* If we're skipping this revision, we're done here. */
854 return svn_fs_apply_textdelta(handler, handler_baton,
855 rb->txn_root, nb->path,
856 svn_checksum_to_cstring(nb->base_checksum,
858 svn_checksum_to_cstring(nb->result_checksum,
865 set_fulltext(svn_stream_t **stream,
868 struct node_baton *nb = node_baton;
869 struct revision_baton *rb = nb->rb;
871 /* If we're skipping this revision, we're done here. */
878 return svn_fs_apply_text(stream,
879 rb->txn_root, nb->path,
880 svn_checksum_to_cstring(nb->result_checksum,
887 close_node(void *baton)
889 struct node_baton *nb = baton;
890 struct revision_baton *rb = nb->rb;
891 struct parse_baton *pb = rb->pb;
893 /* If we're skipping this revision, we're done here. */
899 pb->notify->action = svn_repos_notify_load_node_done;
900 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
908 close_revision(void *baton)
910 struct revision_baton *rb = baton;
911 struct parse_baton *pb = rb->pb;
912 const char *conflict_msg = NULL;
913 svn_revnum_t committed_rev;
915 const char *txn_name = NULL;
916 apr_hash_t *hooks_env;
918 /* If we're skipping this revision or it has an invalid revision
919 number, we're done here. */
920 if (rb->skipped || (rb->rev <= 0))
923 /* Get the txn name and hooks environment if they will be needed. */
924 if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
926 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
927 rb->pool, rb->pool));
929 err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
932 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
933 return svn_error_trace(err);
937 /* Run the pre-commit hook, if so commanded. */
938 if (pb->use_pre_commit_hook)
940 err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
944 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
945 return svn_error_trace(err);
950 err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
951 if (SVN_IS_VALID_REVNUM(committed_rev))
955 /* ### Log any error, but better yet is to rev
956 ### close_revision()'s API to allow both committed_rev and err
957 ### to be returned, see #3768. */
958 svn_error_clear(err);
963 svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
965 return svn_error_quick_wrap(err, conflict_msg);
967 return svn_error_trace(err);
970 /* Run post-commit hook, if so commanded. */
971 if (pb->use_post_commit_hook)
973 if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
974 committed_rev, txn_name,
976 return svn_error_create
977 (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
978 _("Commit succeeded, but post-commit hook failed"));
981 /* After a successful commit, must record the dump-rev -> in-repos-rev
982 mapping, so that copyfrom instructions in the dump file can look up the
983 correct repository revision to copy from. */
984 set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
986 /* If the incoming dump stream has non-contiguous revisions (e.g. from
987 using svndumpfilter --drop-empty-revs without --renumber-revs) then
988 we must account for the missing gaps in PB->REV_MAP. Otherwise we
989 might not be able to map all mergeinfo source revisions to the correct
990 revisions in the target repos. */
991 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
992 && (rb->rev != pb->last_rev_mapped + 1))
996 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
998 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1002 /* Update our "last revision mapped". */
1003 pb->last_rev_mapped = rb->rev;
1005 /* Deltify the predecessors of paths changed in this revision. */
1006 SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1008 /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
1009 current clock-time. We don't want that, we want to preserve
1010 history exactly. Good thing revision props aren't versioned!
1011 Note that if rb->datestamp is NULL, that's fine -- if the dump
1012 data doesn't carry a datestamp, we want to preserve that fact in
1014 SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
1015 rb->datestamp, pb->validate_props, rb->pool));
1017 if (pb->notify_func)
1019 pb->notify->action = svn_repos_notify_load_txn_committed;
1020 pb->notify->new_revision = committed_rev;
1021 pb->notify->old_revision = ((committed_rev == rb->rev)
1022 ? SVN_INVALID_REVNUM
1024 pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
1027 return SVN_NO_ERROR;
1031 /*----------------------------------------------------------------------*/
1033 /** The public routines **/
1037 svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
1040 svn_revnum_t start_rev,
1041 svn_revnum_t end_rev,
1042 svn_boolean_t use_history,
1043 svn_boolean_t validate_props,
1044 enum svn_repos_load_uuid uuid_action,
1045 const char *parent_dir,
1046 svn_repos_notify_func_t notify_func,
1050 svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1051 struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1054 parent_dir = svn_relpath_canonicalize(parent_dir, pool);
1056 SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1057 SVN_IS_VALID_REVNUM(end_rev))
1058 || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1059 (! SVN_IS_VALID_REVNUM(end_rev))));
1060 if (SVN_IS_VALID_REVNUM(start_rev))
1061 SVN_ERR_ASSERT(start_rev <= end_rev);
1063 parser->magic_header_record = magic_header_record;
1064 parser->uuid_record = uuid_record;
1065 parser->new_revision_record = new_revision_record;
1066 parser->new_node_record = new_node_record;
1067 parser->set_revision_property = set_revision_property;
1068 parser->set_node_property = set_node_property;
1069 parser->remove_node_props = remove_node_props;
1070 parser->set_fulltext = set_fulltext;
1071 parser->close_node = close_node;
1072 parser->close_revision = close_revision;
1073 parser->delete_node_property = delete_node_property;
1074 parser->apply_textdelta = apply_textdelta;
1077 pb->fs = svn_repos_fs(repos);
1078 pb->use_history = use_history;
1079 pb->validate_props = validate_props;
1080 pb->notify_func = notify_func;
1081 pb->notify_baton = notify_baton;
1082 pb->notify = svn_repos_notify_create(svn_repos_notify_load_txn_start, pool);
1083 pb->uuid_action = uuid_action;
1084 pb->parent_dir = parent_dir;
1086 pb->rev_map = apr_hash_make(pool);
1087 pb->oldest_old_rev = SVN_INVALID_REVNUM;
1088 pb->last_rev_mapped = SVN_INVALID_REVNUM;
1089 pb->start_rev = start_rev;
1090 pb->end_rev = end_rev;
1092 *callbacks = parser;
1094 return SVN_NO_ERROR;
1100 svn_repos_load_fs4(svn_repos_t *repos,
1101 svn_stream_t *dumpstream,
1102 svn_revnum_t start_rev,
1103 svn_revnum_t end_rev,
1104 enum svn_repos_load_uuid uuid_action,
1105 const char *parent_dir,
1106 svn_boolean_t use_pre_commit_hook,
1107 svn_boolean_t use_post_commit_hook,
1108 svn_boolean_t validate_props,
1109 svn_repos_notify_func_t notify_func,
1111 svn_cancel_func_t cancel_func,
1115 const svn_repos_parse_fns3_t *parser;
1117 struct parse_baton *pb;
1119 /* This is really simple. */
1121 SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
1124 TRUE, /* look for copyfrom revs */
1132 /* Heh. We know this is a parse_baton. This file made it. So
1133 cast away, and set our hook booleans. */
1135 pb->use_pre_commit_hook = use_pre_commit_hook;
1136 pb->use_post_commit_hook = use_post_commit_hook;
1138 return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1139 cancel_func, cancel_baton, pool);