load_editor.c revision 289166
1116742Ssam/*
2116904Ssam *  load_editor.c: The svn_delta_editor_t editor used by svnrdump to
3178354Ssam *  load revisions.
4116742Ssam *
5116742Ssam * ====================================================================
6116742Ssam *    Licensed to the Apache Software Foundation (ASF) under one
7116742Ssam *    or more contributor license agreements.  See the NOTICE file
8116742Ssam *    distributed with this work for additional information
9116742Ssam *    regarding copyright ownership.  The ASF licenses this file
10116742Ssam *    to you under the Apache License, Version 2.0 (the
11116742Ssam *    "License"); you may not use this file except in compliance
12116742Ssam *    with the License.  You may obtain a copy of the License at
13116742Ssam *
14116742Ssam *      http://www.apache.org/licenses/LICENSE-2.0
15116904Ssam *
16116904Ssam *    Unless required by applicable law or agreed to in writing,
17116904Ssam *    software distributed under the License is distributed on an
18116904Ssam *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19116904Ssam *    KIND, either express or implied.  See the License for the
20116904Ssam *    specific language governing permissions and limitations
21116904Ssam *    under the License.
22116904Ssam * ====================================================================
23116904Ssam */
24116904Ssam
25116904Ssam#include "svn_cmdline.h"
26116742Ssam#include "svn_pools.h"
27116742Ssam#include "svn_delta.h"
28116742Ssam#include "svn_repos.h"
29116742Ssam#include "svn_props.h"
30116742Ssam#include "svn_path.h"
31138568Ssam#include "svn_ra.h"
32170530Ssam#include "svn_subst.h"
33116742Ssam#include "svn_io.h"
34138568Ssam#include "svn_private_config.h"
35178354Ssam#include "private/svn_repos_private.h"
36178354Ssam#include "private/svn_ra_private.h"
37178354Ssam#include "private/svn_mergeinfo_private.h"
38178354Ssam#include "private/svn_fspath.h"
39178354Ssam
40178354Ssam#include "svnrdump.h"
41178354Ssam
42178354Ssam#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
43178354Ssam
44178354Ssam#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
45138568Ssam
46138568Ssam#if 0
47138568Ssam#define LDR_DBG(x) SVN_DBG(x)
48138568Ssam#else
49138568Ssam#define LDR_DBG(x) while(0)
50138568Ssam#endif
51138568Ssam
52138568Ssam
53138568Ssam
54170530Ssam/**
55138568Ssam * General baton used by the parser functions.
56172211Ssam */
57172211Ssamstruct parse_baton
58172211Ssam{
59116742Ssam  /* Commit editor and baton used to transfer loaded revisions to
60116742Ssam     the target repository. */
61116742Ssam  const svn_delta_editor_t *commit_editor;
62170530Ssam  void *commit_edit_baton;
63138568Ssam
64116742Ssam  /* RA session(s) for committing to the target repository. */
65138568Ssam  svn_ra_session_t *session;
66138568Ssam  svn_ra_session_t *aux_session;
67178354Ssam
68138568Ssam  /* To bleep, or not to bleep?  (What kind of question is that?) */
69116742Ssam  svn_boolean_t quiet;
70178354Ssam
71178354Ssam  /* UUID found in the dumpstream, if any; NULL otherwise. */
72178354Ssam  const char *uuid;
73178354Ssam
74178354Ssam  /* Root URL of the target repository. */
75178354Ssam  const char *root_url;
76178354Ssam
77178354Ssam  /* The "parent directory" of the target repository in which to load.
78178354Ssam     (This is essentially the difference between ROOT_URL and
79178354Ssam     SESSION's url, and roughly equivalent to the 'svnadmin load
80178354Ssam     --parent-dir' option.) */
81178354Ssam  const char *parent_dir;
82178354Ssam
83178354Ssam  /* A mapping of svn_revnum_t * dump stream revisions to their
84178354Ssam     corresponding svn_revnum_t * target repository revisions. */
85178354Ssam  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
86178354Ssam     ### for discussion about improving the memory costs of this mapping. */
87178354Ssam  apr_hash_t *rev_map;
88178354Ssam
89116742Ssam  /* The most recent (youngest) revision from the dump stream mapped in
90116742Ssam     REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
91116742Ssam  svn_revnum_t last_rev_mapped;
92116742Ssam
93116742Ssam  /* The oldest revision loaded from the dump stream, or
94116742Ssam     SVN_INVALID_REVNUM if none have been loaded. */
95178354Ssam  svn_revnum_t oldest_dumpstream_rev;
96178354Ssam};
97178354Ssam
98178354Ssam/**
99178354Ssam * Use to wrap the dir_context_t in commit.c so we can keep track of
100178354Ssam * depth, relpath and parent for open_directory and close_directory.
101120483Ssam */
102183252Ssamstruct directory_baton
103183252Ssam{
104183252Ssam  void *baton;
105183252Ssam  const char *relpath;
106183252Ssam  int depth;
107183252Ssam
108183252Ssam  /* The copy-from source of this directory, no matter whether it is
109183252Ssam     copied explicitly (the root node of a copy) or implicitly (being an
110183252Ssam     existing child of a copied directory). For a node that is newly
111183252Ssam     added (without history), even inside a copied parent, these are
112183252Ssam     NULL and SVN_INVALID_REVNUM. */
113183252Ssam  const char *copyfrom_path;
114183252Ssam  svn_revnum_t copyfrom_rev;
115183255Ssam
116183255Ssam  struct directory_baton *parent;
117183256Ssam};
118183257Ssam
119183257Ssam/**
120183252Ssam * Baton used to represent a node; to be used by the parser
121183252Ssam * functions. Contains a link to the revision baton.
122183252Ssam */
123170530Ssamstruct node_baton
124170530Ssam{
125170530Ssam  const char *path;
126170530Ssam  svn_node_kind_t kind;
127170530Ssam  enum svn_node_action action;
128170530Ssam
129170530Ssam  /* Is this directory explicitly added? If not, then it already existed
130170530Ssam     or is a child of a copy. */
131183252Ssam  svn_boolean_t is_added;
132170530Ssam
133183251Ssam  svn_revnum_t copyfrom_rev;
134173273Ssam  const char *copyfrom_path;
135170530Ssam  const char *copyfrom_url;
136178354Ssam
137172225Ssam  void *file_baton;
138172225Ssam  const char *base_checksum;
139172225Ssam
140172225Ssam  /* (const char *name) -> (svn_prop_t *) */
141170530Ssam  apr_hash_t *prop_changes;
142138568Ssam
143138568Ssam  struct revision_baton *rb;
144116742Ssam};
145116742Ssam
146170530Ssam/**
147178354Ssam * Baton used to represet a revision; used by the parser
148170530Ssam * functions. Contains a link to the parser baton.
149116742Ssam */
150116742Ssamstruct revision_baton
151170530Ssam{
152170530Ssam  svn_revnum_t rev;
153116742Ssam  apr_hash_t *revprop_table;
154116742Ssam  apr_int32_t rev_offset;
155138568Ssam
156170530Ssam  const svn_string_t *datestamp;
157178354Ssam  const svn_string_t *author;
158138568Ssam
159170530Ssam  struct parse_baton *pb;
160170530Ssam  struct directory_baton *db;
161170530Ssam  apr_pool_t *pool;
162170530Ssam};
163116742Ssam
164170530Ssam
165170530Ssam
166170530Ssam/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
167178354Ssam   anything added to the hash is allocated in the hash's pool. */
168170530Ssamstatic void
169170530Ssamset_revision_mapping(apr_hash_t *rev_map,
170170530Ssam                     svn_revnum_t from_rev,
171116742Ssam                     svn_revnum_t to_rev)
172170530Ssam{
173170530Ssam  svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
174170530Ssam                                         sizeof(svn_revnum_t) * 2);
175170530Ssam  mapped_revs[0] = from_rev;
176170530Ssam  mapped_revs[1] = to_rev;
177170530Ssam  apr_hash_set(rev_map, mapped_revs,
178170530Ssam               sizeof(svn_revnum_t), mapped_revs + 1);
179170530Ssam}
180170530Ssam
181170530Ssam/* Return the revision to which FROM_REV maps in REV_MAP, or
182170530Ssam   SVN_INVALID_REVNUM if no such mapping exists. */
183170530Ssamstatic svn_revnum_t
184116742Ssamget_revision_mapping(apr_hash_t *rev_map,
185138568Ssam                     svn_revnum_t from_rev)
186138568Ssam{
187178354Ssam  svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
188178354Ssam                                      sizeof(from_rev));
189138568Ssam  return to_rev ? *to_rev : SVN_INVALID_REVNUM;
190178354Ssam}
191178354Ssam
192178354Ssam
193178354Ssam/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with
194116742Ssam   PARENT_DIR, and return it in *MERGEINFO_VAL. */
195138568Ssam/* ### FIXME:  Consider somehow sharing code with
196178354Ssam   ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */
197116742Ssamstatic svn_error_t *
198170530Ssamprefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
199173273Ssam                       const svn_string_t *mergeinfo_orig,
200173273Ssam                       const char *parent_dir,
201182828Ssam                       apr_pool_t *pool)
202182828Ssam{
203183255Ssam  apr_hash_t *prefixed_mergeinfo, *mergeinfo;
204183257Ssam  apr_hash_index_t *hi;
205183257Ssam  void *rangelist;
206170530Ssam
207138568Ssam  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
208138568Ssam  prefixed_mergeinfo = apr_hash_make(pool);
209138568Ssam  for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
210138568Ssam    {
211138568Ssam      const void *key;
212138568Ssam      const char *path, *merge_source;
213178354Ssam
214178354Ssam      apr_hash_this(hi, &key, NULL, &rangelist);
215178354Ssam      merge_source = svn_relpath_canonicalize(key, pool);
216178354Ssam
217178354Ssam      /* The svn:mergeinfo property syntax demands a repos abspath */
218178354Ssam      path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
219178354Ssam                                                       merge_source, pool),
220178354Ssam                                      pool);
221178354Ssam      svn_hash_sets(prefixed_mergeinfo, path, rangelist);
222178354Ssam    }
223178354Ssam  return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
224178354Ssam}
225178354Ssam
226178354Ssam
227178354Ssam/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
228178354Ssam   as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
229178354Ssam   (allocated from POOL). */
230178354Ssam/* ### FIXME:  Consider somehow sharing code with
231178354Ssam   ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */
232178354Ssamstatic svn_error_t *
233178354Ssamrenumber_mergeinfo_revs(svn_string_t **final_val,
234178354Ssam                        const svn_string_t *initial_val,
235178354Ssam                        struct revision_baton *rb,
236178354Ssam                        apr_pool_t *pool)
237178354Ssam{
238178354Ssam  apr_pool_t *subpool = svn_pool_create(pool);
239178354Ssam  svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
240178354Ssam  svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
241178354Ssam  apr_hash_index_t *hi;
242178354Ssam
243178354Ssam  SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
244178354Ssam
245116742Ssam  /* Issue #3020
246116742Ssam     http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
247116742Ssam     Remove mergeinfo older than the oldest revision in the dump stream
248138568Ssam     and adjust its revisions by the difference between the head rev of
249116742Ssam     the target repository and the current dump stream rev. */
250116742Ssam  if (rb->pb->oldest_dumpstream_rev > 1)
251116742Ssam    {
252116742Ssam      SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
253116742Ssam                  &predates_stream_mergeinfo, mergeinfo,
254116742Ssam                  rb->pb->oldest_dumpstream_rev - 1, 0,
255138568Ssam                  TRUE, subpool, subpool));
256116742Ssam      SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
257116742Ssam                  &mergeinfo, mergeinfo,
258116742Ssam                  rb->pb->oldest_dumpstream_rev - 1, 0,
259116742Ssam                  FALSE, subpool, subpool));
260116742Ssam      SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
261144618Ssam                  &predates_stream_mergeinfo,
262144618Ssam                  predates_stream_mergeinfo,
263144618Ssam                  -rb->rev_offset, subpool, subpool));
264178354Ssam    }
265178354Ssam  else
266178354Ssam    {
267127877Ssam      predates_stream_mergeinfo = NULL;
268138568Ssam    }
269138568Ssam
270138568Ssam  for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
271138568Ssam    {
272138568Ssam      svn_rangelist_t *rangelist;
273116742Ssam      struct parse_baton *pb = rb->pb;
274148302Ssam      int i;
275148302Ssam      const void *path;
276138568Ssam      apr_ssize_t pathlen;
277178354Ssam      void *val;
278178354Ssam
279178354Ssam      apr_hash_this(hi, &path, &pathlen, &val);
280178354Ssam      rangelist = val;
281178354Ssam
282178354Ssam      /* Possibly renumber revisions in merge source's rangelist. */
283148306Ssam      for (i = 0; i < rangelist->nelts; i++)
284170530Ssam        {
285184274Ssam          svn_revnum_t rev_from_map;
286170530Ssam          svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
287178354Ssam                                                   svn_merge_range_t *);
288178354Ssam          rev_from_map = get_revision_mapping(pb->rev_map, range->start);
289138568Ssam          if (SVN_IS_VALID_REVNUM(rev_from_map))
290178354Ssam            {
291178354Ssam              range->start = rev_from_map;
292178354Ssam            }
293178354Ssam          else if (range->start == pb->oldest_dumpstream_rev - 1)
294178354Ssam            {
295178354Ssam              /* Since the start revision of svn_merge_range_t are not
296178354Ssam                 inclusive there is one possible valid start revision that
297138568Ssam                 won't be found in the PB->REV_MAP mapping of load stream
298138568Ssam                 revsions to loaded revisions: The revision immediately
299178354Ssam                 preceeding the oldest revision from the load stream.
300178354Ssam                 This is a valid revision for mergeinfo, but not a valid
301178354Ssam                 copy from revision (which PB->REV_MAP also maps for) so it
302178354Ssam                 will never be in the mapping.
303138568Ssam
304138568Ssam                 If that is what we have here, then find the mapping for the
305138568Ssam                 oldest rev from the load stream and subtract 1 to get the
306138568Ssam                 renumbered, non-inclusive, start revision. */
307138568Ssam              rev_from_map = get_revision_mapping(pb->rev_map,
308138568Ssam                                                  pb->oldest_dumpstream_rev);
309170530Ssam              if (SVN_IS_VALID_REVNUM(rev_from_map))
310170530Ssam                range->start = rev_from_map - 1;
311178354Ssam            }
312138568Ssam          else
313178354Ssam            {
314138568Ssam              /* If we can't remap the start revision then don't even bother
315138568Ssam                 trying to remap the end revision.  It's possible we might
316138568Ssam                 actually succeed at the latter, which can result in invalid
317178354Ssam                 mergeinfo with a start rev > end rev.  If that gets into the
318178354Ssam                 repository then a world of bustage breaks loose anytime that
319178354Ssam                 bogus mergeinfo is parsed.  See
320178354Ssam                 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
321178354Ssam                 */
322178354Ssam              continue;
323178354Ssam            }
324178354Ssam
325178354Ssam          rev_from_map = get_revision_mapping(pb->rev_map, range->end);
326178354Ssam          if (SVN_IS_VALID_REVNUM(rev_from_map))
327138568Ssam            range->end = rev_from_map;
328144618Ssam        }
329138568Ssam      apr_hash_set(final_mergeinfo, path, pathlen, rangelist);
330178354Ssam    }
331178354Ssam
332178354Ssam  if (predates_stream_mergeinfo)
333178354Ssam    {
334170530Ssam      SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
335178354Ssam                                   subpool, subpool));
336138568Ssam    }
337178354Ssam
338178354Ssam  SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
339178354Ssam
340178354Ssam  SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
341178354Ssam  svn_pool_destroy(subpool);
342178354Ssam
343178354Ssam  return SVN_NO_ERROR;
344178354Ssam}
345178354Ssam
346178354Ssam
347170530Ssamstatic svn_error_t *
348170530Ssamcommit_callback(const svn_commit_info_t *commit_info,
349138568Ssam                void *baton,
350148863Ssam                apr_pool_t *pool)
351148863Ssam{
352170530Ssam  struct revision_baton *rb = baton;
353148863Ssam  struct parse_baton *pb = rb->pb;
354178354Ssam
355170530Ssam  /* ### Don't print directly; generate a notification. */
356170530Ssam  if (! pb->quiet)
357138568Ssam    SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
358138568Ssam                               commit_info->revision));
359178354Ssam
360178354Ssam  /* Add the mapping of the dumpstream revision to the committed revision. */
361138568Ssam  set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
362138568Ssam
363178354Ssam  /* If the incoming dump stream has non-contiguous revisions (e.g. from
364178354Ssam     using svndumpfilter --drop-empty-revs without --renumber-revs) then
365178354Ssam     we must account for the missing gaps in PB->REV_MAP.  Otherwise we
366178354Ssam     might not be able to map all mergeinfo source revisions to the correct
367178354Ssam     revisions in the target repos. */
368178354Ssam  if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
369178354Ssam      && (rb->rev != pb->last_rev_mapped + 1))
370178354Ssam    {
371178354Ssam      svn_revnum_t i;
372178354Ssam
373138568Ssam      for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
374144618Ssam        {
375178354Ssam          set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
376178354Ssam        }
377170530Ssam    }
378178354Ssam
379178354Ssam  /* Update our "last revision mapped". */
380178354Ssam  pb->last_rev_mapped = rb->rev;
381178354Ssam
382178354Ssam  return SVN_NO_ERROR;
383178354Ssam}
384178354Ssam
385170530Ssam/* Implements `svn_ra__lock_retry_func_t'. */
386170530Ssamstatic svn_error_t *
387148863Ssamlock_retry_func(void *baton,
388170530Ssam                const svn_string_t *reposlocktoken,
389178354Ssam                apr_pool_t *pool)
390178354Ssam{
391138568Ssam  return svn_cmdline_printf(pool,
392148863Ssam                            _("Failed to get lock on destination "
393170530Ssam                              "repos, currently held by '%s'\n"),
394138568Ssam                            reposlocktoken->data);
395116742Ssam}
396144618Ssam
397116742Ssam
398116742Ssamstatic svn_error_t *
399178354Ssamfetch_base_func(const char **filename,
400144618Ssam                void *baton,
401138568Ssam                const char *path,
402144618Ssam                svn_revnum_t base_revision,
403138568Ssam                apr_pool_t *result_pool,
404178354Ssam                apr_pool_t *scratch_pool)
405178354Ssam{
406170530Ssam  struct revision_baton *rb = baton;
407153073Ssam  svn_stream_t *fstream;
408153073Ssam  svn_error_t *err;
409153073Ssam
410178354Ssam  if (! SVN_IS_VALID_REVNUM(base_revision))
411148936Ssam    base_revision = rb->rev - 1;
412148936Ssam
413178354Ssam  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
414178354Ssam                                 svn_io_file_del_on_pool_cleanup,
415178354Ssam                                 result_pool, scratch_pool));
416178354Ssam
417116742Ssam  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