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