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