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"
33 #include "svn_private_config.h"
35 #include <apr_network_io.h>
37 #include "load_editor.h"
39 #define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
40 #define LOCK_RETRIES 10
43 #define LDR_DBG(x) SVN_DBG(x)
45 #define LDR_DBG(x) while(0)
49 commit_callback(const svn_commit_info_t
*commit_info
,
53 /* ### Don't print directly; generate a notification. */
54 SVN_ERR(svn_cmdline_printf(pool
, "* Loaded revision %ld.\n",
55 commit_info
->revision
));
59 /* See subversion/svnsync/main.c for docstring */
60 static svn_boolean_t
is_atomicity_error(svn_error_t
*err
)
62 return svn_error_has_cause(err
, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH
);
65 /* Acquire a lock (of sorts) on the repository associated with the
66 * given RA SESSION. This lock is just a revprop change attempt in a
67 * time-delay loop. This function is duplicated by svnsync in main.c.
69 * ### TODO: Make this function more generic and
70 * expose it through a header for use by other Subversion
71 * applications to avoid duplication.
74 get_lock(const svn_string_t
**lock_string_p
,
75 svn_ra_session_t
*session
,
78 char hostname_str
[APRMAXHOSTLEN
+ 1] = { 0 };
79 svn_string_t
*mylocktoken
, *reposlocktoken
;
81 svn_boolean_t be_atomic
;
85 SVN_ERR(svn_ra_has_capability(session
, &be_atomic
,
86 SVN_RA_CAPABILITY_ATOMIC_REVPROPS
,
90 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
92 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
93 _("Target server does not support atomic revision "
94 "property edits; consider upgrading it to 1.7 "
95 "or using an external locking program"));
96 svn_handle_warning2(stderr
, err
, "svnrdump: ");
100 apr_err
= apr_gethostname(hostname_str
, sizeof(hostname_str
), pool
);
102 return svn_error_wrap_apr(apr_err
, _("Can't get local hostname"));
104 mylocktoken
= svn_string_createf(pool
, "%s:%s", hostname_str
,
105 svn_uuid_generate(pool
));
107 /* If we succeed, this is what the property will be set to. */
108 *lock_string_p
= mylocktoken
;
110 subpool
= svn_pool_create(pool
);
112 for (i
= 0; i
< LOCK_RETRIES
; ++i
)
116 svn_pool_clear(subpool
);
118 SVN_ERR(svn_ra_rev_prop(session
, 0, SVNRDUMP_PROP_LOCK
, &reposlocktoken
,
123 /* Did we get it? If so, we're done. */
124 if (strcmp(reposlocktoken
->data
, mylocktoken
->data
) == 0)
127 /* ...otherwise, tell the user that someone else has the
128 lock and sleep before retrying. */
129 SVN_ERR(svn_cmdline_printf
130 (pool
, _("Failed to get lock on destination "
131 "repos, currently held by '%s'\n"),
132 reposlocktoken
->data
));
134 apr_sleep(apr_time_from_sec(1));
136 else if (i
< LOCK_RETRIES
- 1)
138 const svn_string_t
*unset
= NULL
;
140 /* Except in the very last iteration, try to set the lock. */
141 err
= svn_ra_change_rev_prop2(session
, 0, SVNRDUMP_PROP_LOCK
,
142 be_atomic
? &unset
: NULL
,
143 mylocktoken
, subpool
);
145 if (be_atomic
&& err
&& is_atomicity_error(err
))
147 /* Someone else has the lock. Let's loop. */
148 svn_error_clear(err
);
150 else if (be_atomic
&& err
== SVN_NO_ERROR
)
152 /* We have the lock. However, for compatibility with
153 concurrent svnrdumps that don't support atomicity, loop
154 anyway to double-check that they haven't overwritten
160 /* Genuine error, or we aren't atomic and need to loop. */
166 return svn_error_createf(APR_EINVAL
, NULL
,
167 _("Couldn't get lock on destination repos "
168 "after %d attempts"), i
);
172 new_revision_record(void **revision_baton
,
177 struct revision_baton
*rb
;
178 struct parse_baton
*pb
;
179 apr_hash_index_t
*hi
;
181 rb
= apr_pcalloc(pool
, sizeof(*rb
));
183 rb
->pool
= svn_pool_create(pool
);
186 for (hi
= apr_hash_first(pool
, headers
); hi
; hi
= apr_hash_next(hi
))
190 const char *hname
, *hval
;
192 apr_hash_this(hi
, &key
, NULL
, &val
);
196 if (strcmp(hname
, SVN_REPOS_DUMPFILE_REVISION_NUMBER
) == 0)
197 rb
->rev
= atoi(hval
);
200 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
201 them to be created in new_node_record */
202 rb
->pb
->commit_editor
= NULL
;
203 rb
->pb
->commit_edit_baton
= NULL
;
204 rb
->revprop_table
= apr_hash_make(rb
->pool
);
206 *revision_baton
= rb
;
211 uuid_record(const char *uuid
,
215 struct parse_baton
*pb
;
217 pb
->uuid
= apr_pstrdup(pool
, uuid
);
222 new_node_record(void **node_baton
,
224 void *revision_baton
,
227 const struct svn_delta_editor_t
*commit_editor
;
228 struct node_baton
*nb
;
229 struct revision_baton
*rb
;
230 struct directory_baton
*child_db
;
231 apr_hash_index_t
*hi
;
233 void *commit_edit_baton
;
235 apr_array_header_t
*residual_open_path
;
236 char *relpath_compose
;
237 const char *nb_dirname
;
238 apr_size_t residual_close_count
;
242 nb
= apr_pcalloc(rb
->pool
, sizeof(*nb
));
245 nb
->copyfrom_path
= NULL
;
246 nb
->copyfrom_rev
= SVN_INVALID_REVNUM
;
248 commit_editor
= rb
->pb
->commit_editor
;
249 commit_edit_baton
= rb
->pb
->commit_edit_baton
;
251 /* If the creation of commit_editor is pending, create it now and
252 open_root on it; also create a top-level directory baton. */
256 /* The revprop_table should have been filled in with important
257 information like svn:log in set_revision_property. We can now
258 use it all this information to create our commit_editor. But
259 first, clear revprops that we aren't allowed to set with the
260 commit_editor. We'll set them separately using the RA API
261 after closing the editor (see close_revision). */
263 apr_hash_set(rb
->revprop_table
, SVN_PROP_REVISION_AUTHOR
,
264 APR_HASH_KEY_STRING
, NULL
);
265 apr_hash_set(rb
->revprop_table
, SVN_PROP_REVISION_DATE
,
266 APR_HASH_KEY_STRING
, NULL
);
268 SVN_ERR(svn_ra_get_commit_editor3(rb
->pb
->session
, &commit_editor
,
269 &commit_edit_baton
, rb
->revprop_table
,
270 commit_callback
, NULL
, NULL
, FALSE
,
273 rb
->pb
->commit_editor
= commit_editor
;
274 rb
->pb
->commit_edit_baton
= commit_edit_baton
;
276 SVN_ERR(commit_editor
->open_root(commit_edit_baton
, rb
->rev
- 1,
277 rb
->pool
, &child_baton
));
279 LDR_DBG(("Opened root %p\n", child_baton
));
281 /* child_db corresponds to the root directory baton here */
282 child_db
= apr_pcalloc(rb
->pool
, sizeof(*child_db
));
283 child_db
->baton
= child_baton
;
285 child_db
->relpath
= svn_relpath_canonicalize("/", rb
->pool
);
286 child_db
->parent
= NULL
;
290 for (hi
= apr_hash_first(rb
->pool
, headers
); hi
; hi
= apr_hash_next(hi
))
294 const char *hname
, *hval
;
296 apr_hash_this(hi
, &key
, NULL
, &val
);
300 /* Parse the different kinds of headers we can encounter and
301 stuff them into the node_baton for writing later */
302 if (strcmp(hname
, SVN_REPOS_DUMPFILE_NODE_PATH
) == 0)
303 nb
->path
= apr_pstrdup(rb
->pool
, hval
);
304 if (strcmp(hname
, SVN_REPOS_DUMPFILE_NODE_KIND
) == 0)
305 nb
->kind
= strcmp(hval
, "file") == 0 ? svn_node_file
: svn_node_dir
;
306 if (strcmp(hname
, SVN_REPOS_DUMPFILE_NODE_ACTION
) == 0)
308 if (strcmp(hval
, "add") == 0)
309 nb
->action
= svn_node_action_add
;
310 if (strcmp(hval
, "change") == 0)
311 nb
->action
= svn_node_action_change
;
312 if (strcmp(hval
, "delete") == 0)
313 nb
->action
= svn_node_action_delete
;
314 if (strcmp(hval
, "replace") == 0)
315 nb
->action
= svn_node_action_replace
;
317 if (strcmp(hname
, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
) == 0)
318 nb
->base_checksum
= apr_pstrdup(rb
->pool
, hval
);
319 if (strcmp(hname
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
) == 0)
320 nb
->copyfrom_rev
= atoi(hval
);
321 if (strcmp(hname
, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
) == 0)
323 svn_path_url_add_component2(rb
->pb
->root_url
,
324 apr_pstrdup(rb
->pool
, hval
),
328 nb_dirname
= svn_relpath_dirname(nb
->path
, pool
);
329 if (svn_path_compare_paths(nb_dirname
,
330 rb
->db
->relpath
) != 0)
332 /* Before attempting to handle the action, call open_directory
333 for all the path components and set the directory baton
336 svn_relpath_get_longest_ancestor(nb_dirname
,
337 rb
->db
->relpath
, pool
);
338 residual_close_count
=
339 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path
,
342 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path
,
345 /* First close all as many directories as there are after
346 skip_ancestor, and then open fresh directories */
347 for (i
= 0; i
< residual_close_count
; i
++)
349 /* Don't worry about destroying the actual rb->db object,
350 since the pool we're using has the lifetime of one
352 LDR_DBG(("Closing dir %p\n", rb
->db
->baton
));
353 SVN_ERR(commit_editor
->close_directory(rb
->db
->baton
, rb
->pool
));
354 rb
->db
= rb
->db
->parent
;
357 for (i
= 0; i
< residual_open_path
->nelts
; i
++)
360 svn_relpath_join(rb
->db
->relpath
,
361 APR_ARRAY_IDX(residual_open_path
, i
, const char *),
363 SVN_ERR(commit_editor
->open_directory(relpath_compose
,
366 rb
->pool
, &child_baton
));
367 LDR_DBG(("Opened dir %p\n", child_baton
));
368 child_db
= apr_pcalloc(rb
->pool
, sizeof(*child_db
));
369 child_db
->baton
= child_baton
;
370 child_db
->depth
= rb
->db
->depth
+ 1;
371 child_db
->relpath
= relpath_compose
;
372 child_db
->parent
= rb
->db
;
379 case svn_node_action_add
:
383 SVN_ERR(commit_editor
->add_file(nb
->path
, rb
->db
->baton
,
386 rb
->pool
, &(nb
->file_baton
)));
387 LDR_DBG(("Added file %s to dir %p as %p\n", nb
->path
, rb
->db
->baton
, nb
->file_baton
));
390 SVN_ERR(commit_editor
->add_directory(nb
->path
, rb
->db
->baton
,
393 rb
->pool
, &child_baton
));
394 LDR_DBG(("Added dir %s to dir %p as %p\n", nb
->path
, rb
->db
->baton
, child_baton
));
395 child_db
= apr_pcalloc(rb
->pool
, sizeof(*child_db
));
396 child_db
->baton
= child_baton
;
397 child_db
->depth
= rb
->db
->depth
+ 1;
398 child_db
->relpath
= apr_pstrdup(rb
->pool
, nb
->path
);
399 child_db
->parent
= rb
->db
;
406 case svn_node_action_change
:
410 /* open_file to set the file_baton so we can apply props,
412 SVN_ERR(commit_editor
->open_file(nb
->path
, rb
->db
->baton
,
413 SVN_INVALID_REVNUM
, rb
->pool
,
417 /* The directory baton has already been set */
421 case svn_node_action_delete
:
422 LDR_DBG(("Deleting entry %s in %p\n", nb
->path
, rb
->db
->baton
));
423 SVN_ERR(commit_editor
->delete_entry(nb
->path
, rb
->rev
,
424 rb
->db
->baton
, rb
->pool
));
426 case svn_node_action_replace
:
427 /* Absent in dumpstream; represented as a delete + add */
436 set_revision_property(void *baton
,
438 const svn_string_t
*value
)
440 struct revision_baton
*rb
;
444 apr_hash_set(rb
->revprop_table
, apr_pstrdup(rb
->pool
, name
),
445 APR_HASH_KEY_STRING
, svn_string_dup(value
, rb
->pool
));
447 /* Special handling for revision 0; this is safe because the
448 commit_editor hasn't been created yet. */
449 SVN_ERR(svn_ra_change_rev_prop2(rb
->pb
->session
, rb
->rev
,
450 name
, NULL
, value
, rb
->pool
));
452 /* Remember any datestamp/ author that passes through (see comment
453 in close_revision). */
454 if (!strcmp(name
, SVN_PROP_REVISION_DATE
))
455 rb
->datestamp
= svn_string_dup(value
, rb
->pool
);
456 if (!strcmp(name
, SVN_PROP_REVISION_AUTHOR
))
457 rb
->author
= svn_string_dup(value
, rb
->pool
);
463 set_node_property(void *baton
,
465 const svn_string_t
*value
)
467 struct node_baton
*nb
;
468 const struct svn_delta_editor_t
*commit_editor
;
471 commit_editor
= nb
->rb
->pb
->commit_editor
;
477 LDR_DBG(("Applying properties on %p\n", nb
->file_baton
));
478 SVN_ERR(commit_editor
->change_file_prop(nb
->file_baton
, name
,
482 LDR_DBG(("Applying properties on %p\n", nb
->rb
->db
->baton
));
483 SVN_ERR(commit_editor
->change_dir_prop(nb
->rb
->db
->baton
, name
,
493 delete_node_property(void *baton
,
496 struct node_baton
*nb
;
497 const struct svn_delta_editor_t
*commit_editor
;
500 commit_editor
= nb
->rb
->pb
->commit_editor
;
503 if (nb
->kind
== svn_node_file
)
504 SVN_ERR(commit_editor
->change_file_prop(nb
->file_baton
, name
,
507 SVN_ERR(commit_editor
->change_dir_prop(nb
->rb
->db
->baton
, name
,
514 remove_node_props(void *baton
)
516 /* ### Not implemented */
521 set_fulltext(svn_stream_t
**stream
,
524 /* ### Not implemented */
529 apply_textdelta(svn_txdelta_window_handler_t
*handler
,
530 void **handler_baton
,
533 struct node_baton
*nb
;
534 const struct svn_delta_editor_t
*commit_editor
;
538 commit_editor
= nb
->rb
->pb
->commit_editor
;
540 LDR_DBG(("Applying textdelta to %p\n", nb
->file_baton
));
541 SVN_ERR(commit_editor
->apply_textdelta(nb
->file_baton
, nb
->base_checksum
,
542 pool
, handler
, handler_baton
));
548 close_node(void *baton
)
550 struct node_baton
*nb
;
551 const struct svn_delta_editor_t
*commit_editor
;
554 commit_editor
= nb
->rb
->pb
->commit_editor
;
556 if (nb
->kind
== svn_node_file
)
558 LDR_DBG(("Closing file %p\n", nb
->file_baton
));
559 SVN_ERR(commit_editor
->close_file(nb
->file_baton
, NULL
, nb
->rb
->pool
));
562 /* The svn_node_dir case is handled in close_revision */
568 close_revision(void *baton
)
570 struct revision_baton
*rb
;
571 const svn_delta_editor_t
*commit_editor
;
572 void *commit_edit_baton
;
576 commit_editor
= rb
->pb
->commit_editor
;
577 commit_edit_baton
= rb
->pb
->commit_edit_baton
;
579 /* Fake revision 0 */
581 /* ### Don't print directly; generate a notification. */
582 SVN_ERR(svn_cmdline_printf(rb
->pool
, "* Loaded revision 0.\n"));
583 else if (commit_editor
)
585 /* Close all pending open directories, and then close the edit
587 while (rb
->db
&& rb
->db
->parent
)
589 LDR_DBG(("Closing dir %p\n", rb
->db
->baton
));
590 SVN_ERR(commit_editor
->close_directory(rb
->db
->baton
, rb
->pool
));
591 rb
->db
= rb
->db
->parent
;
593 /* root dir's baton */
594 LDR_DBG(("Closing edit on %p\n", commit_edit_baton
));
595 SVN_ERR(commit_editor
->close_directory(rb
->db
->baton
, rb
->pool
));
596 SVN_ERR(commit_editor
->close_edit(commit_edit_baton
, rb
->pool
));
600 /* Legitimate revision with no node information */
601 SVN_ERR(svn_ra_get_commit_editor3(rb
->pb
->session
, &commit_editor
,
602 &commit_edit_baton
, rb
->revprop_table
,
603 commit_callback
, NULL
, NULL
, FALSE
,
606 SVN_ERR(commit_editor
->open_root(commit_edit_baton
, rb
->rev
- 1,
607 rb
->pool
, &child_baton
));
609 LDR_DBG(("Opened root %p\n", child_baton
));
610 LDR_DBG(("Closing edit on %p\n", commit_edit_baton
));
611 SVN_ERR(commit_editor
->close_directory(child_baton
, rb
->pool
));
612 SVN_ERR(commit_editor
->close_edit(commit_edit_baton
, rb
->pool
));
615 /* svn_fs_commit_txn rewrites the datestamp/ author property-
616 rewrite it by hand after closing the commit_editor. */
617 SVN_ERR(svn_ra_change_rev_prop2(rb
->pb
->session
, rb
->rev
,
618 SVN_PROP_REVISION_DATE
,
619 NULL
, rb
->datestamp
, rb
->pool
));
620 SVN_ERR(svn_ra_change_rev_prop2(rb
->pb
->session
, rb
->rev
,
621 SVN_PROP_REVISION_AUTHOR
,
622 NULL
, rb
->author
, rb
->pool
));
624 svn_pool_destroy(rb
->pool
);
630 get_dumpstream_loader(const svn_repos_parse_fns2_t
**parser
,
632 svn_ra_session_t
*session
,
635 svn_repos_parse_fns2_t
*pf
;
636 struct parse_baton
*pb
;
638 pf
= apr_pcalloc(pool
, sizeof(*pf
));
639 pf
->new_revision_record
= new_revision_record
;
640 pf
->uuid_record
= uuid_record
;
641 pf
->new_node_record
= new_node_record
;
642 pf
->set_revision_property
= set_revision_property
;
643 pf
->set_node_property
= set_node_property
;
644 pf
->delete_node_property
= delete_node_property
;
645 pf
->remove_node_props
= remove_node_props
;
646 pf
->set_fulltext
= set_fulltext
;
647 pf
->apply_textdelta
= apply_textdelta
;
648 pf
->close_node
= close_node
;
649 pf
->close_revision
= close_revision
;
651 pb
= apr_pcalloc(pool
, sizeof(*pb
));
652 pb
->session
= session
;
661 drive_dumpstream_loader(svn_stream_t
*stream
,
662 const svn_repos_parse_fns2_t
*parser
,
664 svn_ra_session_t
*session
,
665 svn_cancel_func_t cancel_func
,
669 struct parse_baton
*pb
= parse_baton
;
670 const svn_string_t
*lock_string
;
671 svn_boolean_t be_atomic
;
674 SVN_ERR(svn_ra_has_capability(session
, &be_atomic
,
675 SVN_RA_CAPABILITY_ATOMIC_REVPROPS
,
678 SVN_ERR(get_lock(&lock_string
, session
, pool
));
679 SVN_ERR(svn_ra_get_repos_root2(session
, &(pb
->root_url
), pool
));
680 SVN_ERR(svn_repos_parse_dumpstream2(stream
, parser
, parse_baton
,
681 cancel_func
, cancel_baton
, pool
));
682 err
= svn_ra_change_rev_prop2(session
, 0, SVNRDUMP_PROP_LOCK
,
683 be_atomic
? &lock_string
: NULL
, NULL
, pool
);
684 if (is_atomicity_error(err
))
685 return svn_error_quick_wrap(err
,
686 _("\"svnrdump load\"'s lock was stolen; "