1 /*
2 * load_editor.c: The svn_delta_editor_t editor used by svnrdump to
3 * load revisions.
4 *
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
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
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 * ====================================================================
23 */
24
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_subst.h"
33 #include "svn_io.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"
39
40 #include "svnrdump.h"
41
42 #define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
43
44 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
45
46
47 /**
48 * A vtable that is driven by get_dumpstream_loader().
49 */
50 typedef struct loader_fns_t
51 {
52 /* Callback on starting a revision, to obtain an editor for the revision.
53 *
54 * This callback returns in *EDITOR and *EDIT_BATON an editor through which
55 * its caller will drive the changes for revision REVISION.
56 *
57 * This callback should call the editor's SET_TARGET_REVISION method if
58 * required.
59 *
60 * REV_PROPS holds the revision properties.
61 *
62 * POOL (and REV_PROPS) will persist until after the REVFINISH callback
63 * returns.
64 *
65 * Modelled on svn_ra_replay_revstart_callback_t.
66 */
67 svn_error_t *(*revstart)(
68 svn_revnum_t revision,
69 void *baton,
70 const svn_delta_editor_t **editor,
71 void **edit_baton,
72 apr_hash_t *rev_props,
73 apr_pool_t *pool);
74
75 /* Fetch the node props for ORIG_PATH @ ORIG_REV, a node of kind KIND.
76 *
77 * This callback is required if a non-deltas-format dump is parsed;
78 * otherwise it will not be called and may be null. If required and not
79 * provided, the loader will return SVN_ERR_UNSUPPORTED_FEATURE.
80 *
81 * Only regular props are needed. Any special kinds of property such as
82 * entry-props and DAV/WC-props will be ignored.
83 */
84 svn_error_t *(*fetch_props)(
85 apr_hash_t **props,
86 void *baton,
87 const char *orig_path,
88 svn_revnum_t orig_rev,
89 svn_node_kind_t kind,
90 apr_pool_t *result_pool,
91 apr_pool_t *scratch_pool);
92
93 } loader_fns_t;
94
95 /**
96 * General baton used by the parser functions.
97 */
98 struct parse_baton
99 {
100 /* RA session(s) for committing to the target repository. */
101 svn_ra_session_t *session;
102 svn_ra_session_t *aux_session;
103
104 /* To bleep, or not to bleep? (What kind of question is that?) */
105 svn_boolean_t quiet;
106
107 /* Root URL of the target repository. */
108 const char *root_url;
109
110 /* The "parent directory" of the target repository in which to load.
111 (This is essentially the difference between ROOT_URL and
112 SESSION's url, and roughly equivalent to the 'svnadmin load
113 --parent-dir' option.) */
114 const char *parent_dir;
115
116 /* A mapping of svn_revnum_t * dump stream revisions to their
117 corresponding svn_revnum_t * target repository revisions. */
118 /* ### See https://issues.apache.org/jira/browse/SVN-3903
119 ### for discussion about improving the memory costs of this mapping. */
120 apr_hash_t *rev_map;
121
122 /* The most recent (youngest) revision from the dump stream mapped in
123 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
124 svn_revnum_t last_rev_mapped;
125
126 /* The oldest revision loaded from the dump stream, or
127 SVN_INVALID_REVNUM if none have been loaded. */
128 svn_revnum_t oldest_dumpstream_rev;
129
130 /* An hash containing specific revision properties to skip while
131 loading. */
132 apr_hash_t *skip_revprops;
133
134 const loader_fns_t *callbacks;
135 void *cb_baton;
136 };
137
138 /**
139 * Use to wrap the dir_context_t in commit.c so we can keep track of
140 * relpath and parent for open_directory and close_directory.
141 */
142 struct directory_baton
143 {
144 void *baton;
145 const char *relpath;
146
147 /* The copy-from source of this directory, no matter whether it is
148 copied explicitly (the root node of a copy) or implicitly (being an
149 existing child of a copied directory). For a node that is newly
150 added (without history), even inside a copied parent, these are
151 NULL and SVN_INVALID_REVNUM. */
152 const char *copyfrom_path;
153 svn_revnum_t copyfrom_rev;
154
155 struct directory_baton *parent;
156 };
157
158 /**
159 * Baton used to represent a node; to be used by the parser
160 * functions. Contains a link to the revision baton.
161 */
162 struct node_baton
163 {
164 const char *path;
165 svn_node_kind_t kind;
166 enum svn_node_action action;
167
168 /* Is this directory explicitly added? If not, then it already existed
169 or is a child of a copy. */
170 svn_boolean_t is_added;
171
172 svn_revnum_t copyfrom_rev;
173 const char *copyfrom_path;
174 const char *copyfrom_url;
175
176 void *file_baton;
177 const char *base_checksum;
178
179 /* (const char *name) -> (svn_prop_t *) */
180 apr_hash_t *prop_changes;
181
182 struct revision_baton *rb;
183 };
184
185 /**
186 * Baton used to represet a revision; used by the parser
187 * functions. Contains a link to the parser baton.
188 */
189 struct revision_baton
190 {
191 svn_revnum_t rev;
192 apr_hash_t *revprop_table;
193 svn_revnum_t head_rev_before_commit;
194 apr_int32_t rev_offset;
195
196 /* Commit editor and baton used to transfer loaded revisions to
197 the target repository. */
198 const svn_delta_editor_t *commit_editor;
199 void *commit_edit_baton;
200
201 const svn_string_t *datestamp;
202 const svn_string_t *author;
203
204 struct parse_baton *pb;
205 struct directory_baton *db;
206 apr_pool_t *pool;
207 };
208
209
210
211 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
212 anything added to the hash is allocated in the hash's pool. */
213 static void
set_revision_mapping(apr_hash_t * rev_map,svn_revnum_t from_rev,svn_revnum_t to_rev)214 set_revision_mapping(apr_hash_t *rev_map,
215 svn_revnum_t from_rev,
216 svn_revnum_t to_rev)
217 {
218 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
219 sizeof(svn_revnum_t) * 2);
220 mapped_revs[0] = from_rev;
221 mapped_revs[1] = to_rev;
222 apr_hash_set(rev_map, mapped_revs, sizeof(*mapped_revs), mapped_revs + 1);
223 }
224
225 /* Return the revision to which FROM_REV maps in REV_MAP, or
226 SVN_INVALID_REVNUM if no such mapping exists. */
227 static svn_revnum_t
get_revision_mapping(apr_hash_t * rev_map,svn_revnum_t from_rev)228 get_revision_mapping(apr_hash_t *rev_map,
229 svn_revnum_t from_rev)
230 {
231 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
232 sizeof(from_rev));
233 return to_rev ? *to_rev : SVN_INVALID_REVNUM;
234 }
235
236
237 static svn_error_t *
magic_header_record(int version,void * parse_baton,apr_pool_t * pool)238 magic_header_record(int version,
239 void *parse_baton,
240 apr_pool_t *pool)
241 {
242 return SVN_NO_ERROR;
243 }
244
245 static svn_error_t *
uuid_record(const char * uuid,void * parse_baton,apr_pool_t * pool)246 uuid_record(const char *uuid,
247 void *parse_baton,
248 apr_pool_t *pool)
249 {
250 return SVN_NO_ERROR;
251 }
252
253 /* Push information about another directory onto the linked list RB->db.
254 *
255 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
256 * repository-relative path of this directory. IS_ADDED is true iff this
257 * directory is being added (with or without history). If added with
258 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
259 * are NULL/SVN_INVALID_REVNUM.
260 */
261 static void
push_directory(struct revision_baton * rb,void * child_baton,const char * relpath,svn_boolean_t is_added,const char * copyfrom_path,svn_revnum_t copyfrom_rev)262 push_directory(struct revision_baton *rb,
263 void *child_baton,
264 const char *relpath,
265 svn_boolean_t is_added,
266 const char *copyfrom_path,
267 svn_revnum_t copyfrom_rev)
268 {
269 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
270
271 SVN_ERR_ASSERT_NO_RETURN(
272 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
273
274 /* If this node is an existing (not newly added) child of a copied node,
275 calculate where it was copied from. */
276 if (!is_added
277 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
278 {
279 const char *name = svn_relpath_basename(relpath, NULL);
280
281 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
282 rb->pool);
283 copyfrom_rev = rb->db->copyfrom_rev;
284 }
285
286 child_db->baton = child_baton;
287 child_db->relpath = relpath;
288 child_db->copyfrom_path = copyfrom_path;
289 child_db->copyfrom_rev = copyfrom_rev;
290 child_db->parent = rb->db;
291 rb->db = child_db;
292 }
293
294 /* Called to obtain an editor for the revision described by RB.
295 */
296 static svn_error_t *
revision_start_edit(struct revision_baton * rb,apr_pool_t * scratch_pool)297 revision_start_edit(struct revision_baton *rb,
298 apr_pool_t *scratch_pool)
299 {
300 struct parse_baton *pb = rb->pb;
301 void *child_baton;
302
303 SVN_ERR(pb->callbacks->revstart(rb->rev,
304 pb->cb_baton,
305 &rb->commit_editor, &rb->commit_edit_baton,
306 rb->revprop_table,
307 rb->pool));
308 SVN_ERR(rb->commit_editor->open_root(rb->commit_edit_baton,
309 rb->head_rev_before_commit,
310 rb->pool, &child_baton));
311 /* child_baton corresponds to the root directory baton here */
312 push_directory(rb, child_baton, "", TRUE /*is_added*/,
313 NULL, SVN_INVALID_REVNUM);
314 return SVN_NO_ERROR;
315 }
316
317 static svn_error_t *
new_revision_record(void ** revision_baton,apr_hash_t * headers,void * parse_baton,apr_pool_t * pool)318 new_revision_record(void **revision_baton,
319 apr_hash_t *headers,
320 void *parse_baton,
321 apr_pool_t *pool)
322 {
323 struct parse_baton *pb = parse_baton;
324 struct revision_baton *rb;
325 const char *rev_str;
326
327 rb = apr_pcalloc(pool, sizeof(*rb));
328 rb->pool = svn_pool_create(pool);
329 rb->pb = pb;
330 rb->db = NULL;
331
332 rev_str = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
333 if (rev_str)
334 rb->rev = SVN_STR_TO_REV(rev_str);
335
336 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &rb->head_rev_before_commit,
337 pool));
338
339 /* FIXME: This is a lame fallback loading multiple segments of dump in
340 several separate operations. It is highly susceptible to race conditions.
341 Calculate the revision 'offset' for finding copyfrom sources.
342 It might be positive or negative. */
343 rb->rev_offset = (apr_int32_t) ((rb->rev) - (rb->head_rev_before_commit + 1));
344
345 /* Stash the oldest (non-zero) dumpstream revision seen. */
346 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
347 pb->oldest_dumpstream_rev = rb->rev;
348
349 /* Set the commit_editor/ commit_edit_baton to NULL and wait for
350 them to be created in new_node_record */
351 rb->commit_editor = NULL;
352 rb->commit_edit_baton = NULL;
353 rb->revprop_table = apr_hash_make(rb->pool);
354
355 *revision_baton = rb;
356 return SVN_NO_ERROR;
357 }
358
359 static svn_error_t *
new_node_record(void ** node_baton,apr_hash_t * headers,void * revision_baton,apr_pool_t * pool)360 new_node_record(void **node_baton,
361 apr_hash_t *headers,
362 void *revision_baton,
363 apr_pool_t *pool)
364 {
365 struct revision_baton *rb = revision_baton;
366 const struct svn_delta_editor_t *commit_editor = rb->commit_editor;
367 struct node_baton *nb;
368 apr_hash_index_t *hi;
369 void *child_baton;
370 const char *nb_dirname;
371
372 nb = apr_pcalloc(rb->pool, sizeof(*nb));
373 nb->rb = rb;
374 nb->is_added = FALSE;
375 nb->copyfrom_path = NULL;
376 nb->copyfrom_url = NULL;
377 nb->copyfrom_rev = SVN_INVALID_REVNUM;
378 nb->prop_changes = apr_hash_make(rb->pool);
379
380 /* If the creation of commit_editor is pending, create it now and
381 open_root on it; also create a top-level directory baton. */
382 if (!commit_editor)
383 {
384 /* The revprop_table should have been filled in with important
385 information like svn:log in set_revision_property. We can now
386 use it all this information to create our commit_editor. But
387 first, clear revprops that we aren't allowed to set with the
388 commit_editor. We'll set them separately using the RA API
389 after closing the editor (see close_revision). */
390
391 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
392 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
393
394 SVN_ERR(revision_start_edit(rb, pool));
395 commit_editor = rb->commit_editor;
396 }
397
398 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
399 {
400 const char *hname = apr_hash_this_key(hi);
401 const char *hval = apr_hash_this_val(hi);
402
403 /* Parse the different kinds of headers we can encounter and
404 stuff them into the node_baton for writing later */
405 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
406 nb->path = apr_pstrdup(rb->pool, hval);
407 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
408 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
409 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
410 {
411 if (strcmp(hval, "add") == 0)
412 nb->action = svn_node_action_add;
413 if (strcmp(hval, "change") == 0)
414 nb->action = svn_node_action_change;
415 if (strcmp(hval, "delete") == 0)
416 nb->action = svn_node_action_delete;
417 if (strcmp(hval, "replace") == 0)
418 nb->action = svn_node_action_replace;
419 }
420 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
421 nb->base_checksum = apr_pstrdup(rb->pool, hval);
422 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
423 nb->copyfrom_rev = SVN_STR_TO_REV(hval);
424 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
425 nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
426 }
427
428 /* Before handling the new node, ensure depth-first editing order by
429 traversing the directory hierarchy from the old node's to the new
430 node's parent directory. */
431 nb_dirname = svn_relpath_dirname(nb->path, pool);
432 if (svn_path_compare_paths(nb_dirname,
433 rb->db->relpath) != 0)
434 {
435 char *ancestor_path;
436 apr_size_t residual_close_count;
437 apr_array_header_t *residual_open_path;
438 int i;
439 apr_size_t n;
440
441 ancestor_path =
442 svn_relpath_get_longest_ancestor(nb_dirname,
443 rb->db->relpath, pool);
444 residual_close_count =
445 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
446 rb->db->relpath));
447 residual_open_path =
448 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
449 nb_dirname), pool);
450
451 /* First close all as many directories as there are after
452 skip_ancestor, and then open fresh directories */
453 for (n = 0; n < residual_close_count; n ++)
454 {
455 /* Don't worry about destroying the actual rb->db object,
456 since the pool we're using has the lifetime of one
457 revision anyway */
458 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
459 rb->db = rb->db->parent;
460 }
461
462 for (i = 0; i < residual_open_path->nelts; i ++)
463 {
464 char *relpath_compose =
465 svn_relpath_join(rb->db->relpath,
466 APR_ARRAY_IDX(residual_open_path, i, const char *),
467 rb->pool);
468 SVN_ERR(commit_editor->open_directory(relpath_compose,
469 rb->db->baton,
470 rb->head_rev_before_commit,
471 rb->pool, &child_baton));
472 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
473 NULL, SVN_INVALID_REVNUM);
474 }
475 }
476
477 /* Fix up the copyfrom information in light of mapped revisions and
478 non-root load targets, and convert copyfrom path into a full
479 URL. */
480 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
481 {
482 svn_revnum_t copyfrom_rev;
483
484 /* Try to find the copyfrom revision in the revision map;
485 failing that, fall back to the revision offset approach. */
486 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
487 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
488 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
489
490 if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
491 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
492 _("Relative source revision %ld is not"
493 " available in current repository"),
494 copyfrom_rev);
495
496 nb->copyfrom_rev = copyfrom_rev;
497
498 if (rb->pb->parent_dir)
499 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
500 nb->copyfrom_path, rb->pool);
501 /* Convert to a URL, as the commit editor requires. */
502 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
503 nb->copyfrom_path,
504 rb->pool);
505 }
506
507
508 switch (nb->action)
509 {
510 case svn_node_action_delete:
511 case svn_node_action_replace:
512 SVN_ERR(commit_editor->delete_entry(nb->path,
513 rb->head_rev_before_commit,
514 rb->db->baton, rb->pool));
515 if (nb->action == svn_node_action_delete)
516 break;
517 else
518 /* FALL THROUGH */;
519 case svn_node_action_add:
520 nb->is_added = TRUE;
521 switch (nb->kind)
522 {
523 case svn_node_file:
524 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
525 nb->copyfrom_url,
526 nb->copyfrom_rev,
527 rb->pool, &(nb->file_baton)));
528 break;
529 case svn_node_dir:
530 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
531 nb->copyfrom_url,
532 nb->copyfrom_rev,
533 rb->pool, &child_baton));
534 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
535 nb->copyfrom_path, nb->copyfrom_rev);
536 break;
537 default:
538 break;
539 }
540 break;
541 case svn_node_action_change:
542 switch (nb->kind)
543 {
544 case svn_node_file:
545 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
546 SVN_INVALID_REVNUM, rb->pool,
547 &(nb->file_baton)));
548 break;
549 default:
550 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
551 rb->head_rev_before_commit,
552 rb->pool, &child_baton));
553 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
554 NULL, SVN_INVALID_REVNUM);
555 break;
556 }
557 break;
558 }
559
560 *node_baton = nb;
561 return SVN_NO_ERROR;
562 }
563
564 static svn_error_t *
set_revision_property(void * baton,const char * name,const svn_string_t * value)565 set_revision_property(void *baton,
566 const char *name,
567 const svn_string_t *value)
568 {
569 struct revision_baton *rb = baton;
570
571 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value,
572 rb->pool, rb->pool));
573 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
574
575 if (rb->rev > 0)
576 {
577 if (! svn_hash_gets(rb->pb->skip_revprops, name))
578 svn_hash_sets(rb->revprop_table,
579 apr_pstrdup(rb->pool, name),
580 svn_string_dup(value, rb->pool));
581 }
582 else if (rb->head_rev_before_commit == 0
583 && ! svn_hash_gets(rb->pb->skip_revprops, name))
584 {
585 /* Special case: set revision 0 properties directly (which is
586 safe because the commit_editor hasn't been created yet), but
587 only when loading into an 'empty' filesystem. */
588 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
589 name, NULL, value, rb->pool));
590 }
591
592 /* Remember any datestamp/ author that passes through (see comment
593 in close_revision). */
594 if (!strcmp(name, SVN_PROP_REVISION_DATE))
595 rb->datestamp = svn_string_dup(value, rb->pool);
596 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
597 rb->author = svn_string_dup(value, rb->pool);
598
599 return SVN_NO_ERROR;
600 }
601
602 static svn_error_t *
set_node_property(void * baton,const char * name,const svn_string_t * value)603 set_node_property(void *baton,
604 const char *name,
605 const svn_string_t *value)
606 {
607 struct node_baton *nb = baton;
608 struct revision_baton *rb = nb->rb;
609 struct parse_baton *pb = rb->pb;
610 apr_pool_t *pool = nb->rb->pool;
611 svn_prop_t *prop;
612
613 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
614 {
615 svn_string_t *new_value;
616 svn_error_t *err;
617
618 err = svn_repos__adjust_mergeinfo_property(&new_value, value,
619 pb->parent_dir,
620 pb->rev_map,
621 pb->oldest_dumpstream_rev,
622 rb->rev_offset,
623 NULL, NULL, /*notify*/
624 pool, pool);
625 if (err)
626 {
627 return svn_error_quick_wrap(err,
628 _("Invalid svn:mergeinfo value"));
629 }
630
631 value = new_value;
632 }
633
634 SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
635
636 SVN_ERR(svn_repos__validate_prop(name, value, pool));
637
638 prop = apr_palloc(nb->rb->pool, sizeof (*prop));
639 prop->name = apr_pstrdup(pool, name);
640 prop->value = svn_string_dup(value, pool);
641 svn_hash_sets(nb->prop_changes, prop->name, prop);
642
643 return SVN_NO_ERROR;
644 }
645
646 static svn_error_t *
delete_node_property(void * baton,const char * name)647 delete_node_property(void *baton,
648 const char *name)
649 {
650 struct node_baton *nb = baton;
651 apr_pool_t *pool = nb->rb->pool;
652 svn_prop_t *prop;
653
654 SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
655
656 prop = apr_palloc(pool, sizeof (*prop));
657 prop->name = apr_pstrdup(pool, name);
658 prop->value = NULL;
659 svn_hash_sets(nb->prop_changes, prop->name, prop);
660
661 return SVN_NO_ERROR;
662 }
663
664 /* Delete all the properties of the node, if any.
665 *
666 * The commit editor doesn't have a method to delete a node's properties
667 * without knowing what they are, so we have to first find out what
668 * properties the node would have had. If it's copied (explicitly or
669 * implicitly), we look at the copy source. If it's only being changed,
670 * we look at the node's current path in the head revision.
671 */
672 static svn_error_t *
remove_node_props(void * baton)673 remove_node_props(void *baton)
674 {
675 struct node_baton *nb = baton;
676 struct revision_baton *rb = nb->rb;
677 apr_pool_t *pool = nb->rb->pool;
678 apr_hash_index_t *hi;
679 apr_hash_t *props;
680 const char *orig_path;
681 svn_revnum_t orig_rev;
682
683 /* Find the path and revision that has the node's original properties */
684 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
685 {
686 orig_path = nb->copyfrom_path;
687 orig_rev = nb->copyfrom_rev;
688 }
689 else if (!nb->is_added
690 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
691 {
692 /* If this is a dir, then it's described by rb->db;
693 if this is a file, then it's a child of the dir in rb->db. */
694 orig_path = (nb->kind == svn_node_dir)
695 ? rb->db->copyfrom_path
696 : svn_relpath_join(rb->db->copyfrom_path,
697 svn_relpath_basename(nb->path, NULL),
698 rb->pool);
699 orig_rev = rb->db->copyfrom_rev;
700 }
701 else
702 {
703 /* ### Should we query at a known, fixed, "head" revision number
704 instead of passing SVN_INVALID_REVNUM and getting a moving target? */
705 orig_path = nb->path;
706 orig_rev = SVN_INVALID_REVNUM;
707 }
708
709 if ((nb->action == svn_node_action_add
710 || nb->action == svn_node_action_replace)
711 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
712 /* Add-without-history; no "old" properties to worry about. */
713 return SVN_NO_ERROR;
714
715 if (! rb->pb->callbacks->fetch_props)
716 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
717 _("This dumpstream reader requires a delta "
718 "format dumpstream"));
719 SVN_ERR(rb->pb->callbacks->fetch_props(&props,
720 rb->pb->cb_baton,
721 orig_path, orig_rev, nb->kind,
722 pool, pool));
723
724 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
725 {
726 const char *name = apr_hash_this_key(hi);
727 svn_prop_kind_t kind = svn_property_kind2(name);
728
729 if (kind == svn_prop_regular_kind)
730 SVN_ERR(set_node_property(nb, name, NULL));
731 }
732
733 return SVN_NO_ERROR;
734 }
735
736 static svn_error_t *
set_fulltext(svn_stream_t ** stream,void * node_baton)737 set_fulltext(svn_stream_t **stream,
738 void *node_baton)
739 {
740 struct node_baton *nb = node_baton;
741 const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
742 svn_txdelta_window_handler_t handler;
743 void *handler_baton;
744 apr_pool_t *pool = nb->rb->pool;
745
746 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
747 pool, &handler, &handler_baton));
748 *stream = svn_txdelta_target_push(handler, handler_baton,
749 svn_stream_empty(pool), pool);
750 return SVN_NO_ERROR;
751 }
752
753 static svn_error_t *
apply_textdelta(svn_txdelta_window_handler_t * handler,void ** handler_baton,void * node_baton)754 apply_textdelta(svn_txdelta_window_handler_t *handler,
755 void **handler_baton,
756 void *node_baton)
757 {
758 struct node_baton *nb = node_baton;
759 const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
760 apr_pool_t *pool = nb->rb->pool;
761
762 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
763 pool, handler, handler_baton));
764
765 return SVN_NO_ERROR;
766 }
767
768 static svn_error_t *
close_node(void * baton)769 close_node(void *baton)
770 {
771 struct node_baton *nb = baton;
772 const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
773 apr_pool_t *pool = nb->rb->pool;
774 apr_hash_index_t *hi;
775
776 for (hi = apr_hash_first(pool, nb->prop_changes);
777 hi; hi = apr_hash_next(hi))
778 {
779 const char *name = apr_hash_this_key(hi);
780 svn_prop_t *prop = apr_hash_this_val(hi);
781
782 switch (nb->kind)
783 {
784 case svn_node_file:
785 SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
786 name, prop->value, pool));
787 break;
788 case svn_node_dir:
789 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
790 name, prop->value, pool));
791 break;
792 default:
793 break;
794 }
795 }
796
797 /* Pass a file node closure through to the editor *unless* we
798 deleted the file (which doesn't require us to open it). */
799 if ((nb->kind == svn_node_file) && (nb->file_baton))
800 {
801 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
802 }
803
804 /* The svn_node_dir case is handled in close_revision */
805
806 return SVN_NO_ERROR;
807 }
808
809 static svn_error_t *
close_revision(void * baton)810 close_revision(void *baton)
811 {
812 struct revision_baton *rb = baton;
813 const svn_delta_editor_t *commit_editor = rb->commit_editor;
814 void *commit_edit_baton = rb->commit_edit_baton;
815 svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
816
817 /* Fake revision 0 */
818 if (rb->rev == 0)
819 {
820 /* ### Don't print directly; generate a notification. */
821 if (! rb->pb->quiet)
822 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
823 }
824 else if (commit_editor)
825 {
826 /* Close all pending open directories, and then close the edit
827 session itself */
828 while (rb->db && rb->db->parent)
829 {
830 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
831 rb->db = rb->db->parent;
832 }
833 /* root dir's baton */
834 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
835 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
836 }
837 else
838 {
839 /* Legitimate revision with no node information */
840 SVN_ERR(revision_start_edit(rb, rb->pool));
841 commit_editor = rb->commit_editor;
842 commit_edit_baton = rb->commit_edit_baton;
843
844 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
845 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
846 }
847
848 /* svn_fs_commit_txn() rewrites the datestamp and author properties;
849 we'll rewrite them again by hand after closing the commit_editor.
850 The only time we don't do this is for revision 0 when loaded into
851 a non-empty repository. */
852 if (rb->rev > 0)
853 {
854 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
855 }
856 else if (rb->head_rev_before_commit == 0)
857 {
858 committed_rev = 0;
859 }
860
861 if (SVN_IS_VALID_REVNUM(committed_rev))
862 {
863 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE))
864 {
865 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
866 SVN_PROP_REVISION_DATE,
867 NULL, rb->datestamp, rb->pool));
868 }
869 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR))
870 {
871 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
872 SVN_PROP_REVISION_AUTHOR,
873 NULL, rb->author, rb->pool));
874 }
875 }
876
877 svn_pool_destroy(rb->pool);
878
879 return SVN_NO_ERROR;
880 }
881
882 /*----------------------------------------------------------------------*/
883
884 /**
885 * Baton used for commit callback (and Ev2 shims).
886 */
887 struct commit_baton_t
888 {
889 svn_revnum_t rev;
890 struct parse_baton *pb;
891 };
892
893 /*
894 * - Notification of the commit.
895 * - Update the revision number mapping to take account of the actual
896 * committed revision number.
897 */
898 static svn_error_t *
commit_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)899 commit_callback(const svn_commit_info_t *commit_info,
900 void *baton,
901 apr_pool_t *pool)
902 {
903 struct commit_baton_t *cb = baton;
904 struct parse_baton *pb = cb->pb;
905
906 /* ### Don't print directly; generate a notification. */
907 if (! pb->quiet)
908 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
909 commit_info->revision));
910
911 /* Add the mapping of the dumpstream revision to the committed revision. */
912 set_revision_mapping(pb->rev_map, cb->rev, commit_info->revision);
913
914 /* If the incoming dump stream has non-contiguous revisions (e.g. from
915 using svndumpfilter --drop-empty-revs without --renumber-revs) then
916 we must account for the missing gaps in PB->REV_MAP. Otherwise we
917 might not be able to map all mergeinfo source revisions to the correct
918 revisions in the target repos. */
919 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
920 && (cb->rev != pb->last_rev_mapped + 1))
921 {
922 svn_revnum_t i;
923
924 for (i = pb->last_rev_mapped + 1; i < cb->rev; i++)
925 {
926 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
927 }
928 }
929
930 /* Update our "last revision mapped". */
931 pb->last_rev_mapped = cb->rev;
932
933 return SVN_NO_ERROR;
934 }
935
936 static svn_error_t *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)937 fetch_base_func(const char **filename,
938 void *baton,
939 const char *path,
940 svn_revnum_t base_revision,
941 apr_pool_t *result_pool,
942 apr_pool_t *scratch_pool)
943 {
944 struct commit_baton_t *cb = baton;
945 svn_stream_t *fstream;
946 svn_error_t *err;
947
948 if (! SVN_IS_VALID_REVNUM(base_revision))
949 base_revision = cb->rev - 1;
950
951 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
952 svn_io_file_del_on_pool_cleanup,
953 result_pool, scratch_pool));
954
955 err = svn_ra_get_file(cb->pb->aux_session, path, base_revision,
956 fstream, NULL, NULL, scratch_pool);
957 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
958 {
959 svn_error_clear(err);
960 SVN_ERR(svn_stream_close(fstream));
961
962 *filename = NULL;
963 return SVN_NO_ERROR;
964 }
965 else if (err)
966 return svn_error_trace(err);
967
968 SVN_ERR(svn_stream_close(fstream));
969
970 return SVN_NO_ERROR;
971 }
972
973 static svn_error_t *
fetch_props(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,svn_node_kind_t node_kind,apr_pool_t * result_pool,apr_pool_t * scratch_pool)974 fetch_props(apr_hash_t **props,
975 void *baton,
976 const char *path,
977 svn_revnum_t base_revision,
978 svn_node_kind_t node_kind,
979 apr_pool_t *result_pool,
980 apr_pool_t *scratch_pool)
981 {
982 struct parse_baton *pb = baton;
983
984 if (node_kind == svn_node_file)
985 {
986 SVN_ERR(svn_ra_get_file(pb->aux_session, path, base_revision,
987 NULL, NULL, props, result_pool));
988 }
989 else if (node_kind == svn_node_dir)
990 {
991 apr_array_header_t *tmp_props;
992
993 SVN_ERR(svn_ra_get_dir2(pb->aux_session, NULL, NULL, props, path,
994 base_revision, 0 /* Dirent fields */,
995 result_pool));
996 tmp_props = svn_prop_hash_to_array(*props, result_pool);
997 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
998 result_pool));
999 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1000 }
1001 else
1002 {
1003 *props = apr_hash_make(result_pool);
1004 }
1005
1006 return SVN_NO_ERROR;
1007 }
1008
1009 static svn_error_t *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1010 fetch_props_func(apr_hash_t **props,
1011 void *baton,
1012 const char *path,
1013 svn_revnum_t base_revision,
1014 apr_pool_t *result_pool,
1015 apr_pool_t *scratch_pool)
1016 {
1017 struct commit_baton_t *cb = baton;
1018 svn_node_kind_t node_kind;
1019
1020 if (! SVN_IS_VALID_REVNUM(base_revision))
1021 base_revision = cb->rev - 1;
1022
1023 SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision,
1024 &node_kind, scratch_pool));
1025 SVN_ERR(fetch_props(props, cb->pb, path, base_revision, node_kind,
1026 result_pool, scratch_pool));
1027
1028 return SVN_NO_ERROR;
1029 }
1030
1031 static svn_error_t *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)1032 fetch_kind_func(svn_node_kind_t *kind,
1033 void *baton,
1034 const char *path,
1035 svn_revnum_t base_revision,
1036 apr_pool_t *scratch_pool)
1037 {
1038 struct commit_baton_t *cb = baton;
1039
1040 if (! SVN_IS_VALID_REVNUM(base_revision))
1041 base_revision = cb->rev - 1;
1042
1043 SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision,
1044 kind, scratch_pool));
1045
1046 return SVN_NO_ERROR;
1047 }
1048
1049 static svn_delta_shim_callbacks_t *
get_shim_callbacks(struct commit_baton_t * cb,apr_pool_t * pool)1050 get_shim_callbacks(struct commit_baton_t *cb,
1051 apr_pool_t *pool)
1052 {
1053 svn_delta_shim_callbacks_t *callbacks =
1054 svn_delta_shim_callbacks_default(pool);
1055
1056 callbacks->fetch_props_func = fetch_props_func;
1057 callbacks->fetch_kind_func = fetch_kind_func;
1058 callbacks->fetch_base_func = fetch_base_func;
1059 callbacks->fetch_baton = cb;
1060
1061 return callbacks;
1062 }
1063
1064 /* */
1065 static svn_error_t *
revstart(svn_revnum_t revision,void * baton,const svn_delta_editor_t ** editor_p,void ** edit_baton_p,apr_hash_t * rev_props,apr_pool_t * result_pool)1066 revstart(svn_revnum_t revision,
1067 void *baton,
1068 const svn_delta_editor_t **editor_p,
1069 void **edit_baton_p,
1070 apr_hash_t *rev_props,
1071 apr_pool_t *result_pool)
1072 {
1073 struct parse_baton *pb = baton;
1074 struct commit_baton_t *cb = apr_palloc(result_pool, sizeof(*cb));
1075
1076 cb->rev = revision;
1077 cb->pb = pb;
1078 SVN_ERR(svn_ra__register_editor_shim_callbacks(pb->session,
1079 get_shim_callbacks(cb, result_pool)));
1080 SVN_ERR(svn_ra_get_commit_editor3(pb->session,
1081 editor_p, edit_baton_p,
1082 rev_props,
1083 commit_callback, cb,
1084 NULL, FALSE, result_pool));
1085 return SVN_NO_ERROR;
1086 }
1087
1088 /* Return an implementation of the dumpstream parser API that will drive
1089 * commits over the RA layer to the location described by SESSION.
1090 *
1091 * Use AUX_SESSION (which is opened to the same URL as SESSION)
1092 * for any secondary, out-of-band RA communications required. This is
1093 * needed when loading a non-deltas dump, and for Ev2.
1094 *
1095 * Print feedback to the console for each revision, unless QUIET is true.
1096 *
1097 * Ignore (don't set) any revision property whose name is a key in
1098 * SKIP_REVPROPS. The values in the hash are unimportant.
1099 */
1100 static svn_error_t *
get_dumpstream_loader(svn_repos_parse_fns3_t ** parser_p,void ** parse_baton_p,svn_ra_session_t * session,svn_ra_session_t * aux_session,svn_boolean_t quiet,apr_hash_t * skip_revprops,apr_pool_t * pool)1101 get_dumpstream_loader(svn_repos_parse_fns3_t **parser_p,
1102 void **parse_baton_p,
1103 svn_ra_session_t *session,
1104 svn_ra_session_t *aux_session,
1105 svn_boolean_t quiet,
1106 apr_hash_t *skip_revprops,
1107 apr_pool_t *pool)
1108 {
1109 svn_repos_parse_fns3_t *parser;
1110 struct parse_baton *parse_baton;
1111 const char *session_url, *root_url, *parent_dir;
1112 static const loader_fns_t callbacks = { revstart, fetch_props };
1113
1114 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1115 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1116 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1117 session_url, pool));
1118
1119 parser = apr_pcalloc(pool, sizeof(*parser));
1120 parser->magic_header_record = magic_header_record;
1121 parser->uuid_record = uuid_record;
1122 parser->new_revision_record = new_revision_record;
1123 parser->new_node_record = new_node_record;
1124 parser->set_revision_property = set_revision_property;
1125 parser->set_node_property = set_node_property;
1126 parser->delete_node_property = delete_node_property;
1127 parser->remove_node_props = remove_node_props;
1128 parser->set_fulltext = set_fulltext;
1129 parser->apply_textdelta = apply_textdelta;
1130 parser->close_node = close_node;
1131 parser->close_revision = close_revision;
1132
1133 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1134 parse_baton->session = session;
1135 parse_baton->aux_session = aux_session;
1136 parse_baton->quiet = quiet;
1137 parse_baton->root_url = root_url;
1138 parse_baton->parent_dir = parent_dir;
1139 parse_baton->rev_map = apr_hash_make(pool);
1140 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1141 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1142 parse_baton->skip_revprops = skip_revprops;
1143 parse_baton->callbacks = &callbacks;
1144 parse_baton->cb_baton = parse_baton;
1145
1146 *parser_p = parser;
1147 *parse_baton_p = parse_baton;
1148 return SVN_NO_ERROR;
1149 }
1150
1151 /*----------------------------------------------------------------------*/
1152
1153 /* Dump-stream parser wrapper */
1154
1155 struct filter_parse_baton_t
1156 {
1157 const svn_repos_parse_fns3_t *wrapped_parser;
1158 struct parse_baton *wrapped_pb;
1159 };
1160
1161 struct filter_revision_baton_t
1162 {
1163 struct filter_parse_baton_t *pb;
1164 void *wrapped_rb;
1165 };
1166
1167 struct filter_node_baton_t
1168 {
1169 struct filter_revision_baton_t *rb;
1170 void *wrapped_nb;
1171 };
1172
1173 static svn_error_t *
filter_magic_header_record(int version,void * parse_baton,apr_pool_t * pool)1174 filter_magic_header_record(int version,
1175 void *parse_baton,
1176 apr_pool_t *pool)
1177 {
1178 struct filter_parse_baton_t *pb = parse_baton;
1179
1180 SVN_ERR(pb->wrapped_parser->magic_header_record(version, pb->wrapped_pb,
1181 pool));
1182 return SVN_NO_ERROR;
1183 }
1184
1185 static svn_error_t *
filter_uuid_record(const char * uuid,void * parse_baton,apr_pool_t * pool)1186 filter_uuid_record(const char *uuid,
1187 void *parse_baton,
1188 apr_pool_t *pool)
1189 {
1190 struct filter_parse_baton_t *pb = parse_baton;
1191
1192 SVN_ERR(pb->wrapped_parser->uuid_record(uuid, pb->wrapped_pb, pool));
1193 return SVN_NO_ERROR;
1194 }
1195
1196 static svn_error_t *
filter_new_revision_record(void ** revision_baton,apr_hash_t * headers,void * parse_baton,apr_pool_t * pool)1197 filter_new_revision_record(void **revision_baton,
1198 apr_hash_t *headers,
1199 void *parse_baton,
1200 apr_pool_t *pool)
1201 {
1202 struct filter_parse_baton_t *pb = parse_baton;
1203 struct filter_revision_baton_t *rb = apr_pcalloc(pool, sizeof (*rb));
1204
1205 rb->pb = pb;
1206 SVN_ERR(pb->wrapped_parser->new_revision_record(&rb->wrapped_rb, headers,
1207 pb->wrapped_pb, pool));
1208 *revision_baton = rb;
1209 return SVN_NO_ERROR;
1210 }
1211
1212 static svn_error_t *
filter_new_node_record(void ** node_baton,apr_hash_t * headers,void * revision_baton,apr_pool_t * pool)1213 filter_new_node_record(void **node_baton,
1214 apr_hash_t *headers,
1215 void *revision_baton,
1216 apr_pool_t *pool)
1217 {
1218 struct filter_revision_baton_t *rb = revision_baton;
1219 struct filter_parse_baton_t *pb = rb->pb;
1220 struct filter_node_baton_t *nb = apr_pcalloc(pool, sizeof (*nb));
1221
1222 nb->rb = rb;
1223 SVN_ERR(pb->wrapped_parser->new_node_record(&nb->wrapped_nb, headers,
1224 rb->wrapped_rb, pool));
1225 *node_baton = nb;
1226 return SVN_NO_ERROR;
1227 }
1228
1229 static svn_error_t *
filter_set_revision_property(void * revision_baton,const char * name,const svn_string_t * value)1230 filter_set_revision_property(void *revision_baton,
1231 const char *name,
1232 const svn_string_t *value)
1233 {
1234 struct filter_revision_baton_t *rb = revision_baton;
1235 struct filter_parse_baton_t *pb = rb->pb;
1236
1237 SVN_ERR(pb->wrapped_parser->set_revision_property(rb->wrapped_rb,
1238 name, value));
1239 return SVN_NO_ERROR;
1240 }
1241
1242 static svn_error_t *
filter_set_node_property(void * node_baton,const char * name,const svn_string_t * value)1243 filter_set_node_property(void *node_baton,
1244 const char *name,
1245 const svn_string_t *value)
1246 {
1247 struct filter_node_baton_t *nb = node_baton;
1248 struct filter_revision_baton_t *rb = nb->rb;
1249 struct filter_parse_baton_t *pb = rb->pb;
1250
1251 SVN_ERR(pb->wrapped_parser->set_node_property(nb->wrapped_nb,
1252 name, value));
1253 return SVN_NO_ERROR;
1254 }
1255
1256 static svn_error_t *
filter_delete_node_property(void * node_baton,const char * name)1257 filter_delete_node_property(void *node_baton,
1258 const char *name)
1259 {
1260 struct filter_node_baton_t *nb = node_baton;
1261 struct filter_revision_baton_t *rb = nb->rb;
1262 struct filter_parse_baton_t *pb = rb->pb;
1263
1264 SVN_ERR(pb->wrapped_parser->delete_node_property(nb->wrapped_nb, name));
1265 return SVN_NO_ERROR;
1266 }
1267
1268 static svn_error_t *
filter_remove_node_props(void * node_baton)1269 filter_remove_node_props(void *node_baton)
1270 {
1271 struct filter_node_baton_t *nb = node_baton;
1272 struct filter_revision_baton_t *rb = nb->rb;
1273 struct filter_parse_baton_t *pb = rb->pb;
1274
1275 SVN_ERR(pb->wrapped_parser->remove_node_props(nb->wrapped_nb));
1276 return SVN_NO_ERROR;
1277 }
1278
1279 static svn_error_t *
filter_set_fulltext(svn_stream_t ** stream,void * node_baton)1280 filter_set_fulltext(svn_stream_t **stream,
1281 void *node_baton)
1282 {
1283 struct filter_node_baton_t *nb = node_baton;
1284 struct filter_revision_baton_t *rb = nb->rb;
1285 struct filter_parse_baton_t *pb = rb->pb;
1286
1287 SVN_ERR(pb->wrapped_parser->set_fulltext(stream, nb->wrapped_nb));
1288 return SVN_NO_ERROR;
1289 }
1290
1291 static svn_error_t *
filter_apply_textdelta(svn_txdelta_window_handler_t * handler,void ** handler_baton,void * node_baton)1292 filter_apply_textdelta(svn_txdelta_window_handler_t *handler,
1293 void **handler_baton,
1294 void *node_baton)
1295 {
1296 struct filter_node_baton_t *nb = node_baton;
1297 struct filter_revision_baton_t *rb = nb->rb;
1298 struct filter_parse_baton_t *pb = rb->pb;
1299
1300 SVN_ERR(pb->wrapped_parser->apply_textdelta(handler, handler_baton,
1301 nb->wrapped_nb));
1302 return SVN_NO_ERROR;
1303 }
1304
1305 static svn_error_t *
filter_close_node(void * node_baton)1306 filter_close_node(void *node_baton)
1307 {
1308 struct filter_node_baton_t *nb = node_baton;
1309 struct filter_revision_baton_t *rb = nb->rb;
1310 struct filter_parse_baton_t *pb = rb->pb;
1311
1312 SVN_ERR(pb->wrapped_parser->close_node(nb->wrapped_nb));
1313 return SVN_NO_ERROR;
1314 }
1315
1316 static svn_error_t *
filter_close_revision(void * revision_baton)1317 filter_close_revision(void *revision_baton)
1318 {
1319 struct filter_revision_baton_t *rb = revision_baton;
1320 struct filter_parse_baton_t *pb = rb->pb;
1321
1322 SVN_ERR(pb->wrapped_parser->close_revision(rb->wrapped_rb));
1323 return SVN_NO_ERROR;
1324 }
1325
1326 static svn_error_t *
get_dumpstream_filter(svn_repos_parse_fns3_t ** parser_p,void ** parse_baton_p,const svn_repos_parse_fns3_t * wrapped_parser,void * wrapped_baton,apr_pool_t * pool)1327 get_dumpstream_filter(svn_repos_parse_fns3_t **parser_p,
1328 void **parse_baton_p,
1329 const svn_repos_parse_fns3_t *wrapped_parser,
1330 void *wrapped_baton,
1331 apr_pool_t *pool)
1332 {
1333 svn_repos_parse_fns3_t *parser;
1334 struct filter_parse_baton_t *b;
1335
1336 parser = apr_pcalloc(pool, sizeof(*parser));
1337 parser->magic_header_record = filter_magic_header_record;
1338 parser->uuid_record = filter_uuid_record;
1339 parser->new_revision_record = filter_new_revision_record;
1340 parser->new_node_record = filter_new_node_record;
1341 parser->set_revision_property = filter_set_revision_property;
1342 parser->set_node_property = filter_set_node_property;
1343 parser->delete_node_property = filter_delete_node_property;
1344 parser->remove_node_props = filter_remove_node_props;
1345 parser->set_fulltext = filter_set_fulltext;
1346 parser->apply_textdelta = filter_apply_textdelta;
1347 parser->close_node = filter_close_node;
1348 parser->close_revision = filter_close_revision;
1349
1350 b = apr_pcalloc(pool, sizeof(*b));
1351 b->wrapped_parser = wrapped_parser;
1352 b->wrapped_pb = wrapped_baton;
1353
1354 *parser_p = parser;
1355 *parse_baton_p = b;
1356 return SVN_NO_ERROR;
1357 }
1358
1359 /*----------------------------------------------------------------------*/
1360
1361 /* Implements `svn_ra__lock_retry_func_t'. */
1362 static svn_error_t *
lock_retry_func(void * baton,const svn_string_t * reposlocktoken,apr_pool_t * pool)1363 lock_retry_func(void *baton,
1364 const svn_string_t *reposlocktoken,
1365 apr_pool_t *pool)
1366 {
1367 return svn_cmdline_printf(pool,
1368 _("Failed to get lock on destination "
1369 "repos, currently held by '%s'\n"),
1370 reposlocktoken->data);
1371 }
1372
1373 /* Acquire a lock (of sorts) on the repository associated with the
1374 * given RA SESSION. This lock is just a revprop change attempt in a
1375 * time-delay loop. This function is duplicated by svnsync in
1376 * svnsync/svnsync.c
1377 *
1378 * ### TODO: Make this function more generic and
1379 * expose it through a header for use by other Subversion
1380 * applications to avoid duplication.
1381 */
1382 static svn_error_t *
get_lock(const svn_string_t ** lock_string_p,svn_ra_session_t * session,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1383 get_lock(const svn_string_t **lock_string_p,
1384 svn_ra_session_t *session,
1385 svn_cancel_func_t cancel_func,
1386 void *cancel_baton,
1387 apr_pool_t *pool)
1388 {
1389 svn_boolean_t be_atomic;
1390
1391 SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1392 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1393 pool));
1394 if (! be_atomic)
1395 {
1396 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */
1397 svn_error_t *err =
1398 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1399 _("Target server does not support atomic revision "
1400 "property edits; consider upgrading it to 1.7."));
1401 svn_handle_warning2(stderr, err, "svnrdump: ");
1402 svn_error_clear(err);
1403 }
1404
1405 return svn_ra__get_operational_lock(lock_string_p, NULL, session,
1406 SVNRDUMP_PROP_LOCK, FALSE,
1407 10 /* retries */, lock_retry_func, NULL,
1408 cancel_func, cancel_baton, pool);
1409 }
1410
1411 svn_error_t *
svn_rdump__load_dumpstream(svn_stream_t * stream,svn_ra_session_t * session,svn_ra_session_t * aux_session,svn_boolean_t quiet,apr_hash_t * skip_revprops,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1412 svn_rdump__load_dumpstream(svn_stream_t *stream,
1413 svn_ra_session_t *session,
1414 svn_ra_session_t *aux_session,
1415 svn_boolean_t quiet,
1416 apr_hash_t *skip_revprops,
1417 svn_cancel_func_t cancel_func,
1418 void *cancel_baton,
1419 apr_pool_t *pool)
1420 {
1421 svn_repos_parse_fns3_t *parser;
1422 void *parse_baton;
1423 const svn_string_t *lock_string;
1424 svn_error_t *err;
1425
1426 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1427
1428 SVN_ERR(get_dumpstream_loader(&parser, &parse_baton,
1429 session, aux_session,
1430 quiet, skip_revprops,
1431 pool));
1432
1433 /* Interpose a filtering layer: currently doing nothing */
1434 SVN_ERR(get_dumpstream_filter(&parser, &parse_baton,
1435 parser, parse_baton,
1436 pool));
1437
1438 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1439 cancel_func, cancel_baton, pool);
1440
1441 /* If all goes well, or if we're cancelled cleanly, don't leave a
1442 stray lock behind. */
1443 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1444 err = svn_error_compose_create(
1445 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,
1446 lock_string, pool),
1447 err);
1448 return err;
1449 }
1450