2 * load_editor.c: The svn_delta_editor_t editor used by svnrdump to
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
25 #include "svn_cmdline.h"
26 #include "svn_pools.h"
27 #include "svn_delta.h"
28 #include "svn_repos.h"
29 #include "svn_props.h"
32 #include "svn_subst.h"
34 #include "svn_private_config.h"
35 #include "private/svn_repos_private.h"
36 #include "private/svn_ra_private.h"
37 #include "private/svn_mergeinfo_private.h"
38 #include "private/svn_fspath.h"
42 #define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
45 #define LDR_DBG(x) SVN_DBG(x)
47 #define LDR_DBG(x) while(0)
53 * General baton used by the parser functions.
57 /* Commit editor and baton used to transfer loaded revisions to
58 the target repository. */
59 const svn_delta_editor_t *commit_editor;
60 void *commit_edit_baton;
62 /* RA session(s) for committing to the target repository. */
63 svn_ra_session_t *session;
64 svn_ra_session_t *aux_session;
66 /* To bleep, or not to bleep? (What kind of question is that?) */
69 /* UUID found in the dumpstream, if any; NULL otherwise. */
72 /* Root URL of the target repository. */
75 /* The "parent directory" of the target repository in which to load.
76 (This is essentially the difference between ROOT_URL and
77 SESSION's url, and roughly equivalent to the 'svnadmin load
78 --parent-dir' option.) */
79 const char *parent_dir;
81 /* A mapping of svn_revnum_t * dump stream revisions to their
82 corresponding svn_revnum_t * target repository revisions. */
83 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
84 ### for discussion about improving the memory costs of this mapping. */
87 /* The most recent (youngest) revision from the dump stream mapped in
88 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
89 svn_revnum_t last_rev_mapped;
91 /* The oldest revision loaded from the dump stream, or
92 SVN_INVALID_REVNUM if none have been loaded. */
93 svn_revnum_t oldest_dumpstream_rev;
97 * Use to wrap the dir_context_t in commit.c so we can keep track of
98 * depth, relpath and parent for open_directory and close_directory.
100 struct directory_baton
105 struct directory_baton *parent;
109 * Baton used to represent a node; to be used by the parser
110 * functions. Contains a link to the revision baton.
115 svn_node_kind_t kind;
116 enum svn_node_action action;
118 svn_revnum_t copyfrom_rev;
119 const char *copyfrom_path;
122 const char *base_checksum;
124 struct revision_baton *rb;
128 * Baton used to represet a revision; used by the parser
129 * functions. Contains a link to the parser baton.
131 struct revision_baton
134 apr_hash_t *revprop_table;
135 apr_int32_t rev_offset;
137 const svn_string_t *datestamp;
138 const svn_string_t *author;
140 struct parse_baton *pb;
141 struct directory_baton *db;
147 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
148 anything added to the hash is allocated in the hash's pool. */
150 set_revision_mapping(apr_hash_t *rev_map,
151 svn_revnum_t from_rev,
154 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
155 sizeof(svn_revnum_t) * 2);
156 mapped_revs[0] = from_rev;
157 mapped_revs[1] = to_rev;
158 apr_hash_set(rev_map, mapped_revs,
159 sizeof(svn_revnum_t), mapped_revs + 1);
162 /* Return the revision to which FROM_REV maps in REV_MAP, or
163 SVN_INVALID_REVNUM if no such mapping exists. */
165 get_revision_mapping(apr_hash_t *rev_map,
166 svn_revnum_t from_rev)
168 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
170 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
174 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with
175 PARENT_DIR, and return it in *MERGEINFO_VAL. */
176 /* ### FIXME: Consider somehow sharing code with
177 ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */
179 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
180 const svn_string_t *mergeinfo_orig,
181 const char *parent_dir,
184 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
185 apr_hash_index_t *hi;
188 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
189 prefixed_mergeinfo = apr_hash_make(pool);
190 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
193 const char *path, *merge_source;
195 apr_hash_this(hi, &key, NULL, &rangelist);
196 merge_source = svn_relpath_canonicalize(key, pool);
198 /* The svn:mergeinfo property syntax demands a repos abspath */
199 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
202 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
204 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
208 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
209 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
210 (allocated from POOL). */
211 /* ### FIXME: Consider somehow sharing code with
212 ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */
214 renumber_mergeinfo_revs(svn_string_t **final_val,
215 const svn_string_t *initial_val,
216 struct revision_baton *rb,
219 apr_pool_t *subpool = svn_pool_create(pool);
220 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
221 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
222 apr_hash_index_t *hi;
224 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
227 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
228 Remove mergeinfo older than the oldest revision in the dump stream
229 and adjust its revisions by the difference between the head rev of
230 the target repository and the current dump stream rev. */
231 if (rb->pb->oldest_dumpstream_rev > 1)
233 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
234 &predates_stream_mergeinfo, mergeinfo,
235 rb->pb->oldest_dumpstream_rev - 1, 0,
236 TRUE, subpool, subpool));
237 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
238 &mergeinfo, mergeinfo,
239 rb->pb->oldest_dumpstream_rev - 1, 0,
240 FALSE, subpool, subpool));
241 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
242 &predates_stream_mergeinfo,
243 predates_stream_mergeinfo,
244 -rb->rev_offset, subpool, subpool));
248 predates_stream_mergeinfo = NULL;
251 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
253 svn_rangelist_t *rangelist;
254 struct parse_baton *pb = rb->pb;
260 apr_hash_this(hi, &path, &pathlen, &val);
263 /* Possibly renumber revisions in merge source's rangelist. */
264 for (i = 0; i < rangelist->nelts; i++)
266 svn_revnum_t rev_from_map;
267 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
268 svn_merge_range_t *);
269 rev_from_map = get_revision_mapping(pb->rev_map, range->start);
270 if (SVN_IS_VALID_REVNUM(rev_from_map))
272 range->start = rev_from_map;
274 else if (range->start == pb->oldest_dumpstream_rev - 1)
276 /* Since the start revision of svn_merge_range_t are not
277 inclusive there is one possible valid start revision that
278 won't be found in the PB->REV_MAP mapping of load stream
279 revsions to loaded revisions: The revision immediately
280 preceeding the oldest revision from the load stream.
281 This is a valid revision for mergeinfo, but not a valid
282 copy from revision (which PB->REV_MAP also maps for) so it
283 will never be in the mapping.
285 If that is what we have here, then find the mapping for the
286 oldest rev from the load stream and subtract 1 to get the
287 renumbered, non-inclusive, start revision. */
288 rev_from_map = get_revision_mapping(pb->rev_map,
289 pb->oldest_dumpstream_rev);
290 if (SVN_IS_VALID_REVNUM(rev_from_map))
291 range->start = rev_from_map - 1;
295 /* If we can't remap the start revision then don't even bother
296 trying to remap the end revision. It's possible we might
297 actually succeed at the latter, which can result in invalid
298 mergeinfo with a start rev > end rev. If that gets into the
299 repository then a world of bustage breaks loose anytime that
300 bogus mergeinfo is parsed. See
301 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
306 rev_from_map = get_revision_mapping(pb->rev_map, range->end);
307 if (SVN_IS_VALID_REVNUM(rev_from_map))
308 range->end = rev_from_map;
310 apr_hash_set(final_mergeinfo, path, pathlen, rangelist);
313 if (predates_stream_mergeinfo)
315 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
319 SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
321 /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
322 or r1. However, svndumpfilter can be abused to produce r1 merge source
323 revs. So if we encounter any, then strip them out, no need to put them
324 into the load target. */
325 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
330 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
331 svn_pool_destroy(subpool);
338 commit_callback(const svn_commit_info_t *commit_info,
342 struct revision_baton *rb = baton;
343 struct parse_baton *pb = rb->pb;
345 /* ### Don't print directly; generate a notification. */
347 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
348 commit_info->revision));
350 /* Add the mapping of the dumpstream revision to the committed revision. */
351 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
353 /* If the incoming dump stream has non-contiguous revisions (e.g. from
354 using svndumpfilter --drop-empty-revs without --renumber-revs) then
355 we must account for the missing gaps in PB->REV_MAP. Otherwise we
356 might not be able to map all mergeinfo source revisions to the correct
357 revisions in the target repos. */
358 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
359 && (rb->rev != pb->last_rev_mapped + 1))
363 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
365 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
369 /* Update our "last revision mapped". */
370 pb->last_rev_mapped = rb->rev;
375 /* Implements `svn_ra__lock_retry_func_t'. */
377 lock_retry_func(void *baton,
378 const svn_string_t *reposlocktoken,
381 return svn_cmdline_printf(pool,
382 _("Failed to get lock on destination "
383 "repos, currently held by '%s'\n"),
384 reposlocktoken->data);
389 fetch_base_func(const char **filename,
392 svn_revnum_t base_revision,
393 apr_pool_t *result_pool,
394 apr_pool_t *scratch_pool)
396 struct revision_baton *rb = baton;
397 svn_stream_t *fstream;
400 if (! SVN_IS_VALID_REVNUM(base_revision))
401 base_revision = rb->rev - 1;
403 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
404 svn_io_file_del_on_pool_cleanup,
405 result_pool, scratch_pool));
407 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
408 fstream, NULL, NULL, scratch_pool);
409 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
411 svn_error_clear(err);
412 SVN_ERR(svn_stream_close(fstream));
418 return svn_error_trace(err);
420 SVN_ERR(svn_stream_close(fstream));
426 fetch_props_func(apr_hash_t **props,
429 svn_revnum_t base_revision,
430 apr_pool_t *result_pool,
431 apr_pool_t *scratch_pool)
433 struct revision_baton *rb = baton;
434 svn_node_kind_t node_kind;
436 if (! SVN_IS_VALID_REVNUM(base_revision))
437 base_revision = rb->rev - 1;
439 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
440 &node_kind, scratch_pool));
442 if (node_kind == svn_node_file)
444 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
445 NULL, NULL, props, result_pool));
447 else if (node_kind == svn_node_dir)
449 apr_array_header_t *tmp_props;
451 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
452 base_revision, 0 /* Dirent fields */,
454 tmp_props = svn_prop_hash_to_array(*props, result_pool);
455 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
457 *props = svn_prop_array_to_hash(tmp_props, result_pool);
461 *props = apr_hash_make(result_pool);
468 fetch_kind_func(svn_node_kind_t *kind,
471 svn_revnum_t base_revision,
472 apr_pool_t *scratch_pool)
474 struct revision_baton *rb = baton;
476 if (! SVN_IS_VALID_REVNUM(base_revision))
477 base_revision = rb->rev - 1;
479 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
480 kind, scratch_pool));
485 static svn_delta_shim_callbacks_t *
486 get_shim_callbacks(struct revision_baton *rb,
489 svn_delta_shim_callbacks_t *callbacks =
490 svn_delta_shim_callbacks_default(pool);
492 callbacks->fetch_props_func = fetch_props_func;
493 callbacks->fetch_kind_func = fetch_kind_func;
494 callbacks->fetch_base_func = fetch_base_func;
495 callbacks->fetch_baton = rb;
500 /* Acquire a lock (of sorts) on the repository associated with the
501 * given RA SESSION. This lock is just a revprop change attempt in a
502 * time-delay loop. This function is duplicated by svnsync in
505 * ### TODO: Make this function more generic and
506 * expose it through a header for use by other Subversion
507 * applications to avoid duplication.
510 get_lock(const svn_string_t **lock_string_p,
511 svn_ra_session_t *session,
512 svn_cancel_func_t cancel_func,
516 svn_boolean_t be_atomic;
518 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
519 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
523 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
525 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
526 _("Target server does not support atomic revision "
527 "property edits; consider upgrading it to 1.7."));
528 svn_handle_warning2(stderr, err, "svnrdump: ");
529 svn_error_clear(err);
532 return svn_ra__get_operational_lock(lock_string_p, NULL, session,
533 SVNRDUMP_PROP_LOCK, FALSE,
534 10 /* retries */, lock_retry_func, NULL,
535 cancel_func, cancel_baton, pool);
539 new_revision_record(void **revision_baton,
544 struct revision_baton *rb;
545 struct parse_baton *pb;
546 apr_hash_index_t *hi;
547 svn_revnum_t head_rev;
549 rb = apr_pcalloc(pool, sizeof(*rb));
551 rb->pool = svn_pool_create(pool);
554 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
556 const char *hname = svn__apr_hash_index_key(hi);
557 const char *hval = svn__apr_hash_index_val(hi);
559 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
560 rb->rev = atoi(hval);
563 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
565 /* FIXME: This is a lame fallback loading multiple segments of dump in
566 several separate operations. It is highly susceptible to race conditions.
567 Calculate the revision 'offset' for finding copyfrom sources.
568 It might be positive or negative. */
569 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
571 /* Stash the oldest (non-zero) dumpstream revision seen. */
572 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
573 pb->oldest_dumpstream_rev = rb->rev;
575 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
576 them to be created in new_node_record */
577 rb->pb->commit_editor = NULL;
578 rb->pb->commit_edit_baton = NULL;
579 rb->revprop_table = apr_hash_make(rb->pool);
581 *revision_baton = rb;
586 magic_header_record(int version,
594 uuid_record(const char *uuid,
598 struct parse_baton *pb;
600 pb->uuid = apr_pstrdup(pool, uuid);
605 new_node_record(void **node_baton,
607 void *revision_baton,
610 struct revision_baton *rb = revision_baton;
611 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
612 void *commit_edit_baton = rb->pb->commit_edit_baton;
613 struct node_baton *nb;
614 struct directory_baton *child_db;
615 apr_hash_index_t *hi;
617 char *relpath_compose;
618 const char *nb_dirname;
620 nb = apr_pcalloc(rb->pool, sizeof(*nb));
623 nb->copyfrom_path = NULL;
624 nb->copyfrom_rev = SVN_INVALID_REVNUM;
626 /* If the creation of commit_editor is pending, create it now and
627 open_root on it; also create a top-level directory baton. */
631 /* The revprop_table should have been filled in with important
632 information like svn:log in set_revision_property. We can now
633 use it all this information to create our commit_editor. But
634 first, clear revprops that we aren't allowed to set with the
635 commit_editor. We'll set them separately using the RA API
636 after closing the editor (see close_revision). */
638 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
639 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
641 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
642 get_shim_callbacks(rb, rb->pool)));
643 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
644 &commit_edit_baton, rb->revprop_table,
645 commit_callback, revision_baton,
646 NULL, FALSE, rb->pool));
648 rb->pb->commit_editor = commit_editor;
649 rb->pb->commit_edit_baton = commit_edit_baton;
651 SVN_ERR(commit_editor->open_root(commit_edit_baton,
652 rb->rev - rb->rev_offset - 1,
653 rb->pool, &child_baton));
655 LDR_DBG(("Opened root %p\n", child_baton));
657 /* child_db corresponds to the root directory baton here */
658 child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
659 child_db->baton = child_baton;
661 child_db->relpath = "";
662 child_db->parent = NULL;
666 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
668 const char *hname = svn__apr_hash_index_key(hi);
669 const char *hval = svn__apr_hash_index_val(hi);
671 /* Parse the different kinds of headers we can encounter and
672 stuff them into the node_baton for writing later */
673 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
674 nb->path = apr_pstrdup(rb->pool, hval);
675 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
676 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
677 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
679 if (strcmp(hval, "add") == 0)
680 nb->action = svn_node_action_add;
681 if (strcmp(hval, "change") == 0)
682 nb->action = svn_node_action_change;
683 if (strcmp(hval, "delete") == 0)
684 nb->action = svn_node_action_delete;
685 if (strcmp(hval, "replace") == 0)
686 nb->action = svn_node_action_replace;
688 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
689 nb->base_checksum = apr_pstrdup(rb->pool, hval);
690 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
691 nb->copyfrom_rev = atoi(hval);
692 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
693 nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
696 nb_dirname = svn_relpath_dirname(nb->path, pool);
697 if (svn_path_compare_paths(nb_dirname,
698 rb->db->relpath) != 0)
701 apr_size_t residual_close_count;
702 apr_array_header_t *residual_open_path;
706 /* Before attempting to handle the action, call open_directory
707 for all the path components and set the directory baton
710 svn_relpath_get_longest_ancestor(nb_dirname,
711 rb->db->relpath, pool);
712 residual_close_count =
713 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
716 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
719 /* First close all as many directories as there are after
720 skip_ancestor, and then open fresh directories */
721 for (n = 0; n < residual_close_count; n ++)
723 /* Don't worry about destroying the actual rb->db object,
724 since the pool we're using has the lifetime of one
726 LDR_DBG(("Closing dir %p\n", rb->db->baton));
727 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
728 rb->db = rb->db->parent;
731 for (i = 0; i < residual_open_path->nelts; i ++)
734 svn_relpath_join(rb->db->relpath,
735 APR_ARRAY_IDX(residual_open_path, i, const char *),
737 SVN_ERR(commit_editor->open_directory(relpath_compose,
739 rb->rev - rb->rev_offset - 1,
740 rb->pool, &child_baton));
741 LDR_DBG(("Opened dir %p\n", child_baton));
742 child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
743 child_db->baton = child_baton;
744 child_db->depth = rb->db->depth + 1;
745 child_db->relpath = relpath_compose;
746 child_db->parent = rb->db;
751 /* Fix up the copyfrom information in light of mapped revisions and
752 non-root load targets, and convert copyfrom path into a full
754 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
756 svn_revnum_t copyfrom_rev;
758 /* Try to find the copyfrom revision in the revision map;
759 failing that, fall back to the revision offset approach. */
760 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
761 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
762 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
764 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
765 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
766 _("Relative source revision %ld is not"
767 " available in current repository"),
770 nb->copyfrom_rev = copyfrom_rev;
772 if (rb->pb->parent_dir)
773 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
774 nb->copyfrom_path, rb->pool);
775 nb->copyfrom_path = svn_path_url_add_component2(rb->pb->root_url,
783 case svn_node_action_delete:
784 case svn_node_action_replace:
785 LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton));
786 SVN_ERR(commit_editor->delete_entry(nb->path, rb->rev - rb->rev_offset,
787 rb->db->baton, rb->pool));
788 if (nb->action == svn_node_action_delete)
792 case svn_node_action_add:
796 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
799 rb->pool, &(nb->file_baton)));
800 LDR_DBG(("Added file %s to dir %p as %p\n",
801 nb->path, rb->db->baton, nb->file_baton));
804 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
807 rb->pool, &child_baton));
808 LDR_DBG(("Added dir %s to dir %p as %p\n",
809 nb->path, rb->db->baton, child_baton));
810 child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
811 child_db->baton = child_baton;
812 child_db->depth = rb->db->depth + 1;
813 child_db->relpath = apr_pstrdup(rb->pool, nb->path);
814 child_db->parent = rb->db;
821 case svn_node_action_change:
825 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
826 SVN_INVALID_REVNUM, rb->pool,
830 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
831 rb->rev - rb->rev_offset - 1,
832 rb->pool, &child_baton));
833 child_db = apr_pcalloc(rb->pool, sizeof(*child_db));
834 child_db->baton = child_baton;
835 child_db->depth = rb->db->depth + 1;
836 child_db->relpath = apr_pstrdup(rb->pool, nb->path);
837 child_db->parent = rb->db;
849 set_revision_property(void *baton,
851 const svn_string_t *value)
853 struct revision_baton *rb = baton;
855 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
857 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
861 svn_hash_sets(rb->revprop_table,
862 apr_pstrdup(rb->pool, name),
863 svn_string_dup(value, rb->pool));
865 else if (rb->rev_offset == -1)
867 /* Special case: set revision 0 properties directly (which is
868 safe because the commit_editor hasn't been created yet), but
869 only when loading into an 'empty' filesystem. */
870 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
871 name, NULL, value, rb->pool));
874 /* Remember any datestamp/ author that passes through (see comment
875 in close_revision). */
876 if (!strcmp(name, SVN_PROP_REVISION_DATE))
877 rb->datestamp = svn_string_dup(value, rb->pool);
878 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
879 rb->author = svn_string_dup(value, rb->pool);
885 set_node_property(void *baton,
887 const svn_string_t *value)
889 struct node_baton *nb = baton;
890 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
891 apr_pool_t *pool = nb->rb->pool;
893 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
895 svn_string_t *renumbered_mergeinfo;
896 svn_string_t prop_val;
898 /* Tolerate mergeinfo with "\r\n" line endings because some
899 dumpstream sources might contain as much. If so normalize
900 the line endings to '\n' and make a notification to
901 PARSE_BATON->FEEDBACK_STREAM that we have made this
903 if (strstr(value->data, "\r"))
905 const char *prop_eol_normalized;
907 SVN_ERR(svn_subst_translate_cstring2(value->data,
908 &prop_eol_normalized,
909 "\n", /* translate to LF */
910 FALSE, /* no repair */
911 NULL, /* no keywords */
912 FALSE, /* no expansion */
914 prop_val.data = prop_eol_normalized;
915 prop_val.len = strlen(prop_eol_normalized);
918 /* ### TODO: notify? */
921 /* Renumber mergeinfo as appropriate. */
922 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value,
924 value = renumbered_mergeinfo;
926 if (nb->rb->pb->parent_dir)
928 /* Prefix the merge source paths with PB->parent_dir. */
929 /* ASSUMPTION: All source paths are included in the dump stream. */
930 svn_string_t *mergeinfo_val;
931 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
932 nb->rb->pb->parent_dir, pool));
933 value = mergeinfo_val;
937 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
939 SVN_ERR(svn_repos__validate_prop(name, value, pool));
944 LDR_DBG(("Applying properties on %p\n", nb->file_baton));
945 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
949 LDR_DBG(("Applying properties on %p\n", nb->rb->db->baton));
950 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
960 delete_node_property(void *baton,
963 struct node_baton *nb = baton;
964 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
965 apr_pool_t *pool = nb->rb->pool;
967 SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
969 if (nb->kind == svn_node_file)
970 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
973 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
980 remove_node_props(void *baton)
982 struct node_baton *nb = baton;
983 apr_pool_t *pool = nb->rb->pool;
984 apr_hash_index_t *hi;
987 if ((nb->action == svn_node_action_add
988 || nb->action == svn_node_action_replace)
989 && ! SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
990 /* Add-without-history; no "old" properties to worry about. */
993 if (nb->kind == svn_node_file)
995 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, nb->path,
996 SVN_INVALID_REVNUM, NULL, NULL, &props, pool));
998 else /* nb->kind == svn_node_dir */
1000 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
1001 nb->path, SVN_INVALID_REVNUM, 0, pool));
1004 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1006 const char *name = svn__apr_hash_index_key(hi);
1007 svn_prop_kind_t kind = svn_property_kind2(name);
1009 if (kind == svn_prop_regular_kind)
1010 SVN_ERR(set_node_property(nb, name, NULL));
1013 return SVN_NO_ERROR;
1016 static svn_error_t *
1017 set_fulltext(svn_stream_t **stream,
1020 struct node_baton *nb = node_baton;
1021 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1022 svn_txdelta_window_handler_t handler;
1023 void *handler_baton;
1024 apr_pool_t *pool = nb->rb->pool;
1026 LDR_DBG(("Setting fulltext for %p\n", nb->file_baton));
1027 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
1028 pool, &handler, &handler_baton));
1029 *stream = svn_txdelta_target_push(handler, handler_baton,
1030 svn_stream_empty(pool), pool);
1031 return SVN_NO_ERROR;
1034 static svn_error_t *
1035 apply_textdelta(svn_txdelta_window_handler_t *handler,
1036 void **handler_baton,
1039 struct node_baton *nb = node_baton;
1040 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1041 apr_pool_t *pool = nb->rb->pool;
1043 LDR_DBG(("Applying textdelta to %p\n", nb->file_baton));
1044 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
1045 pool, handler, handler_baton));
1047 return SVN_NO_ERROR;
1050 static svn_error_t *
1051 close_node(void *baton)
1053 struct node_baton *nb = baton;
1054 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1056 /* Pass a file node closure through to the editor *unless* we
1057 deleted the file (which doesn't require us to open it). */
1058 if ((nb->kind == svn_node_file) && (nb->file_baton))
1060 LDR_DBG(("Closing file %p\n", nb->file_baton));
1061 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
1064 /* The svn_node_dir case is handled in close_revision */
1066 return SVN_NO_ERROR;
1069 static svn_error_t *
1070 close_revision(void *baton)
1072 struct revision_baton *rb = baton;
1073 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
1074 void *commit_edit_baton = rb->pb->commit_edit_baton;
1075 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
1077 /* Fake revision 0 */
1080 /* ### Don't print directly; generate a notification. */
1081 if (! rb->pb->quiet)
1082 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
1084 else if (commit_editor)
1086 /* Close all pending open directories, and then close the edit
1088 while (rb->db && rb->db->parent)
1090 LDR_DBG(("Closing dir %p\n", rb->db->baton));
1091 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
1092 rb->db = rb->db->parent;
1094 /* root dir's baton */
1095 LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
1096 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
1097 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1103 /* Legitimate revision with no node information */
1104 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
1105 &commit_edit_baton, rb->revprop_table,
1106 commit_callback, baton,
1107 NULL, FALSE, rb->pool));
1109 SVN_ERR(commit_editor->open_root(commit_edit_baton,
1110 rb->rev - rb->rev_offset - 1,
1111 rb->pool, &child_baton));
1113 LDR_DBG(("Opened root %p\n", child_baton));
1114 LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
1115 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
1116 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1119 /* svn_fs_commit_txn() rewrites the datestamp and author properties;
1120 we'll rewrite them again by hand after closing the commit_editor.
1121 The only time we don't do this is for revision 0 when loaded into
1122 a non-empty repository. */
1125 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
1127 else if (rb->rev_offset == -1)
1132 if (SVN_IS_VALID_REVNUM(committed_rev))
1134 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
1135 rb->datestamp, rb->pool));
1136 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1137 SVN_PROP_REVISION_DATE,
1138 NULL, rb->datestamp, rb->pool));
1139 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
1140 rb->author, rb->pool));
1141 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1142 SVN_PROP_REVISION_AUTHOR,
1143 NULL, rb->author, rb->pool));
1146 svn_pool_destroy(rb->pool);
1148 return SVN_NO_ERROR;
1152 svn_rdump__load_dumpstream(svn_stream_t *stream,
1153 svn_ra_session_t *session,
1154 svn_ra_session_t *aux_session,
1155 svn_boolean_t quiet,
1156 svn_cancel_func_t cancel_func,
1160 svn_repos_parse_fns3_t *parser;
1161 struct parse_baton *parse_baton;
1162 const svn_string_t *lock_string;
1163 svn_boolean_t be_atomic;
1165 const char *session_url, *root_url, *parent_dir;
1167 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1168 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1170 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1171 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1172 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1173 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1174 session_url, pool));
1176 parser = apr_pcalloc(pool, sizeof(*parser));
1177 parser->magic_header_record = magic_header_record;
1178 parser->uuid_record = uuid_record;
1179 parser->new_revision_record = new_revision_record;
1180 parser->new_node_record = new_node_record;
1181 parser->set_revision_property = set_revision_property;
1182 parser->set_node_property = set_node_property;
1183 parser->delete_node_property = delete_node_property;
1184 parser->remove_node_props = remove_node_props;
1185 parser->set_fulltext = set_fulltext;
1186 parser->apply_textdelta = apply_textdelta;
1187 parser->close_node = close_node;
1188 parser->close_revision = close_revision;
1190 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1191 parse_baton->session = session;
1192 parse_baton->aux_session = aux_session;
1193 parse_baton->quiet = quiet;
1194 parse_baton->root_url = root_url;
1195 parse_baton->parent_dir = parent_dir;
1196 parse_baton->rev_map = apr_hash_make(pool);
1197 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1198 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1200 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1201 cancel_func, cancel_baton, pool);
1203 /* If all goes well, or if we're cancelled cleanly, don't leave a
1204 stray lock behind. */
1205 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1206 err = svn_error_compose_create(
1207 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,