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"
44 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
47 #define LDR_DBG(x) SVN_DBG(x)
49 #define LDR_DBG(x) while(0)
55 * General baton used by the parser functions.
59 /* Commit editor and baton used to transfer loaded revisions to
60 the target repository. */
61 const svn_delta_editor_t *commit_editor;
62 void *commit_edit_baton;
64 /* RA session(s) for committing to the target repository. */
65 svn_ra_session_t *session;
66 svn_ra_session_t *aux_session;
68 /* To bleep, or not to bleep? (What kind of question is that?) */
71 /* UUID found in the dumpstream, if any; NULL otherwise. */
74 /* Root URL of the target repository. */
77 /* The "parent directory" of the target repository in which to load.
78 (This is essentially the difference between ROOT_URL and
79 SESSION's url, and roughly equivalent to the 'svnadmin load
80 --parent-dir' option.) */
81 const char *parent_dir;
83 /* A mapping of svn_revnum_t * dump stream revisions to their
84 corresponding svn_revnum_t * target repository revisions. */
85 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
86 ### for discussion about improving the memory costs of this mapping. */
89 /* The most recent (youngest) revision from the dump stream mapped in
90 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
91 svn_revnum_t last_rev_mapped;
93 /* The oldest revision loaded from the dump stream, or
94 SVN_INVALID_REVNUM if none have been loaded. */
95 svn_revnum_t oldest_dumpstream_rev;
99 * Use to wrap the dir_context_t in commit.c so we can keep track of
100 * depth, relpath and parent for open_directory and close_directory.
102 struct directory_baton
108 /* The copy-from source of this directory, no matter whether it is
109 copied explicitly (the root node of a copy) or implicitly (being an
110 existing child of a copied directory). For a node that is newly
111 added (without history), even inside a copied parent, these are
112 NULL and SVN_INVALID_REVNUM. */
113 const char *copyfrom_path;
114 svn_revnum_t copyfrom_rev;
116 struct directory_baton *parent;
120 * Baton used to represent a node; to be used by the parser
121 * functions. Contains a link to the revision baton.
126 svn_node_kind_t kind;
127 enum svn_node_action action;
129 /* Is this directory explicitly added? If not, then it already existed
130 or is a child of a copy. */
131 svn_boolean_t is_added;
133 svn_revnum_t copyfrom_rev;
134 const char *copyfrom_path;
135 const char *copyfrom_url;
138 const char *base_checksum;
140 /* (const char *name) -> (svn_prop_t *) */
141 apr_hash_t *prop_changes;
143 struct revision_baton *rb;
147 * Baton used to represet a revision; used by the parser
148 * functions. Contains a link to the parser baton.
150 struct revision_baton
153 apr_hash_t *revprop_table;
154 apr_int32_t rev_offset;
156 const svn_string_t *datestamp;
157 const svn_string_t *author;
159 struct parse_baton *pb;
160 struct directory_baton *db;
166 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
167 anything added to the hash is allocated in the hash's pool. */
169 set_revision_mapping(apr_hash_t *rev_map,
170 svn_revnum_t from_rev,
173 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
174 sizeof(svn_revnum_t) * 2);
175 mapped_revs[0] = from_rev;
176 mapped_revs[1] = to_rev;
177 apr_hash_set(rev_map, mapped_revs,
178 sizeof(svn_revnum_t), mapped_revs + 1);
181 /* Return the revision to which FROM_REV maps in REV_MAP, or
182 SVN_INVALID_REVNUM if no such mapping exists. */
184 get_revision_mapping(apr_hash_t *rev_map,
185 svn_revnum_t from_rev)
187 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
189 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
193 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with
194 PARENT_DIR, and return it in *MERGEINFO_VAL. */
195 /* ### FIXME: Consider somehow sharing code with
196 ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */
198 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
199 const svn_string_t *mergeinfo_orig,
200 const char *parent_dir,
203 apr_hash_t *prefixed_mergeinfo, *mergeinfo;
204 apr_hash_index_t *hi;
207 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
208 prefixed_mergeinfo = apr_hash_make(pool);
209 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
212 const char *path, *merge_source;
214 apr_hash_this(hi, &key, NULL, &rangelist);
215 merge_source = svn_relpath_canonicalize(key, pool);
217 /* The svn:mergeinfo property syntax demands a repos abspath */
218 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
221 svn_hash_sets(prefixed_mergeinfo, path, rangelist);
223 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
227 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
228 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
229 (allocated from POOL). */
230 /* ### FIXME: Consider somehow sharing code with
231 ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */
233 renumber_mergeinfo_revs(svn_string_t **final_val,
234 const svn_string_t *initial_val,
235 struct revision_baton *rb,
238 apr_pool_t *subpool = svn_pool_create(pool);
239 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
240 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
241 apr_hash_index_t *hi;
243 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
246 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
247 Remove mergeinfo older than the oldest revision in the dump stream
248 and adjust its revisions by the difference between the head rev of
249 the target repository and the current dump stream rev. */
250 if (rb->pb->oldest_dumpstream_rev > 1)
252 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
253 &predates_stream_mergeinfo, mergeinfo,
254 rb->pb->oldest_dumpstream_rev - 1, 0,
255 TRUE, subpool, subpool));
256 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
257 &mergeinfo, mergeinfo,
258 rb->pb->oldest_dumpstream_rev - 1, 0,
259 FALSE, subpool, subpool));
260 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
261 &predates_stream_mergeinfo,
262 predates_stream_mergeinfo,
263 -rb->rev_offset, subpool, subpool));
267 predates_stream_mergeinfo = NULL;
270 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
272 svn_rangelist_t *rangelist;
273 struct parse_baton *pb = rb->pb;
279 apr_hash_this(hi, &path, &pathlen, &val);
282 /* Possibly renumber revisions in merge source's rangelist. */
283 for (i = 0; i < rangelist->nelts; i++)
285 svn_revnum_t rev_from_map;
286 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
287 svn_merge_range_t *);
288 rev_from_map = get_revision_mapping(pb->rev_map, range->start);
289 if (SVN_IS_VALID_REVNUM(rev_from_map))
291 range->start = rev_from_map;
293 else if (range->start == pb->oldest_dumpstream_rev - 1)
295 /* Since the start revision of svn_merge_range_t are not
296 inclusive there is one possible valid start revision that
297 won't be found in the PB->REV_MAP mapping of load stream
298 revsions to loaded revisions: The revision immediately
299 preceeding the oldest revision from the load stream.
300 This is a valid revision for mergeinfo, but not a valid
301 copy from revision (which PB->REV_MAP also maps for) so it
302 will never be in the mapping.
304 If that is what we have here, then find the mapping for the
305 oldest rev from the load stream and subtract 1 to get the
306 renumbered, non-inclusive, start revision. */
307 rev_from_map = get_revision_mapping(pb->rev_map,
308 pb->oldest_dumpstream_rev);
309 if (SVN_IS_VALID_REVNUM(rev_from_map))
310 range->start = rev_from_map - 1;
314 /* If we can't remap the start revision then don't even bother
315 trying to remap the end revision. It's possible we might
316 actually succeed at the latter, which can result in invalid
317 mergeinfo with a start rev > end rev. If that gets into the
318 repository then a world of bustage breaks loose anytime that
319 bogus mergeinfo is parsed. See
320 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
325 rev_from_map = get_revision_mapping(pb->rev_map, range->end);
326 if (SVN_IS_VALID_REVNUM(rev_from_map))
327 range->end = rev_from_map;
329 apr_hash_set(final_mergeinfo, path, pathlen, rangelist);
332 if (predates_stream_mergeinfo)
334 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
338 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
340 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
341 svn_pool_destroy(subpool);
348 commit_callback(const svn_commit_info_t *commit_info,
352 struct revision_baton *rb = baton;
353 struct parse_baton *pb = rb->pb;
355 /* ### Don't print directly; generate a notification. */
357 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
358 commit_info->revision));
360 /* Add the mapping of the dumpstream revision to the committed revision. */
361 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
363 /* If the incoming dump stream has non-contiguous revisions (e.g. from
364 using svndumpfilter --drop-empty-revs without --renumber-revs) then
365 we must account for the missing gaps in PB->REV_MAP. Otherwise we
366 might not be able to map all mergeinfo source revisions to the correct
367 revisions in the target repos. */
368 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
369 && (rb->rev != pb->last_rev_mapped + 1))
373 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
375 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
379 /* Update our "last revision mapped". */
380 pb->last_rev_mapped = rb->rev;
385 /* Implements `svn_ra__lock_retry_func_t'. */
387 lock_retry_func(void *baton,
388 const svn_string_t *reposlocktoken,
391 return svn_cmdline_printf(pool,
392 _("Failed to get lock on destination "
393 "repos, currently held by '%s'\n"),
394 reposlocktoken->data);
399 fetch_base_func(const char **filename,
402 svn_revnum_t base_revision,
403 apr_pool_t *result_pool,
404 apr_pool_t *scratch_pool)
406 struct revision_baton *rb = baton;
407 svn_stream_t *fstream;
410 if (! SVN_IS_VALID_REVNUM(base_revision))
411 base_revision = rb->rev - 1;
413 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
414 svn_io_file_del_on_pool_cleanup,
415 result_pool, scratch_pool));
417 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
418 fstream, NULL, NULL, scratch_pool);
419 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
421 svn_error_clear(err);
422 SVN_ERR(svn_stream_close(fstream));
428 return svn_error_trace(err);
430 SVN_ERR(svn_stream_close(fstream));
436 fetch_props_func(apr_hash_t **props,
439 svn_revnum_t base_revision,
440 apr_pool_t *result_pool,
441 apr_pool_t *scratch_pool)
443 struct revision_baton *rb = baton;
444 svn_node_kind_t node_kind;
446 if (! SVN_IS_VALID_REVNUM(base_revision))
447 base_revision = rb->rev - 1;
449 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
450 &node_kind, scratch_pool));
452 if (node_kind == svn_node_file)
454 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
455 NULL, NULL, props, result_pool));
457 else if (node_kind == svn_node_dir)
459 apr_array_header_t *tmp_props;
461 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
462 base_revision, 0 /* Dirent fields */,
464 tmp_props = svn_prop_hash_to_array(*props, result_pool);
465 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
467 *props = svn_prop_array_to_hash(tmp_props, result_pool);
471 *props = apr_hash_make(result_pool);
478 fetch_kind_func(svn_node_kind_t *kind,
481 svn_revnum_t base_revision,
482 apr_pool_t *scratch_pool)
484 struct revision_baton *rb = baton;
486 if (! SVN_IS_VALID_REVNUM(base_revision))
487 base_revision = rb->rev - 1;
489 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
490 kind, scratch_pool));
495 static svn_delta_shim_callbacks_t *
496 get_shim_callbacks(struct revision_baton *rb,
499 svn_delta_shim_callbacks_t *callbacks =
500 svn_delta_shim_callbacks_default(pool);
502 callbacks->fetch_props_func = fetch_props_func;
503 callbacks->fetch_kind_func = fetch_kind_func;
504 callbacks->fetch_base_func = fetch_base_func;
505 callbacks->fetch_baton = rb;
510 /* Acquire a lock (of sorts) on the repository associated with the
511 * given RA SESSION. This lock is just a revprop change attempt in a
512 * time-delay loop. This function is duplicated by svnsync in
515 * ### TODO: Make this function more generic and
516 * expose it through a header for use by other Subversion
517 * applications to avoid duplication.
520 get_lock(const svn_string_t **lock_string_p,
521 svn_ra_session_t *session,
522 svn_cancel_func_t cancel_func,
526 svn_boolean_t be_atomic;
528 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
529 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
533 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
535 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
536 _("Target server does not support atomic revision "
537 "property edits; consider upgrading it to 1.7."));
538 svn_handle_warning2(stderr, err, "svnrdump: ");
539 svn_error_clear(err);
542 return svn_ra__get_operational_lock(lock_string_p, NULL, session,
543 SVNRDUMP_PROP_LOCK, FALSE,
544 10 /* retries */, lock_retry_func, NULL,
545 cancel_func, cancel_baton, pool);
549 new_revision_record(void **revision_baton,
554 struct revision_baton *rb;
555 struct parse_baton *pb;
556 apr_hash_index_t *hi;
557 svn_revnum_t head_rev;
559 rb = apr_pcalloc(pool, sizeof(*rb));
561 rb->pool = svn_pool_create(pool);
565 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
567 const char *hname = svn__apr_hash_index_key(hi);
568 const char *hval = svn__apr_hash_index_val(hi);
570 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
571 rb->rev = atoi(hval);
574 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
576 /* FIXME: This is a lame fallback loading multiple segments of dump in
577 several separate operations. It is highly susceptible to race conditions.
578 Calculate the revision 'offset' for finding copyfrom sources.
579 It might be positive or negative. */
580 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
582 /* Stash the oldest (non-zero) dumpstream revision seen. */
583 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
584 pb->oldest_dumpstream_rev = rb->rev;
586 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
587 them to be created in new_node_record */
588 rb->pb->commit_editor = NULL;
589 rb->pb->commit_edit_baton = NULL;
590 rb->revprop_table = apr_hash_make(rb->pool);
592 *revision_baton = rb;
597 magic_header_record(int version,
605 uuid_record(const char *uuid,
609 struct parse_baton *pb;
611 pb->uuid = apr_pstrdup(pool, uuid);
615 /* Push information about another directory onto the linked list RB->db.
617 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
618 * repository-relative path of this directory. IS_ADDED is true iff this
619 * directory is being added (with or without history). If added with
620 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
621 * are NULL/SVN_INVALID_REVNUM.
624 push_directory(struct revision_baton *rb,
627 svn_boolean_t is_added,
628 const char *copyfrom_path,
629 svn_revnum_t copyfrom_rev)
631 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
633 SVN_ERR_ASSERT_NO_RETURN(
634 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
636 /* If this node is an existing (not newly added) child of a copied node,
637 calculate where it was copied from. */
639 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
641 const char *name = svn_relpath_basename(relpath, NULL);
643 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
645 copyfrom_rev = rb->db->copyfrom_rev;
648 child_db->baton = child_baton;
649 child_db->relpath = relpath;
650 child_db->copyfrom_path = copyfrom_path;
651 child_db->copyfrom_rev = copyfrom_rev;
652 child_db->parent = rb->db;
657 new_node_record(void **node_baton,
659 void *revision_baton,
662 struct revision_baton *rb = revision_baton;
663 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
664 void *commit_edit_baton = rb->pb->commit_edit_baton;
665 struct node_baton *nb;
666 apr_hash_index_t *hi;
668 const char *nb_dirname;
670 nb = apr_pcalloc(rb->pool, sizeof(*nb));
672 nb->is_added = FALSE;
673 nb->copyfrom_path = NULL;
674 nb->copyfrom_url = NULL;
675 nb->copyfrom_rev = SVN_INVALID_REVNUM;
676 nb->prop_changes = apr_hash_make(rb->pool);
678 /* If the creation of commit_editor is pending, create it now and
679 open_root on it; also create a top-level directory baton. */
683 /* The revprop_table should have been filled in with important
684 information like svn:log in set_revision_property. We can now
685 use it all this information to create our commit_editor. But
686 first, clear revprops that we aren't allowed to set with the
687 commit_editor. We'll set them separately using the RA API
688 after closing the editor (see close_revision). */
690 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
691 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
693 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
694 get_shim_callbacks(rb, rb->pool)));
695 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
696 &commit_edit_baton, rb->revprop_table,
697 commit_callback, revision_baton,
698 NULL, FALSE, rb->pool));
700 rb->pb->commit_editor = commit_editor;
701 rb->pb->commit_edit_baton = commit_edit_baton;
703 SVN_ERR(commit_editor->open_root(commit_edit_baton,
704 rb->rev - rb->rev_offset - 1,
705 rb->pool, &child_baton));
707 LDR_DBG(("Opened root %p\n", child_baton));
709 /* child_baton corresponds to the root directory baton here */
710 push_directory(rb, child_baton, "", TRUE /*is_added*/,
711 NULL, SVN_INVALID_REVNUM);
714 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
716 const char *hname = svn__apr_hash_index_key(hi);
717 const char *hval = svn__apr_hash_index_val(hi);
719 /* Parse the different kinds of headers we can encounter and
720 stuff them into the node_baton for writing later */
721 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
722 nb->path = apr_pstrdup(rb->pool, hval);
723 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
724 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
725 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
727 if (strcmp(hval, "add") == 0)
728 nb->action = svn_node_action_add;
729 if (strcmp(hval, "change") == 0)
730 nb->action = svn_node_action_change;
731 if (strcmp(hval, "delete") == 0)
732 nb->action = svn_node_action_delete;
733 if (strcmp(hval, "replace") == 0)
734 nb->action = svn_node_action_replace;
736 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
737 nb->base_checksum = apr_pstrdup(rb->pool, hval);
738 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
739 nb->copyfrom_rev = atoi(hval);
740 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
741 nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
744 nb_dirname = svn_relpath_dirname(nb->path, pool);
745 if (svn_path_compare_paths(nb_dirname,
746 rb->db->relpath) != 0)
749 apr_size_t residual_close_count;
750 apr_array_header_t *residual_open_path;
754 /* Before attempting to handle the action, call open_directory
755 for all the path components and set the directory baton
758 svn_relpath_get_longest_ancestor(nb_dirname,
759 rb->db->relpath, pool);
760 residual_close_count =
761 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
764 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
767 /* First close all as many directories as there are after
768 skip_ancestor, and then open fresh directories */
769 for (n = 0; n < residual_close_count; n ++)
771 /* Don't worry about destroying the actual rb->db object,
772 since the pool we're using has the lifetime of one
774 LDR_DBG(("Closing dir %p\n", rb->db->baton));
775 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
776 rb->db = rb->db->parent;
779 for (i = 0; i < residual_open_path->nelts; i ++)
781 char *relpath_compose =
782 svn_relpath_join(rb->db->relpath,
783 APR_ARRAY_IDX(residual_open_path, i, const char *),
785 SVN_ERR(commit_editor->open_directory(relpath_compose,
787 rb->rev - rb->rev_offset - 1,
788 rb->pool, &child_baton));
789 LDR_DBG(("Opened dir %p\n", child_baton));
790 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
791 NULL, SVN_INVALID_REVNUM);
795 /* Fix up the copyfrom information in light of mapped revisions and
796 non-root load targets, and convert copyfrom path into a full
798 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
800 svn_revnum_t copyfrom_rev;
802 /* Try to find the copyfrom revision in the revision map;
803 failing that, fall back to the revision offset approach. */
804 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
805 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
806 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
808 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
809 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
810 _("Relative source revision %ld is not"
811 " available in current repository"),
814 nb->copyfrom_rev = copyfrom_rev;
816 if (rb->pb->parent_dir)
817 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
818 nb->copyfrom_path, rb->pool);
819 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
827 case svn_node_action_delete:
828 case svn_node_action_replace:
829 LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton));
830 SVN_ERR(commit_editor->delete_entry(nb->path,
831 rb->rev - rb->rev_offset - 1,
832 rb->db->baton, rb->pool));
833 if (nb->action == svn_node_action_delete)
837 case svn_node_action_add:
842 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
845 rb->pool, &(nb->file_baton)));
846 LDR_DBG(("Added file %s to dir %p as %p\n",
847 nb->path, rb->db->baton, nb->file_baton));
850 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
853 rb->pool, &child_baton));
854 LDR_DBG(("Added dir %s to dir %p as %p\n",
855 nb->path, rb->db->baton, child_baton));
856 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
857 nb->copyfrom_path, nb->copyfrom_rev);
863 case svn_node_action_change:
867 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
868 SVN_INVALID_REVNUM, rb->pool,
872 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
873 rb->rev - rb->rev_offset - 1,
874 rb->pool, &child_baton));
875 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
876 NULL, SVN_INVALID_REVNUM);
887 set_revision_property(void *baton,
889 const svn_string_t *value)
891 struct revision_baton *rb = baton;
893 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
895 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
899 svn_hash_sets(rb->revprop_table,
900 apr_pstrdup(rb->pool, name),
901 svn_string_dup(value, rb->pool));
903 else if (rb->rev_offset == -1)
905 /* Special case: set revision 0 properties directly (which is
906 safe because the commit_editor hasn't been created yet), but
907 only when loading into an 'empty' filesystem. */
908 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
909 name, NULL, value, rb->pool));
912 /* Remember any datestamp/ author that passes through (see comment
913 in close_revision). */
914 if (!strcmp(name, SVN_PROP_REVISION_DATE))
915 rb->datestamp = svn_string_dup(value, rb->pool);
916 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
917 rb->author = svn_string_dup(value, rb->pool);
923 set_node_property(void *baton,
925 const svn_string_t *value)
927 struct node_baton *nb = baton;
928 apr_pool_t *pool = nb->rb->pool;
931 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
933 svn_string_t *renumbered_mergeinfo;
934 svn_string_t prop_val;
936 /* Tolerate mergeinfo with "\r\n" line endings because some
937 dumpstream sources might contain as much. If so normalize
938 the line endings to '\n' and make a notification to
939 PARSE_BATON->FEEDBACK_STREAM that we have made this
941 if (strstr(value->data, "\r"))
943 const char *prop_eol_normalized;
945 SVN_ERR(svn_subst_translate_cstring2(value->data,
946 &prop_eol_normalized,
947 "\n", /* translate to LF */
948 FALSE, /* no repair */
949 NULL, /* no keywords */
950 FALSE, /* no expansion */
952 prop_val.data = prop_eol_normalized;
953 prop_val.len = strlen(prop_eol_normalized);
956 /* ### TODO: notify? */
959 /* Renumber mergeinfo as appropriate. */
960 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value,
962 value = renumbered_mergeinfo;
964 if (nb->rb->pb->parent_dir)
966 /* Prefix the merge source paths with PB->parent_dir. */
967 /* ASSUMPTION: All source paths are included in the dump stream. */
968 svn_string_t *mergeinfo_val;
969 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
970 nb->rb->pb->parent_dir, pool));
971 value = mergeinfo_val;
975 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
977 SVN_ERR(svn_repos__validate_prop(name, value, pool));
979 prop = apr_palloc(nb->rb->pool, sizeof (*prop));
980 prop->name = apr_pstrdup(pool, name);
981 prop->value = value ? svn_string_dup(value, pool) : NULL;
982 svn_hash_sets(nb->prop_changes, prop->name, prop);
988 delete_node_property(void *baton,
991 struct node_baton *nb = baton;
992 apr_pool_t *pool = nb->rb->pool;
995 SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
997 prop = apr_palloc(pool, sizeof (*prop));
998 prop->name = apr_pstrdup(pool, name);
1000 svn_hash_sets(nb->prop_changes, prop->name, prop);
1002 return SVN_NO_ERROR;
1005 /* Delete all the properties of the node, if any.
1007 * The commit editor doesn't have a method to delete a node's properties
1008 * without knowing what they are, so we have to first find out what
1009 * properties the node would have had. If it's copied (explicitly or
1010 * implicitly), we look at the copy source. If it's only being changed,
1011 * we look at the node's current path in the head revision.
1013 static svn_error_t *
1014 remove_node_props(void *baton)
1016 struct node_baton *nb = baton;
1017 struct revision_baton *rb = nb->rb;
1018 apr_pool_t *pool = nb->rb->pool;
1019 apr_hash_index_t *hi;
1021 const char *orig_path;
1022 svn_revnum_t orig_rev;
1024 /* Find the path and revision that has the node's original properties */
1025 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
1027 LDR_DBG(("using nb->copyfrom %s@%ld", nb->copyfrom_path, nb->copyfrom_rev));
1028 orig_path = nb->copyfrom_path;
1029 orig_rev = nb->copyfrom_rev;
1031 else if (!nb->is_added
1032 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
1034 /* If this is a dir, then it's described by rb->db;
1035 if this is a file, then it's a child of the dir in rb->db. */
1036 LDR_DBG(("using rb->db->copyfrom (k=%d) %s@%ld",
1037 nb->kind, rb->db->copyfrom_path, rb->db->copyfrom_rev));
1038 orig_path = (nb->kind == svn_node_dir)
1039 ? rb->db->copyfrom_path
1040 : svn_relpath_join(rb->db->copyfrom_path,
1041 svn_relpath_basename(nb->path, NULL),
1043 orig_rev = rb->db->copyfrom_rev;
1047 LDR_DBG(("using self.path@head %s@%ld", nb->path, SVN_INVALID_REVNUM));
1048 /* ### Should we query at a known, fixed, "head" revision number
1049 instead of passing SVN_INVALID_REVNUM and getting a moving target? */
1050 orig_path = nb->path;
1051 orig_rev = SVN_INVALID_REVNUM;
1053 LDR_DBG(("Trying %s@%ld", orig_path, orig_rev));
1055 if ((nb->action == svn_node_action_add
1056 || nb->action == svn_node_action_replace)
1057 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
1058 /* Add-without-history; no "old" properties to worry about. */
1059 return SVN_NO_ERROR;
1061 if (nb->kind == svn_node_file)
1063 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session,
1064 orig_path, orig_rev, NULL, NULL, &props, pool));
1066 else /* nb->kind == svn_node_dir */
1068 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
1069 orig_path, orig_rev, 0, pool));
1072 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1074 const char *name = svn__apr_hash_index_key(hi);
1075 svn_prop_kind_t kind = svn_property_kind2(name);
1077 if (kind == svn_prop_regular_kind)
1078 SVN_ERR(set_node_property(nb, name, NULL));
1081 return SVN_NO_ERROR;
1084 static svn_error_t *
1085 set_fulltext(svn_stream_t **stream,
1088 struct node_baton *nb = node_baton;
1089 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1090 svn_txdelta_window_handler_t handler;
1091 void *handler_baton;
1092 apr_pool_t *pool = nb->rb->pool;
1094 LDR_DBG(("Setting fulltext for %p\n", nb->file_baton));
1095 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
1096 pool, &handler, &handler_baton));
1097 *stream = svn_txdelta_target_push(handler, handler_baton,
1098 svn_stream_empty(pool), pool);
1099 return SVN_NO_ERROR;
1102 static svn_error_t *
1103 apply_textdelta(svn_txdelta_window_handler_t *handler,
1104 void **handler_baton,
1107 struct node_baton *nb = node_baton;
1108 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1109 apr_pool_t *pool = nb->rb->pool;
1111 LDR_DBG(("Applying textdelta to %p\n", nb->file_baton));
1112 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
1113 pool, handler, handler_baton));
1115 return SVN_NO_ERROR;
1118 static svn_error_t *
1119 close_node(void *baton)
1121 struct node_baton *nb = baton;
1122 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
1123 apr_pool_t *pool = nb->rb->pool;
1124 apr_hash_index_t *hi;
1126 for (hi = apr_hash_first(pool, nb->prop_changes);
1127 hi; hi = apr_hash_next(hi))
1129 const char *name = svn__apr_hash_index_key(hi);
1130 svn_prop_t *prop = svn__apr_hash_index_val(hi);
1135 SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
1136 name, prop->value, pool));
1139 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
1140 name, prop->value, pool));
1147 /* Pass a file node closure through to the editor *unless* we
1148 deleted the file (which doesn't require us to open it). */
1149 if ((nb->kind == svn_node_file) && (nb->file_baton))
1151 LDR_DBG(("Closing file %p\n", nb->file_baton));
1152 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
1155 /* The svn_node_dir case is handled in close_revision */
1157 return SVN_NO_ERROR;
1160 static svn_error_t *
1161 close_revision(void *baton)
1163 struct revision_baton *rb = baton;
1164 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
1165 void *commit_edit_baton = rb->pb->commit_edit_baton;
1166 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
1168 /* Fake revision 0 */
1171 /* ### Don't print directly; generate a notification. */
1172 if (! rb->pb->quiet)
1173 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
1175 else if (commit_editor)
1177 /* Close all pending open directories, and then close the edit
1179 while (rb->db && rb->db->parent)
1181 LDR_DBG(("Closing dir %p\n", rb->db->baton));
1182 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
1183 rb->db = rb->db->parent;
1185 /* root dir's baton */
1186 LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
1187 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
1188 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1194 /* Legitimate revision with no node information */
1195 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
1196 &commit_edit_baton, rb->revprop_table,
1197 commit_callback, baton,
1198 NULL, FALSE, rb->pool));
1200 SVN_ERR(commit_editor->open_root(commit_edit_baton,
1201 rb->rev - rb->rev_offset - 1,
1202 rb->pool, &child_baton));
1204 LDR_DBG(("Opened root %p\n", child_baton));
1205 LDR_DBG(("Closing edit on %p\n", commit_edit_baton));
1206 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
1207 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1210 /* svn_fs_commit_txn() rewrites the datestamp and author properties;
1211 we'll rewrite them again by hand after closing the commit_editor.
1212 The only time we don't do this is for revision 0 when loaded into
1213 a non-empty repository. */
1216 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
1218 else if (rb->rev_offset == -1)
1223 if (SVN_IS_VALID_REVNUM(committed_rev))
1225 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
1226 rb->datestamp, rb->pool));
1227 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1228 SVN_PROP_REVISION_DATE,
1229 NULL, rb->datestamp, rb->pool));
1230 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
1231 rb->author, rb->pool));
1232 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1233 SVN_PROP_REVISION_AUTHOR,
1234 NULL, rb->author, rb->pool));
1237 svn_pool_destroy(rb->pool);
1239 return SVN_NO_ERROR;
1243 svn_rdump__load_dumpstream(svn_stream_t *stream,
1244 svn_ra_session_t *session,
1245 svn_ra_session_t *aux_session,
1246 svn_boolean_t quiet,
1247 svn_cancel_func_t cancel_func,
1251 svn_repos_parse_fns3_t *parser;
1252 struct parse_baton *parse_baton;
1253 const svn_string_t *lock_string;
1254 svn_boolean_t be_atomic;
1256 const char *session_url, *root_url, *parent_dir;
1258 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1259 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1261 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1262 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1263 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1264 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1265 session_url, pool));
1267 parser = apr_pcalloc(pool, sizeof(*parser));
1268 parser->magic_header_record = magic_header_record;
1269 parser->uuid_record = uuid_record;
1270 parser->new_revision_record = new_revision_record;
1271 parser->new_node_record = new_node_record;
1272 parser->set_revision_property = set_revision_property;
1273 parser->set_node_property = set_node_property;
1274 parser->delete_node_property = delete_node_property;
1275 parser->remove_node_props = remove_node_props;
1276 parser->set_fulltext = set_fulltext;
1277 parser->apply_textdelta = apply_textdelta;
1278 parser->close_node = close_node;
1279 parser->close_revision = close_revision;
1281 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1282 parse_baton->session = session;
1283 parse_baton->aux_session = aux_session;
1284 parse_baton->quiet = quiet;
1285 parse_baton->root_url = root_url;
1286 parse_baton->parent_dir = parent_dir;
1287 parse_baton->rev_map = apr_hash_make(pool);
1288 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1289 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1291 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1292 cancel_func, cancel_baton, pool);
1294 /* If all goes well, or if we're cancelled cleanly, don't leave a
1295 stray lock behind. */
1296 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1297 err = svn_error_compose_create(
1298 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,