Fix issue #3716 ("svnrdump does not support cancellation"). Add
[svnrdump.git] / load_editor.c
blobed33889f9ad01adffa60a869cddbf101341f44fe
1 /*
2 * load_editor.c: The svn_delta_editor_t editor used by svnrdump to
3 * load revisions.
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
21 * under the License.
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"
30 #include "svn_path.h"
31 #include "svn_ra.h"
32 #include "svn_io.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
42 #if 0
43 #define LDR_DBG(x) SVN_DBG(x)
44 #else
45 #define LDR_DBG(x) while(0)
46 #endif
48 static svn_error_t *
49 commit_callback(const svn_commit_info_t *commit_info,
50 void *baton,
51 apr_pool_t *pool)
53 /* ### Don't print directly; generate a notification. */
54 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
55 commit_info->revision));
56 return SVN_NO_ERROR;
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.
73 static svn_error_t *
74 get_lock(const svn_string_t **lock_string_p,
75 svn_ra_session_t *session,
76 apr_pool_t *pool)
78 char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
79 svn_string_t *mylocktoken, *reposlocktoken;
80 apr_status_t apr_err;
81 svn_boolean_t be_atomic;
82 apr_pool_t *subpool;
83 int i;
85 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
86 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
87 pool));
88 if (! be_atomic)
90 /* Pre-1.7 server. Can't lock without a race condition.
91 See issue #3546.
93 svn_error_t *err;
95 err = svn_error_create(
96 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
97 _("Target server does not support atomic revision property "
98 "edits; consider upgrading it to 1.7 or using an external "
99 "locking program"));
100 svn_handle_warning2(stderr, err, "svnrdump: ");
101 svn_error_clear(err);
104 apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
105 if (apr_err)
106 return svn_error_wrap_apr(apr_err, _("Can't get local hostname"));
108 mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
109 svn_uuid_generate(pool));
111 /* If we succeed, this is what the property will be set to. */
112 *lock_string_p = mylocktoken;
114 subpool = svn_pool_create(pool);
116 for (i = 0; i < LOCK_RETRIES; ++i)
118 svn_error_t *err;
120 svn_pool_clear(subpool);
122 SVN_ERR(svn_ra_rev_prop(session, 0, SVNRDUMP_PROP_LOCK, &reposlocktoken,
123 subpool));
125 if (reposlocktoken)
127 /* Did we get it? If so, we're done, otherwise we sleep. */
128 if (strcmp(reposlocktoken->data, mylocktoken->data) == 0)
129 return SVN_NO_ERROR;
130 else
132 SVN_ERR(svn_cmdline_printf
133 (pool, _("Failed to get lock on destination "
134 "repos, currently held by '%s'\n"),
135 reposlocktoken->data));
137 apr_sleep(apr_time_from_sec(1));
140 else if (i < LOCK_RETRIES - 1)
142 const svn_string_t *unset = NULL;
144 /* Except in the very last iteration, try to set the lock. */
145 err = svn_ra_change_rev_prop2(session, 0, SVNRDUMP_PROP_LOCK,
146 be_atomic ? &unset : NULL,
147 mylocktoken, subpool);
149 if (be_atomic && err && is_atomicity_error(err))
150 /* Someone else has the lock. Let's loop. */
151 svn_error_clear(err);
152 else if (be_atomic && err == SVN_NO_ERROR)
153 /* We have the lock.
155 However, for compatibility with concurrent svnsync's that don't
156 support atomicity, loop anyway to double-check that they haven't
157 overwritten our lock.
159 continue;
160 else
161 /* Genuine error, or we aren't atomic and need to loop. */
162 SVN_ERR(err);
166 return svn_error_createf(APR_EINVAL, NULL,
167 _("Couldn't get lock on destination repos "
168 "after %d attempts"), i);
171 static svn_error_t *
172 new_revision_record(void **revision_baton,
173 apr_hash_t *headers,
174 void *parse_baton,
175 apr_pool_t *pool)
177 struct revision_baton *rb;
178 struct parse_baton *pb;
179 apr_hash_index_t *hi;
181 rb = apr_pcalloc(pool, sizeof(*rb));
182 pb = parse_baton;
183 rb->pool = svn_pool_create(pool);
184 rb->pb = pb;
186 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
188 const void *key;
189 void *val;
190 const char *hname, *hval;
192 apr_hash_this(hi, &key, NULL, &val);
193 hname = key;
194 hval = 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;
207 return SVN_NO_ERROR;
210 static svn_error_t *
211 uuid_record(const char *uuid,
212 void *parse_baton,
213 apr_pool_t *pool)
215 struct parse_baton *pb;
216 pb = parse_baton;
217 pb->uuid = apr_pstrdup(pool, uuid);
218 return SVN_NO_ERROR;
221 static svn_error_t *
222 new_node_record(void **node_baton,
223 apr_hash_t *headers,
224 void *revision_baton,
225 apr_pool_t *pool)
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;
232 void *child_baton;
233 void *commit_edit_baton;
234 char *ancestor_path;
235 apr_array_header_t *residual_open_path;
236 char *relpath_compose;
237 const char *nb_dirname;
238 apr_size_t residual_close_count;
239 int i;
241 rb = revision_baton;
242 nb = apr_pcalloc(rb->pool, sizeof(*nb));
243 nb->rb = rb;
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. */
254 if (!commit_editor)
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,
271 rb->pool));
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;
284 child_db->depth = 0;
285 child_db->relpath = svn_relpath_canonicalize("/", rb->pool);
286 child_db->parent = NULL;
287 rb->db = child_db;
290 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
292 const void *key;
293 void *val;
294 const char *hname, *hval;
296 apr_hash_this(hi, &key, NULL, &val);
297 hname = key;
298 hval = 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)
322 nb->copyfrom_path =
323 svn_path_url_add_component2(rb->pb->root_url,
324 apr_pstrdup(rb->pool, hval),
325 rb->pool);
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
334 accordingly */
335 ancestor_path =
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,
340 rb->db->relpath));
341 residual_open_path =
342 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
343 nb_dirname), pool);
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
351 revision anyway */
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 ++)
359 relpath_compose =
360 svn_relpath_join(rb->db->relpath,
361 APR_ARRAY_IDX(residual_open_path, i, const char *),
362 rb->pool);
363 SVN_ERR(commit_editor->open_directory(relpath_compose,
364 rb->db->baton,
365 rb->rev - 1,
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;
373 rb->db = child_db;
377 switch (nb->action)
379 case svn_node_action_add:
380 switch (nb->kind)
382 case svn_node_file:
383 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
384 nb->copyfrom_path,
385 nb->copyfrom_rev,
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));
388 break;
389 case svn_node_dir:
390 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
391 nb->copyfrom_path,
392 nb->copyfrom_rev,
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;
400 rb->db = child_db;
401 break;
402 default:
403 break;
405 break;
406 case svn_node_action_change:
407 switch (nb->kind)
409 case svn_node_file:
410 /* open_file to set the file_baton so we can apply props,
411 txdelta to it */
412 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
413 SVN_INVALID_REVNUM, rb->pool,
414 &(nb->file_baton)));
415 break;
416 default:
417 /* The directory baton has already been set */
418 break;
420 break;
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));
425 break;
426 case svn_node_action_replace:
427 /* Absent in dumpstream; represented as a delete + add */
428 break;
431 *node_baton = nb;
432 return SVN_NO_ERROR;
435 static svn_error_t *
436 set_revision_property(void *baton,
437 const char *name,
438 const svn_string_t *value)
440 struct revision_baton *rb;
441 rb = baton;
443 if (rb->rev > 0)
444 apr_hash_set(rb->revprop_table, apr_pstrdup(rb->pool, name),
445 APR_HASH_KEY_STRING, svn_string_dup(value, rb->pool));
446 else
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);
459 return SVN_NO_ERROR;
462 static svn_error_t *
463 set_node_property(void *baton,
464 const char *name,
465 const svn_string_t *value)
467 struct node_baton *nb;
468 const struct svn_delta_editor_t *commit_editor;
469 apr_pool_t *pool;
470 nb = baton;
471 commit_editor = nb->rb->pb->commit_editor;
472 pool = nb->rb->pool;
474 switch (nb->kind)
476 case svn_node_file:
477 LDR_DBG(("Applying properties on %p\n", nb->file_baton));
478 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
479 value, pool));
480 break;
481 case svn_node_dir:
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,
484 value, pool));
485 break;
486 default:
487 break;
489 return SVN_NO_ERROR;
492 static svn_error_t *
493 delete_node_property(void *baton,
494 const char *name)
496 struct node_baton *nb;
497 const struct svn_delta_editor_t *commit_editor;
498 apr_pool_t *pool;
499 nb = baton;
500 commit_editor = nb->rb->pb->commit_editor;
501 pool = nb->rb->pool;
503 if (nb->kind == svn_node_file)
504 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, name,
505 NULL, pool));
506 else
507 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, name,
508 NULL, pool));
510 return SVN_NO_ERROR;
513 static svn_error_t *
514 remove_node_props(void *baton)
516 /* ### Not implemented */
517 return SVN_NO_ERROR;
520 static svn_error_t *
521 set_fulltext(svn_stream_t **stream,
522 void *node_baton)
524 /* ### Not implemented */
525 return SVN_NO_ERROR;
528 static svn_error_t *
529 apply_textdelta(svn_txdelta_window_handler_t *handler,
530 void **handler_baton,
531 void *node_baton)
533 struct node_baton *nb;
534 const struct svn_delta_editor_t *commit_editor;
535 apr_pool_t *pool;
537 nb = node_baton;
538 commit_editor = nb->rb->pb->commit_editor;
539 pool = nb->rb->pool;
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));
544 return SVN_NO_ERROR;
547 static svn_error_t *
548 close_node(void *baton)
550 struct node_baton *nb;
551 const struct svn_delta_editor_t *commit_editor;
553 nb = baton;
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 */
564 return SVN_NO_ERROR;
567 static svn_error_t *
568 close_revision(void *baton)
570 struct revision_baton *rb;
571 const svn_delta_editor_t *commit_editor;
572 void *commit_edit_baton;
573 void *child_baton;
574 rb = baton;
576 commit_editor = rb->pb->commit_editor;
577 commit_edit_baton = rb->pb->commit_edit_baton;
579 /* Fake revision 0 */
580 if (rb->rev == 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
586 session itself */
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));
598 else
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,
604 rb->pool));
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);
626 return SVN_NO_ERROR;
629 svn_error_t *
630 get_dumpstream_loader(const svn_repos_parse_fns2_t **parser,
631 void **parse_baton,
632 svn_ra_session_t *session,
633 apr_pool_t *pool)
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;
654 *parser = pf;
655 *parse_baton = pb;
657 return SVN_NO_ERROR;
660 svn_error_t *
661 drive_dumpstream_loader(svn_stream_t *stream,
662 const svn_repos_parse_fns2_t *parser,
663 void *parse_baton,
664 svn_ra_session_t *session,
665 svn_cancel_func_t cancel_func,
666 void *cancel_baton,
667 apr_pool_t *pool)
669 struct parse_baton *pb = parse_baton;
670 const svn_string_t *lock_string;
671 svn_boolean_t be_atomic;
672 svn_error_t *err;
674 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
675 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
676 pool));
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; "
687 "can't remove it"));
689 return SVN_NO_ERROR;