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,
171 sizeof(svn_revnum_t), mapped_revs + 1);
174 /* Return the revision to which FROM_REV maps in REV_MAP, or
175 SVN_INVALID_REVNUM if no such mapping exists. */
177 get_revision_mapping(apr_hash_t *rev_map,
178 svn_revnum_t from_rev)
180 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
182 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
187 commit_callback(const svn_commit_info_t *commit_info,
191 struct revision_baton *rb = baton;
192 struct parse_baton *pb = rb->pb;
194 /* ### Don't print directly; generate a notification. */
196 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
197 commit_info->revision));
199 /* Add the mapping of the dumpstream revision to the committed revision. */
200 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
202 /* If the incoming dump stream has non-contiguous revisions (e.g. from
203 using svndumpfilter --drop-empty-revs without --renumber-revs) then
204 we must account for the missing gaps in PB->REV_MAP. Otherwise we
205 might not be able to map all mergeinfo source revisions to the correct
206 revisions in the target repos. */
207 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
208 && (rb->rev != pb->last_rev_mapped + 1))
212 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
214 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
218 /* Update our "last revision mapped". */
219 pb->last_rev_mapped = rb->rev;
224 /* Implements `svn_ra__lock_retry_func_t'. */
226 lock_retry_func(void *baton,
227 const svn_string_t *reposlocktoken,
230 return svn_cmdline_printf(pool,
231 _("Failed to get lock on destination "
232 "repos, currently held by '%s'\n"),
233 reposlocktoken->data);
238 fetch_base_func(const char **filename,
241 svn_revnum_t base_revision,
242 apr_pool_t *result_pool,
243 apr_pool_t *scratch_pool)
245 struct revision_baton *rb = baton;
246 svn_stream_t *fstream;
249 if (! SVN_IS_VALID_REVNUM(base_revision))
250 base_revision = rb->rev - 1;
252 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
253 svn_io_file_del_on_pool_cleanup,
254 result_pool, scratch_pool));
256 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
257 fstream, NULL, NULL, scratch_pool);
258 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
260 svn_error_clear(err);
261 SVN_ERR(svn_stream_close(fstream));
267 return svn_error_trace(err);
269 SVN_ERR(svn_stream_close(fstream));
275 fetch_props_func(apr_hash_t **props,
278 svn_revnum_t base_revision,
279 apr_pool_t *result_pool,
280 apr_pool_t *scratch_pool)
282 struct revision_baton *rb = baton;
283 svn_node_kind_t node_kind;
285 if (! SVN_IS_VALID_REVNUM(base_revision))
286 base_revision = rb->rev - 1;
288 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
289 &node_kind, scratch_pool));
291 if (node_kind == svn_node_file)
293 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
294 NULL, NULL, props, result_pool));
296 else if (node_kind == svn_node_dir)
298 apr_array_header_t *tmp_props;
300 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
301 base_revision, 0 /* Dirent fields */,
303 tmp_props = svn_prop_hash_to_array(*props, result_pool);
304 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
306 *props = svn_prop_array_to_hash(tmp_props, result_pool);
310 *props = apr_hash_make(result_pool);
317 fetch_kind_func(svn_node_kind_t *kind,
320 svn_revnum_t base_revision,
321 apr_pool_t *scratch_pool)
323 struct revision_baton *rb = baton;
325 if (! SVN_IS_VALID_REVNUM(base_revision))
326 base_revision = rb->rev - 1;
328 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
329 kind, scratch_pool));
334 static svn_delta_shim_callbacks_t *
335 get_shim_callbacks(struct revision_baton *rb,
338 svn_delta_shim_callbacks_t *callbacks =
339 svn_delta_shim_callbacks_default(pool);
341 callbacks->fetch_props_func = fetch_props_func;
342 callbacks->fetch_kind_func = fetch_kind_func;
343 callbacks->fetch_base_func = fetch_base_func;
344 callbacks->fetch_baton = rb;
349 /* Acquire a lock (of sorts) on the repository associated with the
350 * given RA SESSION. This lock is just a revprop change attempt in a
351 * time-delay loop. This function is duplicated by svnsync in
354 * ### TODO: Make this function more generic and
355 * expose it through a header for use by other Subversion
356 * applications to avoid duplication.
359 get_lock(const svn_string_t **lock_string_p,
360 svn_ra_session_t *session,
361 svn_cancel_func_t cancel_func,
365 svn_boolean_t be_atomic;
367 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
368 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
372 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
374 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
375 _("Target server does not support atomic revision "
376 "property edits; consider upgrading it to 1.7."));
377 svn_handle_warning2(stderr, err, "svnrdump: ");
378 svn_error_clear(err);
381 return svn_ra__get_operational_lock(lock_string_p, NULL, session,
382 SVNRDUMP_PROP_LOCK, FALSE,
383 10 /* retries */, lock_retry_func, NULL,
384 cancel_func, cancel_baton, pool);
388 new_revision_record(void **revision_baton,
393 struct revision_baton *rb;
394 struct parse_baton *pb;
395 apr_hash_index_t *hi;
396 svn_revnum_t head_rev;
398 rb = apr_pcalloc(pool, sizeof(*rb));
400 rb->pool = svn_pool_create(pool);
404 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
406 const char *hname = apr_hash_this_key(hi);
407 const char *hval = apr_hash_this_val(hi);
409 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
410 rb->rev = atoi(hval);
413 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
415 /* FIXME: This is a lame fallback loading multiple segments of dump in
416 several separate operations. It is highly susceptible to race conditions.
417 Calculate the revision 'offset' for finding copyfrom sources.
418 It might be positive or negative. */
419 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
421 /* Stash the oldest (non-zero) dumpstream revision seen. */
422 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
423 pb->oldest_dumpstream_rev = rb->rev;
425 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
426 them to be created in new_node_record */
427 rb->pb->commit_editor = NULL;
428 rb->pb->commit_edit_baton = NULL;
429 rb->revprop_table = apr_hash_make(rb->pool);
431 *revision_baton = rb;
436 magic_header_record(int version,
444 uuid_record(const char *uuid,
451 /* Push information about another directory onto the linked list RB->db.
453 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
454 * repository-relative path of this directory. IS_ADDED is true iff this
455 * directory is being added (with or without history). If added with
456 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
457 * are NULL/SVN_INVALID_REVNUM.
460 push_directory(struct revision_baton *rb,
463 svn_boolean_t is_added,
464 const char *copyfrom_path,
465 svn_revnum_t copyfrom_rev)
467 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
469 SVN_ERR_ASSERT_NO_RETURN(
470 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
472 /* If this node is an existing (not newly added) child of a copied node,
473 calculate where it was copied from. */
475 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
477 const char *name = svn_relpath_basename(relpath, NULL);
479 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
481 copyfrom_rev = rb->db->copyfrom_rev;
484 child_db->baton = child_baton;
485 child_db->relpath = relpath;
486 child_db->copyfrom_path = copyfrom_path;
487 child_db->copyfrom_rev = copyfrom_rev;
488 child_db->parent = rb->db;
493 new_node_record(void **node_baton,
495 void *revision_baton,
498 struct revision_baton *rb = revision_baton;
499 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
500 void *commit_edit_baton = rb->pb->commit_edit_baton;
501 struct node_baton *nb;
502 apr_hash_index_t *hi;
504 const char *nb_dirname;
506 nb = apr_pcalloc(rb->pool, sizeof(*nb));
508 nb->is_added = FALSE;
509 nb->copyfrom_path = NULL;
510 nb->copyfrom_url = NULL;
511 nb->copyfrom_rev = SVN_INVALID_REVNUM;
512 nb->prop_changes = apr_hash_make(rb->pool);
514 /* If the creation of commit_editor is pending, create it now and
515 open_root on it; also create a top-level directory baton. */
519 /* The revprop_table should have been filled in with important
520 information like svn:log in set_revision_property. We can now
521 use it all this information to create our commit_editor. But
522 first, clear revprops that we aren't allowed to set with the
523 commit_editor. We'll set them separately using the RA API
524 after closing the editor (see close_revision). */
526 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
527 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
529 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
530 get_shim_callbacks(rb, rb->pool)));
531 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
532 &commit_edit_baton, rb->revprop_table,
533 commit_callback, revision_baton,
534 NULL, FALSE, rb->pool));
536 rb->pb->commit_editor = commit_editor;
537 rb->pb->commit_edit_baton = commit_edit_baton;
539 SVN_ERR(commit_editor->open_root(commit_edit_baton,
540 rb->rev - rb->rev_offset - 1,
541 rb->pool, &child_baton));
543 /* child_baton corresponds to the root directory baton here */
544 push_directory(rb, child_baton, "", TRUE /*is_added*/,
545 NULL, SVN_INVALID_REVNUM);
548 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
550 const char *hname = apr_hash_this_key(hi);
551 const char *hval = apr_hash_this_val(hi);
553 /* Parse the different kinds of headers we can encounter and
554 stuff them into the node_baton for writing later */
555 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
556 nb->path = apr_pstrdup(rb->pool, hval);
557 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
558 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
559 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
561 if (strcmp(hval, "add") == 0)
562 nb->action = svn_node_action_add;
563 if (strcmp(hval, "change") == 0)
564 nb->action = svn_node_action_change;
565 if (strcmp(hval, "delete") == 0)
566 nb->action = svn_node_action_delete;
567 if (strcmp(hval, "replace") == 0)
568 nb->action = svn_node_action_replace;
570 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
571 nb->base_checksum = apr_pstrdup(rb->pool, hval);
572 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
573 nb->copyfrom_rev = atoi(hval);
574 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
575 nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
578 /* Before handling the new node, ensure depth-first editing order by
579 traversing the directory hierarchy from the old node's to the new
580 node's parent directory. */
581 nb_dirname = svn_relpath_dirname(nb->path, pool);
582 if (svn_path_compare_paths(nb_dirname,
583 rb->db->relpath) != 0)
586 apr_size_t residual_close_count;
587 apr_array_header_t *residual_open_path;
592 svn_relpath_get_longest_ancestor(nb_dirname,
593 rb->db->relpath, pool);
594 residual_close_count =
595 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
598 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
601 /* First close all as many directories as there are after
602 skip_ancestor, and then open fresh directories */
603 for (n = 0; n < residual_close_count; n ++)
605 /* Don't worry about destroying the actual rb->db object,
606 since the pool we're using has the lifetime of one
608 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
609 rb->db = rb->db->parent;
612 for (i = 0; i < residual_open_path->nelts; i ++)
614 char *relpath_compose =
615 svn_relpath_join(rb->db->relpath,
616 APR_ARRAY_IDX(residual_open_path, i, const char *),
618 SVN_ERR(commit_editor->open_directory(relpath_compose,
620 rb->rev - rb->rev_offset - 1,
621 rb->pool, &child_baton));
622 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
623 NULL, SVN_INVALID_REVNUM);
627 /* Fix up the copyfrom information in light of mapped revisions and
628 non-root load targets, and convert copyfrom path into a full
630 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
632 svn_revnum_t copyfrom_rev;
634 /* Try to find the copyfrom revision in the revision map;
635 failing that, fall back to the revision offset approach. */
636 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
637 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
638 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
640 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
641 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
642 _("Relative source revision %ld is not"
643 " available in current repository"),
646 nb->copyfrom_rev = copyfrom_rev;
648 if (rb->pb->parent_dir)
649 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
650 nb->copyfrom_path, rb->pool);
651 /* Convert to a URL, as the commit editor requires. */
652 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
660 case svn_node_action_delete:
661 case svn_node_action_replace:
662 SVN_ERR(commit_editor->delete_entry(nb->path,
663 rb->rev - rb->rev_offset - 1,
664 rb->db->baton, rb->pool));
665 if (nb->action == svn_node_action_delete)
669 case svn_node_action_add:
674 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
677 rb->pool, &(nb->file_baton)));
680 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
683 rb->pool, &child_baton));
684 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
685 nb->copyfrom_path, nb->copyfrom_rev);
691 case svn_node_action_change:
695 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
696 SVN_INVALID_REVNUM, rb->pool,
700 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
701 rb->rev - rb->rev_offset - 1,
702 rb->pool, &child_baton));
703 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
704 NULL, SVN_INVALID_REVNUM);
715 set_revision_property(void *baton,
717 const svn_string_t *value)
719 struct revision_baton *rb = baton;
721 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
723 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
727 if (! svn_hash_gets(rb->pb->skip_revprops, name))
728 svn_hash_sets(rb->revprop_table,
729 apr_pstrdup(rb->pool, name),
730 svn_string_dup(value, rb->pool));
732 else if (rb->rev_offset == -1
733 && ! svn_hash_gets(rb->pb->skip_revprops, name))
735 /* Special case: set revision 0 properties directly (which is
736 safe because the commit_editor hasn't been created yet), but
737 only when loading into an 'empty' filesystem. */
738 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
739 name, NULL, value, rb->pool));
742 /* Remember any datestamp/ author that passes through (see comment
743 in close_revision). */
744 if (!strcmp(name, SVN_PROP_REVISION_DATE))
745 rb->datestamp = svn_string_dup(value, rb->pool);
746 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
747 rb->author = svn_string_dup(value, rb->pool);
753 set_node_property(void *baton,
755 const svn_string_t *value)
757 struct node_baton *nb = baton;
758 struct revision_baton *rb = nb->rb;
759 struct parse_baton *pb = rb->pb;
760 apr_pool_t *pool = nb->rb->pool;
763 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
765 svn_string_t *new_value;
768 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
771 pb->oldest_dumpstream_rev,
773 NULL, NULL, /*notify*/
777 return svn_error_quick_wrap(err,
778 _("Invalid svn:mergeinfo value"));
784 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
786 SVN_ERR(svn_repos__validate_prop(name, value, pool));
788 prop = apr_palloc(nb->rb->pool, sizeof (*prop));
789 prop->name = apr_pstrdup(pool, name);
790 prop->value = svn_string_dup(value, pool);
791 svn_hash_sets(nb->prop_changes, prop->name, prop);
797 delete_node_property(void *baton,
800 struct node_baton *nb = baton;
801 apr_pool_t *pool = nb->rb->pool;
804 SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
806 prop = apr_palloc(pool, sizeof (*prop));
807 prop->name = apr_pstrdup(pool, name);
809 svn_hash_sets(nb->prop_changes, prop->name, prop);
814 /* Delete all the properties of the node, if any.
816 * The commit editor doesn't have a method to delete a node's properties
817 * without knowing what they are, so we have to first find out what
818 * properties the node would have had. If it's copied (explicitly or
819 * implicitly), we look at the copy source. If it's only being changed,
820 * we look at the node's current path in the head revision.
823 remove_node_props(void *baton)
825 struct node_baton *nb = baton;
826 struct revision_baton *rb = nb->rb;
827 apr_pool_t *pool = nb->rb->pool;
828 apr_hash_index_t *hi;
830 const char *orig_path;
831 svn_revnum_t orig_rev;
833 /* Find the path and revision that has the node's original properties */
834 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
836 orig_path = nb->copyfrom_path;
837 orig_rev = nb->copyfrom_rev;
839 else if (!nb->is_added
840 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
842 /* If this is a dir, then it's described by rb->db;
843 if this is a file, then it's a child of the dir in rb->db. */
844 orig_path = (nb->kind == svn_node_dir)
845 ? rb->db->copyfrom_path
846 : svn_relpath_join(rb->db->copyfrom_path,
847 svn_relpath_basename(nb->path, NULL),
849 orig_rev = rb->db->copyfrom_rev;
853 /* ### Should we query at a known, fixed, "head" revision number
854 instead of passing SVN_INVALID_REVNUM and getting a moving target? */
855 orig_path = nb->path;
856 orig_rev = SVN_INVALID_REVNUM;
859 if ((nb->action == svn_node_action_add
860 || nb->action == svn_node_action_replace)
861 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
862 /* Add-without-history; no "old" properties to worry about. */
865 if (nb->kind == svn_node_file)
867 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session,
868 orig_path, orig_rev, NULL, NULL, &props, pool));
870 else /* nb->kind == svn_node_dir */
872 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
873 orig_path, orig_rev, 0, pool));
876 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
878 const char *name = apr_hash_this_key(hi);
879 svn_prop_kind_t kind = svn_property_kind2(name);
881 if (kind == svn_prop_regular_kind)
882 SVN_ERR(set_node_property(nb, name, NULL));
889 set_fulltext(svn_stream_t **stream,
892 struct node_baton *nb = node_baton;
893 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
894 svn_txdelta_window_handler_t handler;
896 apr_pool_t *pool = nb->rb->pool;
898 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
899 pool, &handler, &handler_baton));
900 *stream = svn_txdelta_target_push(handler, handler_baton,
901 svn_stream_empty(pool), pool);
906 apply_textdelta(svn_txdelta_window_handler_t *handler,
907 void **handler_baton,
910 struct node_baton *nb = node_baton;
911 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
912 apr_pool_t *pool = nb->rb->pool;
914 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
915 pool, handler, handler_baton));
921 close_node(void *baton)
923 struct node_baton *nb = baton;
924 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
925 apr_pool_t *pool = nb->rb->pool;
926 apr_hash_index_t *hi;
928 for (hi = apr_hash_first(pool, nb->prop_changes);
929 hi; hi = apr_hash_next(hi))
931 const char *name = apr_hash_this_key(hi);
932 svn_prop_t *prop = apr_hash_this_val(hi);
937 SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
938 name, prop->value, pool));
941 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
942 name, prop->value, pool));
949 /* Pass a file node closure through to the editor *unless* we
950 deleted the file (which doesn't require us to open it). */
951 if ((nb->kind == svn_node_file) && (nb->file_baton))
953 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
956 /* The svn_node_dir case is handled in close_revision */
962 close_revision(void *baton)
964 struct revision_baton *rb = baton;
965 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
966 void *commit_edit_baton = rb->pb->commit_edit_baton;
967 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
969 /* Fake revision 0 */
972 /* ### Don't print directly; generate a notification. */
974 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
976 else if (commit_editor)
978 /* Close all pending open directories, and then close the edit
980 while (rb->db && rb->db->parent)
982 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
983 rb->db = rb->db->parent;
985 /* root dir's baton */
986 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
987 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
993 /* Legitimate revision with no node information */
994 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
995 &commit_edit_baton, rb->revprop_table,
996 commit_callback, baton,
997 NULL, FALSE, rb->pool));
999 SVN_ERR(commit_editor->open_root(commit_edit_baton,
1000 rb->rev - rb->rev_offset - 1,
1001 rb->pool, &child_baton));
1003 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
1004 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1007 /* svn_fs_commit_txn() rewrites the datestamp and author properties;
1008 we'll rewrite them again by hand after closing the commit_editor.
1009 The only time we don't do this is for revision 0 when loaded into
1010 a non-empty repository. */
1013 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
1015 else if (rb->rev_offset == -1)
1020 if (SVN_IS_VALID_REVNUM(committed_rev))
1022 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE))
1024 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
1025 rb->datestamp, rb->pool));
1026 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1027 SVN_PROP_REVISION_DATE,
1028 NULL, rb->datestamp, rb->pool));
1030 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR))
1032 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
1033 rb->author, rb->pool));
1034 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1035 SVN_PROP_REVISION_AUTHOR,
1036 NULL, rb->author, rb->pool));
1040 svn_pool_destroy(rb->pool);
1042 return SVN_NO_ERROR;
1046 svn_rdump__load_dumpstream(svn_stream_t *stream,
1047 svn_ra_session_t *session,
1048 svn_ra_session_t *aux_session,
1049 svn_boolean_t quiet,
1050 apr_hash_t *skip_revprops,
1051 svn_cancel_func_t cancel_func,
1055 svn_repos_parse_fns3_t *parser;
1056 struct parse_baton *parse_baton;
1057 const svn_string_t *lock_string;
1058 svn_boolean_t be_atomic;
1060 const char *session_url, *root_url, *parent_dir;
1062 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1063 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1065 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1066 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1067 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1068 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1069 session_url, pool));
1071 parser = apr_pcalloc(pool, sizeof(*parser));
1072 parser->magic_header_record = magic_header_record;
1073 parser->uuid_record = uuid_record;
1074 parser->new_revision_record = new_revision_record;
1075 parser->new_node_record = new_node_record;
1076 parser->set_revision_property = set_revision_property;
1077 parser->set_node_property = set_node_property;
1078 parser->delete_node_property = delete_node_property;
1079 parser->remove_node_props = remove_node_props;
1080 parser->set_fulltext = set_fulltext;
1081 parser->apply_textdelta = apply_textdelta;
1082 parser->close_node = close_node;
1083 parser->close_revision = close_revision;
1085 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1086 parse_baton->session = session;
1087 parse_baton->aux_session = aux_session;
1088 parse_baton->quiet = quiet;
1089 parse_baton->root_url = root_url;
1090 parse_baton->parent_dir = parent_dir;
1091 parse_baton->rev_map = apr_hash_make(pool);
1092 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1093 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1094 parse_baton->skip_revprops = skip_revprops;
1096 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1097 cancel_func, cancel_baton, pool);
1099 /* If all goes well, or if we're cancelled cleanly, don't leave a
1100 stray lock behind. */
1101 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1102 err = svn_error_compose_create(
1103 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,