load_editor.c revision 289166
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
53
54/**
55 * General baton used by the parser functions.
56 */
57struct 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 */
102struct 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 */
123struct 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 */
150struct 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
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. */
168static void
169set_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. */
183static svn_revnum_t
184get_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() */
197static svn_error_t *
198prefix_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() */
232static svn_error_t *
233renumber_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
347static svn_error_t *
348commit_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'. */
386static svn_error_t *
387lock_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
398static svn_error_t *
399fetch_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
435static svn_error_t *
436fetch_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
477static svn_error_t *
478fetch_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
495static svn_delta_shim_callbacks_t *
496get_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 */
519static svn_error_t *
520get_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
548static svn_error_t *
549new_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
596static svn_error_t *
597magic_header_record(int version,
598            void *parse_baton,
599            apr_pool_t *pool)
600{
601  return SVN_NO_ERROR;
602}
603
604static svn_error_t *
605uuid_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 */
623static void
624push_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
656static svn_error_t *
657new_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
886static svn_error_t *
887set_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
922static svn_error_t *
923set_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
987static svn_error_t *
988delete_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 */
1013static svn_error_t *
1014remove_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
1084static svn_error_t *
1085set_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
1102static svn_error_t *
1103apply_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
1118static svn_error_t *
1119close_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
1160static svn_error_t *
1161close_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
1242svn_error_t *
1243svn_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}
1303