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))
48 * General baton used by the parser functions.
52 /* Commit editor and baton used to transfer loaded revisions to
53 the target repository. */
54 const svn_delta_editor_t *commit_editor;
55 void *commit_edit_baton;
57 /* RA session(s) for committing to the target repository. */
58 svn_ra_session_t *session;
59 svn_ra_session_t *aux_session;
61 /* To bleep, or not to bleep? (What kind of question is that?) */
64 /* Root URL of the target repository. */
67 /* The "parent directory" of the target repository in which to load.
68 (This is essentially the difference between ROOT_URL and
69 SESSION's url, and roughly equivalent to the 'svnadmin load
70 --parent-dir' option.) */
71 const char *parent_dir;
73 /* A mapping of svn_revnum_t * dump stream revisions to their
74 corresponding svn_revnum_t * target repository revisions. */
75 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
76 ### for discussion about improving the memory costs of this mapping. */
79 /* The most recent (youngest) revision from the dump stream mapped in
80 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
81 svn_revnum_t last_rev_mapped;
83 /* The oldest revision loaded from the dump stream, or
84 SVN_INVALID_REVNUM if none have been loaded. */
85 svn_revnum_t oldest_dumpstream_rev;
87 /* An hash containing specific revision properties to skip while
89 apr_hash_t *skip_revprops;
93 * Use to wrap the dir_context_t in commit.c so we can keep track of
94 * relpath and parent for open_directory and close_directory.
96 struct directory_baton
101 /* The copy-from source of this directory, no matter whether it is
102 copied explicitly (the root node of a copy) or implicitly (being an
103 existing child of a copied directory). For a node that is newly
104 added (without history), even inside a copied parent, these are
105 NULL and SVN_INVALID_REVNUM. */
106 const char *copyfrom_path;
107 svn_revnum_t copyfrom_rev;
109 struct directory_baton *parent;
113 * Baton used to represent a node; to be used by the parser
114 * functions. Contains a link to the revision baton.
119 svn_node_kind_t kind;
120 enum svn_node_action action;
122 /* Is this directory explicitly added? If not, then it already existed
123 or is a child of a copy. */
124 svn_boolean_t is_added;
126 svn_revnum_t copyfrom_rev;
127 const char *copyfrom_path;
128 const char *copyfrom_url;
131 const char *base_checksum;
133 /* (const char *name) -> (svn_prop_t *) */
134 apr_hash_t *prop_changes;
136 struct revision_baton *rb;
140 * Baton used to represet a revision; used by the parser
141 * functions. Contains a link to the parser baton.
143 struct revision_baton
146 apr_hash_t *revprop_table;
147 apr_int32_t rev_offset;
149 const svn_string_t *datestamp;
150 const svn_string_t *author;
152 struct parse_baton *pb;
153 struct directory_baton *db;
159 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
160 anything added to the hash is allocated in the hash's pool. */
162 set_revision_mapping(apr_hash_t *rev_map,
163 svn_revnum_t from_rev,
166 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
167 sizeof(svn_revnum_t) * 2);
168 mapped_revs[0] = from_rev;
169 mapped_revs[1] = to_rev;
170 apr_hash_set(rev_map, mapped_revs, sizeof(*mapped_revs), mapped_revs + 1);
173 /* Return the revision to which FROM_REV maps in REV_MAP, or
174 SVN_INVALID_REVNUM if no such mapping exists. */
176 get_revision_mapping(apr_hash_t *rev_map,
177 svn_revnum_t from_rev)
179 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
181 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
186 commit_callback(const svn_commit_info_t *commit_info,
190 struct revision_baton *rb = baton;
191 struct parse_baton *pb = rb->pb;
193 /* ### Don't print directly; generate a notification. */
195 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
196 commit_info->revision));
198 /* Add the mapping of the dumpstream revision to the committed revision. */
199 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
201 /* If the incoming dump stream has non-contiguous revisions (e.g. from
202 using svndumpfilter --drop-empty-revs without --renumber-revs) then
203 we must account for the missing gaps in PB->REV_MAP. Otherwise we
204 might not be able to map all mergeinfo source revisions to the correct
205 revisions in the target repos. */
206 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
207 && (rb->rev != pb->last_rev_mapped + 1))
211 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
213 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
217 /* Update our "last revision mapped". */
218 pb->last_rev_mapped = rb->rev;
223 /* Implements `svn_ra__lock_retry_func_t'. */
225 lock_retry_func(void *baton,
226 const svn_string_t *reposlocktoken,
229 return svn_cmdline_printf(pool,
230 _("Failed to get lock on destination "
231 "repos, currently held by '%s'\n"),
232 reposlocktoken->data);
237 fetch_base_func(const char **filename,
240 svn_revnum_t base_revision,
241 apr_pool_t *result_pool,
242 apr_pool_t *scratch_pool)
244 struct revision_baton *rb = baton;
245 svn_stream_t *fstream;
248 if (! SVN_IS_VALID_REVNUM(base_revision))
249 base_revision = rb->rev - 1;
251 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
252 svn_io_file_del_on_pool_cleanup,
253 result_pool, scratch_pool));
255 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
256 fstream, NULL, NULL, scratch_pool);
257 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
259 svn_error_clear(err);
260 SVN_ERR(svn_stream_close(fstream));
266 return svn_error_trace(err);
268 SVN_ERR(svn_stream_close(fstream));
274 fetch_props_func(apr_hash_t **props,
277 svn_revnum_t base_revision,
278 apr_pool_t *result_pool,
279 apr_pool_t *scratch_pool)
281 struct revision_baton *rb = baton;
282 svn_node_kind_t node_kind;
284 if (! SVN_IS_VALID_REVNUM(base_revision))
285 base_revision = rb->rev - 1;
287 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
288 &node_kind, scratch_pool));
290 if (node_kind == svn_node_file)
292 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
293 NULL, NULL, props, result_pool));
295 else if (node_kind == svn_node_dir)
297 apr_array_header_t *tmp_props;
299 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
300 base_revision, 0 /* Dirent fields */,
302 tmp_props = svn_prop_hash_to_array(*props, result_pool);
303 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
305 *props = svn_prop_array_to_hash(tmp_props, result_pool);
309 *props = apr_hash_make(result_pool);
316 fetch_kind_func(svn_node_kind_t *kind,
319 svn_revnum_t base_revision,
320 apr_pool_t *scratch_pool)
322 struct revision_baton *rb = baton;
324 if (! SVN_IS_VALID_REVNUM(base_revision))
325 base_revision = rb->rev - 1;
327 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
328 kind, scratch_pool));
333 static svn_delta_shim_callbacks_t *
334 get_shim_callbacks(struct revision_baton *rb,
337 svn_delta_shim_callbacks_t *callbacks =
338 svn_delta_shim_callbacks_default(pool);
340 callbacks->fetch_props_func = fetch_props_func;
341 callbacks->fetch_kind_func = fetch_kind_func;
342 callbacks->fetch_base_func = fetch_base_func;
343 callbacks->fetch_baton = rb;
348 /* Acquire a lock (of sorts) on the repository associated with the
349 * given RA SESSION. This lock is just a revprop change attempt in a
350 * time-delay loop. This function is duplicated by svnsync in
353 * ### TODO: Make this function more generic and
354 * expose it through a header for use by other Subversion
355 * applications to avoid duplication.
358 get_lock(const svn_string_t **lock_string_p,
359 svn_ra_session_t *session,
360 svn_cancel_func_t cancel_func,
364 svn_boolean_t be_atomic;
366 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
367 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
371 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
373 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
374 _("Target server does not support atomic revision "
375 "property edits; consider upgrading it to 1.7."));
376 svn_handle_warning2(stderr, err, "svnrdump: ");
377 svn_error_clear(err);
380 return svn_ra__get_operational_lock(lock_string_p, NULL, session,
381 SVNRDUMP_PROP_LOCK, FALSE,
382 10 /* retries */, lock_retry_func, NULL,
383 cancel_func, cancel_baton, pool);
387 new_revision_record(void **revision_baton,
392 struct revision_baton *rb;
393 struct parse_baton *pb;
395 svn_revnum_t head_rev;
397 rb = apr_pcalloc(pool, sizeof(*rb));
399 rb->pool = svn_pool_create(pool);
403 rev_str = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
405 rb->rev = SVN_STR_TO_REV(rev_str);
407 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
409 /* FIXME: This is a lame fallback loading multiple segments of dump in
410 several separate operations. It is highly susceptible to race conditions.
411 Calculate the revision 'offset' for finding copyfrom sources.
412 It might be positive or negative. */
413 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
415 /* Stash the oldest (non-zero) dumpstream revision seen. */
416 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
417 pb->oldest_dumpstream_rev = rb->rev;
419 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
420 them to be created in new_node_record */
421 rb->pb->commit_editor = NULL;
422 rb->pb->commit_edit_baton = NULL;
423 rb->revprop_table = apr_hash_make(rb->pool);
425 *revision_baton = rb;
430 magic_header_record(int version,
438 uuid_record(const char *uuid,
445 /* Push information about another directory onto the linked list RB->db.
447 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
448 * repository-relative path of this directory. IS_ADDED is true iff this
449 * directory is being added (with or without history). If added with
450 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
451 * are NULL/SVN_INVALID_REVNUM.
454 push_directory(struct revision_baton *rb,
457 svn_boolean_t is_added,
458 const char *copyfrom_path,
459 svn_revnum_t copyfrom_rev)
461 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
463 SVN_ERR_ASSERT_NO_RETURN(
464 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
466 /* If this node is an existing (not newly added) child of a copied node,
467 calculate where it was copied from. */
469 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
471 const char *name = svn_relpath_basename(relpath, NULL);
473 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
475 copyfrom_rev = rb->db->copyfrom_rev;
478 child_db->baton = child_baton;
479 child_db->relpath = relpath;
480 child_db->copyfrom_path = copyfrom_path;
481 child_db->copyfrom_rev = copyfrom_rev;
482 child_db->parent = rb->db;
487 new_node_record(void **node_baton,
489 void *revision_baton,
492 struct revision_baton *rb = revision_baton;
493 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
494 void *commit_edit_baton = rb->pb->commit_edit_baton;
495 struct node_baton *nb;
496 svn_revnum_t head_rev_before_commit = rb->rev - rb->rev_offset - 1;
497 apr_hash_index_t *hi;
499 const char *nb_dirname;
501 nb = apr_pcalloc(rb->pool, sizeof(*nb));
503 nb->is_added = FALSE;
504 nb->copyfrom_path = NULL;
505 nb->copyfrom_url = NULL;
506 nb->copyfrom_rev = SVN_INVALID_REVNUM;
507 nb->prop_changes = apr_hash_make(rb->pool);
509 /* If the creation of commit_editor is pending, create it now and
510 open_root on it; also create a top-level directory baton. */
514 /* The revprop_table should have been filled in with important
515 information like svn:log in set_revision_property. We can now
516 use it all this information to create our commit_editor. But
517 first, clear revprops that we aren't allowed to set with the
518 commit_editor. We'll set them separately using the RA API
519 after closing the editor (see close_revision). */
521 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
522 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
524 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
525 get_shim_callbacks(rb, rb->pool)));
526 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
527 &commit_edit_baton, rb->revprop_table,
528 commit_callback, revision_baton,
529 NULL, FALSE, rb->pool));
531 rb->pb->commit_editor = commit_editor;
532 rb->pb->commit_edit_baton = commit_edit_baton;
534 SVN_ERR(commit_editor->open_root(commit_edit_baton,
535 head_rev_before_commit,
536 rb->pool, &child_baton));
538 /* child_baton corresponds to the root directory baton here */
539 push_directory(rb, child_baton, "", TRUE /*is_added*/,
540 NULL, SVN_INVALID_REVNUM);
543 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
545 const char *hname = apr_hash_this_key(hi);
546 const char *hval = apr_hash_this_val(hi);
548 /* Parse the different kinds of headers we can encounter and
549 stuff them into the node_baton for writing later */
550 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
551 nb->path = apr_pstrdup(rb->pool, hval);
552 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
553 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
554 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
556 if (strcmp(hval, "add") == 0)
557 nb->action = svn_node_action_add;
558 if (strcmp(hval, "change") == 0)
559 nb->action = svn_node_action_change;
560 if (strcmp(hval, "delete") == 0)
561 nb->action = svn_node_action_delete;
562 if (strcmp(hval, "replace") == 0)
563 nb->action = svn_node_action_replace;
565 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
566 nb->base_checksum = apr_pstrdup(rb->pool, hval);
567 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
568 nb->copyfrom_rev = SVN_STR_TO_REV(hval);
569 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
570 nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
573 /* Before handling the new node, ensure depth-first editing order by
574 traversing the directory hierarchy from the old node's to the new
575 node's parent directory. */
576 nb_dirname = svn_relpath_dirname(nb->path, pool);
577 if (svn_path_compare_paths(nb_dirname,
578 rb->db->relpath) != 0)
581 apr_size_t residual_close_count;
582 apr_array_header_t *residual_open_path;
587 svn_relpath_get_longest_ancestor(nb_dirname,
588 rb->db->relpath, pool);
589 residual_close_count =
590 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
593 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
596 /* First close all as many directories as there are after
597 skip_ancestor, and then open fresh directories */
598 for (n = 0; n < residual_close_count; n ++)
600 /* Don't worry about destroying the actual rb->db object,
601 since the pool we're using has the lifetime of one
603 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
604 rb->db = rb->db->parent;
607 for (i = 0; i < residual_open_path->nelts; i ++)
609 char *relpath_compose =
610 svn_relpath_join(rb->db->relpath,
611 APR_ARRAY_IDX(residual_open_path, i, const char *),
613 SVN_ERR(commit_editor->open_directory(relpath_compose,
615 head_rev_before_commit,
616 rb->pool, &child_baton));
617 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
618 NULL, SVN_INVALID_REVNUM);
622 /* Fix up the copyfrom information in light of mapped revisions and
623 non-root load targets, and convert copyfrom path into a full
625 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
627 svn_revnum_t copyfrom_rev;
629 /* Try to find the copyfrom revision in the revision map;
630 failing that, fall back to the revision offset approach. */
631 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
632 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
633 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
635 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
636 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
637 _("Relative source revision %ld is not"
638 " available in current repository"),
641 nb->copyfrom_rev = copyfrom_rev;
643 if (rb->pb->parent_dir)
644 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
645 nb->copyfrom_path, rb->pool);
646 /* Convert to a URL, as the commit editor requires. */
647 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
655 case svn_node_action_delete:
656 case svn_node_action_replace:
657 SVN_ERR(commit_editor->delete_entry(nb->path,
658 head_rev_before_commit,
659 rb->db->baton, rb->pool));
660 if (nb->action == svn_node_action_delete)
664 case svn_node_action_add:
669 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
672 rb->pool, &(nb->file_baton)));
675 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
678 rb->pool, &child_baton));
679 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
680 nb->copyfrom_path, nb->copyfrom_rev);
686 case svn_node_action_change:
690 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
691 SVN_INVALID_REVNUM, rb->pool,
695 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
696 head_rev_before_commit,
697 rb->pool, &child_baton));
698 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
699 NULL, SVN_INVALID_REVNUM);
710 set_revision_property(void *baton,
712 const svn_string_t *value)
714 struct revision_baton *rb = baton;
716 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value,
717 rb->pool, rb->pool));
718 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
722 if (! svn_hash_gets(rb->pb->skip_revprops, name))
723 svn_hash_sets(rb->revprop_table,
724 apr_pstrdup(rb->pool, name), value);
726 else if (rb->rev_offset == -1
727 && ! svn_hash_gets(rb->pb->skip_revprops, name))
729 /* Special case: set revision 0 properties directly (which is
730 safe because the commit_editor hasn't been created yet), but
731 only when loading into an 'empty' filesystem. */
732 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
733 name, NULL, value, rb->pool));
736 /* Remember any datestamp/ author that passes through (see comment
737 in close_revision). */
738 if (!strcmp(name, SVN_PROP_REVISION_DATE))
739 rb->datestamp = value;
740 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
747 set_node_property(void *baton,
749 const svn_string_t *value)
751 struct node_baton *nb = baton;
752 struct revision_baton *rb = nb->rb;
753 struct parse_baton *pb = rb->pb;
754 apr_pool_t *pool = nb->rb->pool;
757 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
759 svn_string_t *new_value;
762 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
765 pb->oldest_dumpstream_rev,
767 NULL, NULL, /*notify*/
771 return svn_error_quick_wrap(err,
772 _("Invalid svn:mergeinfo value"));
778 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
780 SVN_ERR(svn_repos__validate_prop(name, value, pool));
782 prop = apr_palloc(nb->rb->pool, sizeof (*prop));
783 prop->name = apr_pstrdup(pool, name);
785 svn_hash_sets(nb->prop_changes, prop->name, prop);
791 delete_node_property(void *baton,
794 struct node_baton *nb = baton;
795 apr_pool_t *pool = nb->rb->pool;
798 SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
800 prop = apr_palloc(pool, sizeof (*prop));
801 prop->name = apr_pstrdup(pool, name);
803 svn_hash_sets(nb->prop_changes, prop->name, prop);
808 /* Delete all the properties of the node, if any.
810 * The commit editor doesn't have a method to delete a node's properties
811 * without knowing what they are, so we have to first find out what
812 * properties the node would have had. If it's copied (explicitly or
813 * implicitly), we look at the copy source. If it's only being changed,
814 * we look at the node's current path in the head revision.
817 remove_node_props(void *baton)
819 struct node_baton *nb = baton;
820 struct revision_baton *rb = nb->rb;
821 apr_pool_t *pool = nb->rb->pool;
822 apr_hash_index_t *hi;
824 const char *orig_path;
825 svn_revnum_t orig_rev;
827 /* Find the path and revision that has the node's original properties */
828 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
830 orig_path = nb->copyfrom_path;
831 orig_rev = nb->copyfrom_rev;
833 else if (!nb->is_added
834 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
836 /* If this is a dir, then it's described by rb->db;
837 if this is a file, then it's a child of the dir in rb->db. */
838 orig_path = (nb->kind == svn_node_dir)
839 ? rb->db->copyfrom_path
840 : svn_relpath_join(rb->db->copyfrom_path,
841 svn_relpath_basename(nb->path, NULL),
843 orig_rev = rb->db->copyfrom_rev;
847 /* ### Should we query at a known, fixed, "head" revision number
848 instead of passing SVN_INVALID_REVNUM and getting a moving target? */
849 orig_path = nb->path;
850 orig_rev = SVN_INVALID_REVNUM;
853 if ((nb->action == svn_node_action_add
854 || nb->action == svn_node_action_replace)
855 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
856 /* Add-without-history; no "old" properties to worry about. */
859 if (nb->kind == svn_node_file)
861 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session,
862 orig_path, orig_rev, NULL, NULL, &props, pool));
864 else /* nb->kind == svn_node_dir */
866 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
867 orig_path, orig_rev, 0, pool));
870 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
872 const char *name = apr_hash_this_key(hi);
873 svn_prop_kind_t kind = svn_property_kind2(name);
875 if (kind == svn_prop_regular_kind)
876 SVN_ERR(set_node_property(nb, name, NULL));
883 set_fulltext(svn_stream_t **stream,
886 struct node_baton *nb = node_baton;
887 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
888 svn_txdelta_window_handler_t handler;
890 apr_pool_t *pool = nb->rb->pool;
892 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
893 pool, &handler, &handler_baton));
894 *stream = svn_txdelta_target_push(handler, handler_baton,
895 svn_stream_empty(pool), pool);
900 apply_textdelta(svn_txdelta_window_handler_t *handler,
901 void **handler_baton,
904 struct node_baton *nb = node_baton;
905 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
906 apr_pool_t *pool = nb->rb->pool;
908 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
909 pool, handler, handler_baton));
915 close_node(void *baton)
917 struct node_baton *nb = baton;
918 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
919 apr_pool_t *pool = nb->rb->pool;
920 apr_hash_index_t *hi;
922 for (hi = apr_hash_first(pool, nb->prop_changes);
923 hi; hi = apr_hash_next(hi))
925 const char *name = apr_hash_this_key(hi);
926 svn_prop_t *prop = apr_hash_this_val(hi);
931 SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
932 name, prop->value, pool));
935 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
936 name, prop->value, pool));
943 /* Pass a file node closure through to the editor *unless* we
944 deleted the file (which doesn't require us to open it). */
945 if ((nb->kind == svn_node_file) && (nb->file_baton))
947 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
950 /* The svn_node_dir case is handled in close_revision */
956 close_revision(void *baton)
958 struct revision_baton *rb = baton;
959 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
960 void *commit_edit_baton = rb->pb->commit_edit_baton;
961 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
963 /* Fake revision 0 */
966 /* ### Don't print directly; generate a notification. */
968 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
970 else if (commit_editor)
972 /* Close all pending open directories, and then close the edit
974 while (rb->db && rb->db->parent)
976 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
977 rb->db = rb->db->parent;
979 /* root dir's baton */
980 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
981 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
985 svn_revnum_t head_rev_before_commit = rb->rev - rb->rev_offset - 1;
988 /* Legitimate revision with no node information */
989 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
990 &commit_edit_baton, rb->revprop_table,
991 commit_callback, baton,
992 NULL, FALSE, rb->pool));
994 SVN_ERR(commit_editor->open_root(commit_edit_baton,
995 head_rev_before_commit,
996 rb->pool, &child_baton));
998 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
999 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1002 /* svn_fs_commit_txn() rewrites the datestamp and author properties;
1003 we'll rewrite them again by hand after closing the commit_editor.
1004 The only time we don't do this is for revision 0 when loaded into
1005 a non-empty repository. */
1008 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
1010 else if (rb->rev_offset == -1)
1015 if (SVN_IS_VALID_REVNUM(committed_rev))
1017 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE))
1019 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
1020 rb->datestamp, rb->pool));
1021 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1022 SVN_PROP_REVISION_DATE,
1023 NULL, rb->datestamp, rb->pool));
1025 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR))
1027 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
1028 rb->author, rb->pool));
1029 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1030 SVN_PROP_REVISION_AUTHOR,
1031 NULL, rb->author, rb->pool));
1035 svn_pool_destroy(rb->pool);
1037 return SVN_NO_ERROR;
1041 svn_rdump__load_dumpstream(svn_stream_t *stream,
1042 svn_ra_session_t *session,
1043 svn_ra_session_t *aux_session,
1044 svn_boolean_t quiet,
1045 apr_hash_t *skip_revprops,
1046 svn_cancel_func_t cancel_func,
1050 svn_repos_parse_fns3_t *parser;
1051 struct parse_baton *parse_baton;
1052 const svn_string_t *lock_string;
1053 svn_boolean_t be_atomic;
1055 const char *session_url, *root_url, *parent_dir;
1057 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1058 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1060 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1061 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1062 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1063 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1064 session_url, pool));
1066 parser = apr_pcalloc(pool, sizeof(*parser));
1067 parser->magic_header_record = magic_header_record;
1068 parser->uuid_record = uuid_record;
1069 parser->new_revision_record = new_revision_record;
1070 parser->new_node_record = new_node_record;
1071 parser->set_revision_property = set_revision_property;
1072 parser->set_node_property = set_node_property;
1073 parser->delete_node_property = delete_node_property;
1074 parser->remove_node_props = remove_node_props;
1075 parser->set_fulltext = set_fulltext;
1076 parser->apply_textdelta = apply_textdelta;
1077 parser->close_node = close_node;
1078 parser->close_revision = close_revision;
1080 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1081 parse_baton->session = session;
1082 parse_baton->aux_session = aux_session;
1083 parse_baton->quiet = quiet;
1084 parse_baton->root_url = root_url;
1085 parse_baton->parent_dir = parent_dir;
1086 parse_baton->rev_map = apr_hash_make(pool);
1087 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1088 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1089 parse_baton->skip_revprops = skip_revprops;
1091 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1092 cancel_func, cancel_baton, pool);
1094 /* If all goes well, or if we're cancelled cleanly, don't leave a
1095 stray lock behind. */
1096 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1097 err = svn_error_compose_create(
1098 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,