]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_repos / load-fs-vtable.c
1 /* load-fs-vtable.c --- dumpstream loader vtable for committing into a
2  *                      Subversion filesystem.
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 #include "svn_private_config.h"
26 #include "svn_hash.h"
27 #include "svn_pools.h"
28 #include "svn_error.h"
29 #include "svn_fs.h"
30 #include "svn_repos.h"
31 #include "svn_string.h"
32 #include "svn_props.h"
33 #include "repos.h"
34 #include "svn_private_config.h"
35 #include "svn_mergeinfo.h"
36 #include "svn_checksum.h"
37 #include "svn_subst.h"
38 #include "svn_ctype.h"
39 #include "svn_dirent_uri.h"
40
41 #include <apr_lib.h>
42
43 #include "private/svn_fspath.h"
44 #include "private/svn_dep_compat.h"
45 #include "private/svn_mergeinfo_private.h"
46
47 /*----------------------------------------------------------------------*/
48 \f
49 /** Batons used herein **/
50
51 struct parse_baton
52 {
53   svn_repos_t *repos;
54   svn_fs_t *fs;
55
56   svn_boolean_t use_history;
57   svn_boolean_t validate_props;
58   svn_boolean_t use_pre_commit_hook;
59   svn_boolean_t use_post_commit_hook;
60   enum svn_repos_load_uuid uuid_action;
61   const char *parent_dir; /* repository relpath, or NULL */
62   svn_repos_notify_func_t notify_func;
63   void *notify_baton;
64   svn_repos_notify_t *notify;
65   apr_pool_t *pool;
66
67   /* Start and end (inclusive) of revision range we'll pay attention
68      to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
69      revisions. */
70   svn_revnum_t start_rev;
71   svn_revnum_t end_rev;
72
73   /* A hash mapping copy-from revisions and mergeinfo range revisions
74      (svn_revnum_t *) in the dump stream to their corresponding revisions
75      (svn_revnum_t *) in the loaded repository.  The hash and its
76      contents are allocated in POOL. */
77   /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
78      ### for discussion about improving the memory costs of this mapping. */
79   apr_hash_t *rev_map;
80
81   /* The most recent (youngest) revision from the dump stream mapped in
82      REV_MAP.  If no revisions have been mapped yet, this is set to
83      SVN_INVALID_REVNUM. */
84   svn_revnum_t last_rev_mapped;
85
86   /* The oldest old revision loaded from the dump stream.  If no revisions
87      have been loaded yet, this is set to SVN_INVALID_REVNUM. */
88   svn_revnum_t oldest_old_rev;
89 };
90
91 struct revision_baton
92 {
93   svn_revnum_t rev;
94   svn_fs_txn_t *txn;
95   svn_fs_root_t *txn_root;
96
97   const svn_string_t *datestamp;
98
99   apr_int32_t rev_offset;
100   svn_boolean_t skipped;
101
102   struct parse_baton *pb;
103   apr_pool_t *pool;
104 };
105
106 struct node_baton
107 {
108   const char *path;
109   svn_node_kind_t kind;
110   enum svn_node_action action;
111   svn_checksum_t *base_checksum;        /* null, if not available */
112   svn_checksum_t *result_checksum;      /* null, if not available */
113   svn_checksum_t *copy_source_checksum; /* null, if not available */
114
115   svn_revnum_t copyfrom_rev;
116   const char *copyfrom_path;
117
118   struct revision_baton *rb;
119   apr_pool_t *pool;
120 };
121
122
123 /*----------------------------------------------------------------------*/
124
125 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
126    anything added to the hash is allocated in the hash's pool. */
127 static void
128 set_revision_mapping(apr_hash_t *rev_map,
129                      svn_revnum_t from_rev,
130                      svn_revnum_t to_rev)
131 {
132   svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
133                                          sizeof(svn_revnum_t) * 2);
134   mapped_revs[0] = from_rev;
135   mapped_revs[1] = to_rev;
136   apr_hash_set(rev_map, mapped_revs,
137                sizeof(svn_revnum_t), mapped_revs + 1);
138 }
139
140 /* Return the revision to which FROM_REV maps in REV_MAP, or
141    SVN_INVALID_REVNUM if no such mapping exists. */
142 static svn_revnum_t
143 get_revision_mapping(apr_hash_t *rev_map,
144                      svn_revnum_t from_rev)
145 {
146   svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
147                                       sizeof(from_rev));
148   return to_rev ? *to_rev : SVN_INVALID_REVNUM;
149 }
150
151
152 /* Change revision property NAME to VALUE for REVISION in REPOS.  If
153    VALIDATE_PROPS is set, use functions which perform validation of
154    the property value.  Otherwise, bypass those checks. */
155 static svn_error_t *
156 change_rev_prop(svn_repos_t *repos,
157                 svn_revnum_t revision,
158                 const char *name,
159                 const svn_string_t *value,
160                 svn_boolean_t validate_props,
161                 apr_pool_t *pool)
162 {
163   if (validate_props)
164     return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
165                                          NULL, value, FALSE, FALSE,
166                                          NULL, NULL, pool);
167   else
168     return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
169                                    NULL, value, pool);
170 }
171
172 /* Change property NAME to VALUE for PATH in TXN_ROOT.  If
173    VALIDATE_PROPS is set, use functions which perform validation of
174    the property value.  Otherwise, bypass those checks. */
175 static svn_error_t *
176 change_node_prop(svn_fs_root_t *txn_root,
177                  const char *path,
178                  const char *name,
179                  const svn_string_t *value,
180                  svn_boolean_t validate_props,
181                  apr_pool_t *pool)
182 {
183   if (validate_props)
184     return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
185   else
186     return svn_fs_change_node_prop(txn_root, path, name, value, pool);
187 }
188
189 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
190    return it in *MERGEINFO_VAL. */
191 /* ### FIXME:  Consider somehow sharing code with
192    ### svnrdump/load_editor.c:prefix_mergeinfo_paths() */
193 static svn_error_t *
194 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
195                        const svn_string_t *mergeinfo_orig,
196                        const char *parent_dir,
197                        apr_pool_t *pool)
198 {
199   apr_hash_t *prefixed_mergeinfo, *mergeinfo;
200   apr_hash_index_t *hi;
201   void *rangelist;
202
203   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
204   prefixed_mergeinfo = apr_hash_make(pool);
205   for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
206     {
207       const void *key;
208       const char *path, *merge_source;
209
210       apr_hash_this(hi, &key, NULL, &rangelist);
211       merge_source = svn_relpath_canonicalize(key, pool);
212
213       /* The svn:mergeinfo property syntax demands a repos abspath */
214       path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
215                                                        merge_source, pool),
216                                       pool);
217       svn_hash_sets(prefixed_mergeinfo, path, rangelist);
218     }
219   return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
220 }
221
222
223 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
224    as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
225    (allocated from POOL). */
226 /* ### FIXME:  Consider somehow sharing code with
227    ### svnrdump/load_editor.c:renumber_mergeinfo_revs() */
228 static svn_error_t *
229 renumber_mergeinfo_revs(svn_string_t **final_val,
230                         const svn_string_t *initial_val,
231                         struct revision_baton *rb,
232                         apr_pool_t *pool)
233 {
234   apr_pool_t *subpool = svn_pool_create(pool);
235   svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
236   svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
237   apr_hash_index_t *hi;
238
239   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
240
241   /* Issue #3020
242      http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16
243      Remove mergeinfo older than the oldest revision in the dump stream
244      and adjust its revisions by the difference between the head rev of
245      the target repository and the current dump stream rev. */
246   if (rb->pb->oldest_old_rev > 1)
247     {
248       SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
249         &predates_stream_mergeinfo, mergeinfo,
250         rb->pb->oldest_old_rev - 1, 0,
251         TRUE, subpool, subpool));
252       SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
253         &mergeinfo, mergeinfo,
254         rb->pb->oldest_old_rev - 1, 0,
255         FALSE, subpool, subpool));
256       SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
257         &predates_stream_mergeinfo, predates_stream_mergeinfo,
258         -rb->rev_offset, subpool, subpool));
259     }
260   else
261     {
262       predates_stream_mergeinfo = NULL;
263     }
264
265   for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
266     {
267       const char *merge_source;
268       svn_rangelist_t *rangelist;
269       struct parse_baton *pb = rb->pb;
270       int i;
271       const void *key;
272       void *val;
273
274       apr_hash_this(hi, &key, NULL, &val);
275       merge_source = key;
276       rangelist = val;
277
278       /* Possibly renumber revisions in merge source's rangelist. */
279       for (i = 0; i < rangelist->nelts; i++)
280         {
281           svn_revnum_t rev_from_map;
282           svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
283                                                    svn_merge_range_t *);
284           rev_from_map = get_revision_mapping(pb->rev_map, range->start);
285           if (SVN_IS_VALID_REVNUM(rev_from_map))
286             {
287               range->start = rev_from_map;
288             }
289           else if (range->start == pb->oldest_old_rev - 1)
290             {
291               /* Since the start revision of svn_merge_range_t are not
292                  inclusive there is one possible valid start revision that
293                  won't be found in the PB->REV_MAP mapping of load stream
294                  revsions to loaded revisions: The revision immediately
295                  preceeding the oldest revision from the load stream.
296                  This is a valid revision for mergeinfo, but not a valid
297                  copy from revision (which PB->REV_MAP also maps for) so it
298                  will never be in the mapping.
299
300                  If that is what we have here, then find the mapping for the
301                  oldest rev from the load stream and subtract 1 to get the
302                  renumbered, non-inclusive, start revision. */
303               rev_from_map = get_revision_mapping(pb->rev_map,
304                                                   pb->oldest_old_rev);
305               if (SVN_IS_VALID_REVNUM(rev_from_map))
306                 range->start = rev_from_map - 1;
307             }
308           else
309             {
310               /* If we can't remap the start revision then don't even bother
311                  trying to remap the end revision.  It's possible we might
312                  actually succeed at the latter, which can result in invalid
313                  mergeinfo with a start rev > end rev.  If that gets into the
314                  repository then a world of bustage breaks loose anytime that
315                  bogus mergeinfo is parsed.  See
316                  http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16.
317                  */
318               continue;
319             }
320
321           rev_from_map = get_revision_mapping(pb->rev_map, range->end);
322           if (SVN_IS_VALID_REVNUM(rev_from_map))
323             range->end = rev_from_map;
324         }
325       svn_hash_sets(final_mergeinfo, merge_source, rangelist);
326     }
327
328   if (predates_stream_mergeinfo)
329       SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
330                                    subpool, subpool));
331
332   SVN_ERR(svn_mergeinfo_sort(final_mergeinfo, subpool));
333
334   /* Mergeinfo revision sources for r0 and r1 are invalid; you can't merge r0
335      or r1.  However, svndumpfilter can be abused to produce r1 merge source
336      revs.  So if we encounter any, then strip them out, no need to put them
337      into the load target. */
338   SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&final_mergeinfo,
339                                                     final_mergeinfo,
340                                                     1, 0, FALSE,
341                                                     subpool, subpool));
342
343   SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
344   svn_pool_destroy(subpool);
345
346   return SVN_NO_ERROR;
347 }
348
349 /*----------------------------------------------------------------------*/
350 \f
351 /** vtable for doing commits to a fs **/
352
353
354 static svn_error_t *
355 make_node_baton(struct node_baton **node_baton_p,
356                 apr_hash_t *headers,
357                 struct revision_baton *rb,
358                 apr_pool_t *pool)
359 {
360   struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
361   const char *val;
362
363   /* Start with sensible defaults. */
364   nb->rb = rb;
365   nb->pool = pool;
366   nb->kind = svn_node_unknown;
367
368   /* Then add info from the headers.  */
369   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
370   {
371     val = svn_relpath_canonicalize(val, pool);
372     if (rb->pb->parent_dir)
373       nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
374     else
375       nb->path = val;
376   }
377
378   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
379     {
380       if (! strcmp(val, "file"))
381         nb->kind = svn_node_file;
382       else if (! strcmp(val, "dir"))
383         nb->kind = svn_node_dir;
384     }
385
386   nb->action = (enum svn_node_action)(-1);  /* an invalid action code */
387   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
388     {
389       if (! strcmp(val, "change"))
390         nb->action = svn_node_action_change;
391       else if (! strcmp(val, "add"))
392         nb->action = svn_node_action_add;
393       else if (! strcmp(val, "delete"))
394         nb->action = svn_node_action_delete;
395       else if (! strcmp(val, "replace"))
396         nb->action = svn_node_action_replace;
397     }
398
399   nb->copyfrom_rev = SVN_INVALID_REVNUM;
400   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
401     {
402       nb->copyfrom_rev = SVN_STR_TO_REV(val);
403     }
404   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
405     {
406       val = svn_relpath_canonicalize(val, pool);
407       if (rb->pb->parent_dir)
408         nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
409       else
410         nb->copyfrom_path = val;
411     }
412
413   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
414     {
415       SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
416                                      val, pool));
417     }
418
419   if ((val = svn_hash_gets(headers,
420                            SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
421     {
422       SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
423                                      pool));
424     }
425
426   if ((val = svn_hash_gets(headers,
427                            SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
428     {
429       SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
430                                      svn_checksum_md5, val, pool));
431     }
432
433   /* What's cool about this dump format is that the parser just
434      ignores any unrecognized headers.  :-)  */
435
436   *node_baton_p = nb;
437   return SVN_NO_ERROR;
438 }
439
440 static struct revision_baton *
441 make_revision_baton(apr_hash_t *headers,
442                     struct parse_baton *pb,
443                     apr_pool_t *pool)
444 {
445   struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
446   const char *val;
447
448   rb->pb = pb;
449   rb->pool = pool;
450   rb->rev = SVN_INVALID_REVNUM;
451
452   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
453     {
454       rb->rev = SVN_STR_TO_REV(val);
455
456       /* If we're filtering revisions, is this one we'll skip? */
457       rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
458                      && ((rb->rev < pb->start_rev) ||
459                          (rb->rev > pb->end_rev)));
460     }
461
462   return rb;
463 }
464
465
466 static svn_error_t *
467 new_revision_record(void **revision_baton,
468                     apr_hash_t *headers,
469                     void *parse_baton,
470                     apr_pool_t *pool)
471 {
472   struct parse_baton *pb = parse_baton;
473   struct revision_baton *rb;
474   svn_revnum_t head_rev;
475
476   rb = make_revision_baton(headers, pb, pool);
477
478   /* ### If we're filtering revisions, and this is one we've skipped,
479      ### and we've skipped it because it has a revision number younger
480      ### than the youngest in our acceptable range, then should we
481      ### just bail out here? */
482   /*
483   if (rb->skipped && (rb->rev > pb->end_rev))
484     return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
485                              _("Finished processing acceptable load "
486                                "revision range"));
487   */
488
489   SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
490
491   /* FIXME: This is a lame fallback loading multiple segments of dump in
492      several separate operations. It is highly susceptible to race conditions.
493      Calculate the revision 'offset' for finding copyfrom sources.
494      It might be positive or negative. */
495   rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
496
497   if ((rb->rev > 0) && (! rb->skipped))
498     {
499       /* Create a new fs txn. */
500       SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev, 0, pool));
501       SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
502
503       if (pb->notify_func)
504         {
505           pb->notify->action = svn_repos_notify_load_txn_start;
506           pb->notify->old_revision = rb->rev;
507           pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
508         }
509
510       /* Stash the oldest "old" revision committed from the load stream. */
511       if (!SVN_IS_VALID_REVNUM(pb->oldest_old_rev))
512         pb->oldest_old_rev = rb->rev;
513     }
514
515   /* If we're skipping this revision, try to notify someone. */
516   if (rb->skipped && pb->notify_func)
517     {
518       pb->notify->action = svn_repos_notify_load_skipped_rev;
519       pb->notify->old_revision = rb->rev;
520       pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
521     }
522
523   /* If we're parsing revision 0, only the revision are (possibly)
524      interesting to us: when loading the stream into an empty
525      filesystem, then we want new filesystem's revision 0 to have the
526      same props.  Otherwise, we just ignore revision 0 in the stream. */
527
528   *revision_baton = rb;
529   return SVN_NO_ERROR;
530 }
531
532
533
534 /* Factorized helper func for new_node_record() */
535 static svn_error_t *
536 maybe_add_with_history(struct node_baton *nb,
537                        struct revision_baton *rb,
538                        apr_pool_t *pool)
539 {
540   struct parse_baton *pb = rb->pb;
541
542   if ((nb->copyfrom_path == NULL) || (! pb->use_history))
543     {
544       /* Add empty file or dir, without history. */
545       if (nb->kind == svn_node_file)
546         SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
547
548       else if (nb->kind == svn_node_dir)
549         SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
550     }
551   else
552     {
553       /* Hunt down the source revision in this fs. */
554       svn_fs_root_t *copy_root;
555       svn_revnum_t copyfrom_rev;
556
557       /* Try to find the copyfrom revision in the revision map;
558          failing that, fall back to the revision offset approach. */
559       copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
560       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
561         copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
562
563       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
564         return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
565                                  _("Relative source revision %ld is not"
566                                    " available in current repository"),
567                                  copyfrom_rev);
568
569       SVN_ERR(svn_fs_revision_root(&copy_root, pb->fs, copyfrom_rev, pool));
570
571       if (nb->copy_source_checksum)
572         {
573           svn_checksum_t *checksum;
574           SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
575                                        nb->copyfrom_path, TRUE, pool));
576           if (!svn_checksum_match(nb->copy_source_checksum, checksum))
577             return svn_checksum_mismatch_err(nb->copy_source_checksum,
578                       checksum, pool,
579                       _("Copy source checksum mismatch on copy from '%s'@%ld\n"
580                         "to '%s' in rev based on r%ld"),
581                       nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
582         }
583
584       SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
585                           rb->txn_root, nb->path, pool));
586
587       if (pb->notify_func)
588         {
589           pb->notify->action = svn_repos_notify_load_copied_node;
590           pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
591         }
592     }
593
594   return SVN_NO_ERROR;
595 }
596
597 static svn_error_t *
598 magic_header_record(int version,
599                     void *parse_baton,
600                     apr_pool_t *pool)
601 {
602   return SVN_NO_ERROR;
603 }
604
605 static svn_error_t *
606 uuid_record(const char *uuid,
607             void *parse_baton,
608             apr_pool_t *pool)
609 {
610   struct parse_baton *pb = parse_baton;
611   svn_revnum_t youngest_rev;
612
613   if (pb->uuid_action == svn_repos_load_uuid_ignore)
614     return SVN_NO_ERROR;
615
616   if (pb->uuid_action != svn_repos_load_uuid_force)
617     {
618       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
619       if (youngest_rev != 0)
620         return SVN_NO_ERROR;
621     }
622
623   return svn_fs_set_uuid(pb->fs, uuid, pool);
624 }
625
626 static svn_error_t *
627 new_node_record(void **node_baton,
628                 apr_hash_t *headers,
629                 void *revision_baton,
630                 apr_pool_t *pool)
631 {
632   struct revision_baton *rb = revision_baton;
633   struct parse_baton *pb = rb->pb;
634   struct node_baton *nb;
635
636   if (rb->rev == 0)
637     return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
638                             _("Malformed dumpstream: "
639                               "Revision 0 must not contain node records"));
640
641   SVN_ERR(make_node_baton(&nb, headers, rb, pool));
642
643   /* If we're skipping this revision, we're done here. */
644   if (rb->skipped)
645     {
646       *node_baton = nb;
647       return SVN_NO_ERROR;
648     }
649
650   /* Make sure we have an action we recognize. */
651   if (nb->action < svn_node_action_change
652         || nb->action > svn_node_action_replace)
653       return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
654                                _("Unrecognized node-action on node '%s'"),
655                                nb->path);
656
657   if (pb->notify_func)
658     {
659       pb->notify->action = svn_repos_notify_load_node_start;
660       pb->notify->node_action = nb->action;
661       pb->notify->path = nb->path;
662       pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
663     }
664
665   switch (nb->action)
666     {
667     case svn_node_action_change:
668       break;
669
670     case svn_node_action_delete:
671       SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
672       break;
673
674     case svn_node_action_add:
675       SVN_ERR(maybe_add_with_history(nb, rb, pool));
676       break;
677
678     case svn_node_action_replace:
679       SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
680       SVN_ERR(maybe_add_with_history(nb, rb, pool));
681       break;
682     }
683
684   *node_baton = nb;
685   return SVN_NO_ERROR;
686 }
687
688 static svn_error_t *
689 set_revision_property(void *baton,
690                       const char *name,
691                       const svn_string_t *value)
692 {
693   struct revision_baton *rb = baton;
694
695   /* If we're skipping this revision, we're done here. */
696   if (rb->skipped)
697     return SVN_NO_ERROR;
698
699   if (rb->rev > 0)
700     {
701       if (rb->pb->validate_props)
702         SVN_ERR(svn_repos_fs_change_txn_prop(rb->txn, name, value, rb->pool));
703       else
704         SVN_ERR(svn_fs_change_txn_prop(rb->txn, name, value, rb->pool));
705
706       /* Remember any datestamp that passes through!  (See comment in
707          close_revision() below.) */
708       if (! strcmp(name, SVN_PROP_REVISION_DATE))
709         rb->datestamp = svn_string_dup(value, rb->pool);
710     }
711   else if (rb->rev == 0)
712     {
713       /* Special case: set revision 0 properties when loading into an
714          'empty' filesystem. */
715       struct parse_baton *pb = rb->pb;
716       svn_revnum_t youngest_rev;
717
718       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
719
720       if (youngest_rev == 0)
721         SVN_ERR(change_rev_prop(pb->repos, 0, name, value,
722                                 pb->validate_props, rb->pool));
723     }
724
725   return SVN_NO_ERROR;
726 }
727
728
729 static svn_error_t *
730 set_node_property(void *baton,
731                   const char *name,
732                   const svn_string_t *value)
733 {
734   struct node_baton *nb = baton;
735   struct revision_baton *rb = nb->rb;
736   struct parse_baton *pb = rb->pb;
737
738   /* If we're skipping this revision, we're done here. */
739   if (rb->skipped)
740     return SVN_NO_ERROR;
741
742   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
743     {
744       svn_string_t *renumbered_mergeinfo;
745       /* ### Need to cast away const. We cannot change the declaration of
746        * ### this function since it is part of svn_repos_parse_fns2_t. */
747       svn_string_t *prop_val = (svn_string_t *)value;
748
749       /* Tolerate mergeinfo with "\r\n" line endings because some
750          dumpstream sources might contain as much.  If so normalize
751          the line endings to '\n' and make a notification to
752          PARSE_BATON->FEEDBACK_STREAM that we have made this
753          correction. */
754       if (strstr(prop_val->data, "\r"))
755         {
756           const char *prop_eol_normalized;
757
758           SVN_ERR(svn_subst_translate_cstring2(prop_val->data,
759                                                &prop_eol_normalized,
760                                                "\n",  /* translate to LF */
761                                                FALSE, /* no repair */
762                                                NULL,  /* no keywords */
763                                                FALSE, /* no expansion */
764                                                nb->pool));
765           prop_val->data = prop_eol_normalized;
766           prop_val->len = strlen(prop_eol_normalized);
767
768           if (pb->notify_func)
769             {
770               pb->notify->action = svn_repos_notify_load_normalized_mergeinfo;
771               pb->notify_func(pb->notify_baton, pb->notify, nb->pool);
772             }
773         }
774
775       /* Renumber mergeinfo as appropriate. */
776       SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, prop_val, rb,
777                                       nb->pool));
778       value = renumbered_mergeinfo;
779       if (pb->parent_dir)
780         {
781           /* Prefix the merge source paths with PB->parent_dir. */
782           /* ASSUMPTION: All source paths are included in the dump stream. */
783           svn_string_t *mergeinfo_val;
784           SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value,
785                                          pb->parent_dir, nb->pool));
786           value = mergeinfo_val;
787         }
788     }
789
790   return change_node_prop(rb->txn_root, nb->path, name, value,
791                           pb->validate_props, nb->pool);
792 }
793
794
795 static svn_error_t *
796 delete_node_property(void *baton,
797                      const char *name)
798 {
799   struct node_baton *nb = baton;
800   struct revision_baton *rb = nb->rb;
801
802   /* If we're skipping this revision, we're done here. */
803   if (rb->skipped)
804     return SVN_NO_ERROR;
805
806   return change_node_prop(rb->txn_root, nb->path, name, NULL,
807                           rb->pb->validate_props, nb->pool);
808 }
809
810
811 static svn_error_t *
812 remove_node_props(void *baton)
813 {
814   struct node_baton *nb = baton;
815   struct revision_baton *rb = nb->rb;
816   apr_hash_t *proplist;
817   apr_hash_index_t *hi;
818
819   /* If we're skipping this revision, we're done here. */
820   if (rb->skipped)
821     return SVN_NO_ERROR;
822
823   SVN_ERR(svn_fs_node_proplist(&proplist,
824                                rb->txn_root, nb->path, nb->pool));
825
826   for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
827     {
828       const void *key;
829
830       apr_hash_this(hi, &key, NULL, NULL);
831       SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
832                                rb->pb->validate_props, nb->pool));
833     }
834
835   return SVN_NO_ERROR;
836 }
837
838
839 static svn_error_t *
840 apply_textdelta(svn_txdelta_window_handler_t *handler,
841                 void **handler_baton,
842                 void *node_baton)
843 {
844   struct node_baton *nb = node_baton;
845   struct revision_baton *rb = nb->rb;
846
847   /* If we're skipping this revision, we're done here. */
848   if (rb->skipped)
849     {
850       *handler = NULL;
851       return SVN_NO_ERROR;
852     }
853
854   return svn_fs_apply_textdelta(handler, handler_baton,
855                                 rb->txn_root, nb->path,
856                                 svn_checksum_to_cstring(nb->base_checksum,
857                                                         nb->pool),
858                                 svn_checksum_to_cstring(nb->result_checksum,
859                                                         nb->pool),
860                                 nb->pool);
861 }
862
863
864 static svn_error_t *
865 set_fulltext(svn_stream_t **stream,
866              void *node_baton)
867 {
868   struct node_baton *nb = node_baton;
869   struct revision_baton *rb = nb->rb;
870
871   /* If we're skipping this revision, we're done here. */
872   if (rb->skipped)
873     {
874       *stream = NULL;
875       return SVN_NO_ERROR;
876     }
877
878   return svn_fs_apply_text(stream,
879                            rb->txn_root, nb->path,
880                            svn_checksum_to_cstring(nb->result_checksum,
881                                                    nb->pool),
882                            nb->pool);
883 }
884
885
886 static svn_error_t *
887 close_node(void *baton)
888 {
889   struct node_baton *nb = baton;
890   struct revision_baton *rb = nb->rb;
891   struct parse_baton *pb = rb->pb;
892
893   /* If we're skipping this revision, we're done here. */
894   if (rb->skipped)
895     return SVN_NO_ERROR;
896
897   if (pb->notify_func)
898     {
899       pb->notify->action = svn_repos_notify_load_node_done;
900       pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
901     }
902
903   return SVN_NO_ERROR;
904 }
905
906
907 static svn_error_t *
908 close_revision(void *baton)
909 {
910   struct revision_baton *rb = baton;
911   struct parse_baton *pb = rb->pb;
912   const char *conflict_msg = NULL;
913   svn_revnum_t committed_rev;
914   svn_error_t *err;
915   const char *txn_name = NULL;
916   apr_hash_t *hooks_env;
917
918   /* If we're skipping this revision or it has an invalid revision
919      number, we're done here. */
920   if (rb->skipped || (rb->rev <= 0))
921     return SVN_NO_ERROR;
922
923   /* Get the txn name and hooks environment if they will be needed. */
924   if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
925     {
926       SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
927                                          rb->pool, rb->pool));
928
929       err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
930       if (err)
931         {
932           svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
933           return svn_error_trace(err);
934         }
935     }
936
937   /* Run the pre-commit hook, if so commanded. */
938   if (pb->use_pre_commit_hook)
939     {
940       err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
941                                         txn_name, rb->pool);
942       if (err)
943         {
944           svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
945           return svn_error_trace(err);
946         }
947     }
948
949   /* Commit. */
950   err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
951   if (SVN_IS_VALID_REVNUM(committed_rev))
952     {
953       if (err)
954         {
955           /* ### Log any error, but better yet is to rev
956              ### close_revision()'s API to allow both committed_rev and err
957              ### to be returned, see #3768. */
958           svn_error_clear(err);
959         }
960     }
961   else
962     {
963       svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
964       if (conflict_msg)
965         return svn_error_quick_wrap(err, conflict_msg);
966       else
967         return svn_error_trace(err);
968     }
969
970   /* Run post-commit hook, if so commanded.  */
971   if (pb->use_post_commit_hook)
972     {
973       if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
974                                               committed_rev, txn_name,
975                                               rb->pool)))
976         return svn_error_create
977           (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
978            _("Commit succeeded, but post-commit hook failed"));
979     }
980
981   /* After a successful commit, must record the dump-rev -> in-repos-rev
982      mapping, so that copyfrom instructions in the dump file can look up the
983      correct repository revision to copy from. */
984   set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
985
986   /* If the incoming dump stream has non-contiguous revisions (e.g. from
987      using svndumpfilter --drop-empty-revs without --renumber-revs) then
988      we must account for the missing gaps in PB->REV_MAP.  Otherwise we
989      might not be able to map all mergeinfo source revisions to the correct
990      revisions in the target repos. */
991   if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
992       && (rb->rev != pb->last_rev_mapped + 1))
993     {
994       svn_revnum_t i;
995
996       for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
997         {
998           set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
999         }
1000     }
1001
1002   /* Update our "last revision mapped". */
1003   pb->last_rev_mapped = rb->rev;
1004
1005   /* Deltify the predecessors of paths changed in this revision. */
1006   SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1007
1008   /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
1009      current clock-time.  We don't want that, we want to preserve
1010      history exactly.  Good thing revision props aren't versioned!
1011      Note that if rb->datestamp is NULL, that's fine -- if the dump
1012      data doesn't carry a datestamp, we want to preserve that fact in
1013      the load. */
1014   SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
1015                           rb->datestamp, pb->validate_props, rb->pool));
1016
1017   if (pb->notify_func)
1018     {
1019       pb->notify->action = svn_repos_notify_load_txn_committed;
1020       pb->notify->new_revision = committed_rev;
1021       pb->notify->old_revision = ((committed_rev == rb->rev)
1022                                     ? SVN_INVALID_REVNUM
1023                                     : rb->rev);
1024       pb->notify_func(pb->notify_baton, pb->notify, rb->pool);
1025     }
1026
1027   return SVN_NO_ERROR;
1028 }
1029
1030
1031 /*----------------------------------------------------------------------*/
1032 \f
1033 /** The public routines **/
1034
1035
1036 svn_error_t *
1037 svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
1038                                void **parse_baton,
1039                                svn_repos_t *repos,
1040                                svn_revnum_t start_rev,
1041                                svn_revnum_t end_rev,
1042                                svn_boolean_t use_history,
1043                                svn_boolean_t validate_props,
1044                                enum svn_repos_load_uuid uuid_action,
1045                                const char *parent_dir,
1046                                svn_repos_notify_func_t notify_func,
1047                                void *notify_baton,
1048                                apr_pool_t *pool)
1049 {
1050   svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1051   struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1052
1053   if (parent_dir)
1054     parent_dir = svn_relpath_canonicalize(parent_dir, pool);
1055
1056   SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1057                   SVN_IS_VALID_REVNUM(end_rev))
1058                  || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1059                      (! SVN_IS_VALID_REVNUM(end_rev))));
1060   if (SVN_IS_VALID_REVNUM(start_rev))
1061     SVN_ERR_ASSERT(start_rev <= end_rev);
1062
1063   parser->magic_header_record = magic_header_record;
1064   parser->uuid_record = uuid_record;
1065   parser->new_revision_record = new_revision_record;
1066   parser->new_node_record = new_node_record;
1067   parser->set_revision_property = set_revision_property;
1068   parser->set_node_property = set_node_property;
1069   parser->remove_node_props = remove_node_props;
1070   parser->set_fulltext = set_fulltext;
1071   parser->close_node = close_node;
1072   parser->close_revision = close_revision;
1073   parser->delete_node_property = delete_node_property;
1074   parser->apply_textdelta = apply_textdelta;
1075
1076   pb->repos = repos;
1077   pb->fs = svn_repos_fs(repos);
1078   pb->use_history = use_history;
1079   pb->validate_props = validate_props;
1080   pb->notify_func = notify_func;
1081   pb->notify_baton = notify_baton;
1082   pb->notify = svn_repos_notify_create(svn_repos_notify_load_txn_start, pool);
1083   pb->uuid_action = uuid_action;
1084   pb->parent_dir = parent_dir;
1085   pb->pool = pool;
1086   pb->rev_map = apr_hash_make(pool);
1087   pb->oldest_old_rev = SVN_INVALID_REVNUM;
1088   pb->last_rev_mapped = SVN_INVALID_REVNUM;
1089   pb->start_rev = start_rev;
1090   pb->end_rev = end_rev;
1091
1092   *callbacks = parser;
1093   *parse_baton = pb;
1094   return SVN_NO_ERROR;
1095 }
1096
1097
1098
1099 svn_error_t *
1100 svn_repos_load_fs4(svn_repos_t *repos,
1101                    svn_stream_t *dumpstream,
1102                    svn_revnum_t start_rev,
1103                    svn_revnum_t end_rev,
1104                    enum svn_repos_load_uuid uuid_action,
1105                    const char *parent_dir,
1106                    svn_boolean_t use_pre_commit_hook,
1107                    svn_boolean_t use_post_commit_hook,
1108                    svn_boolean_t validate_props,
1109                    svn_repos_notify_func_t notify_func,
1110                    void *notify_baton,
1111                    svn_cancel_func_t cancel_func,
1112                    void *cancel_baton,
1113                    apr_pool_t *pool)
1114 {
1115   const svn_repos_parse_fns3_t *parser;
1116   void *parse_baton;
1117   struct parse_baton *pb;
1118
1119   /* This is really simple. */
1120
1121   SVN_ERR(svn_repos_get_fs_build_parser4(&parser, &parse_baton,
1122                                          repos,
1123                                          start_rev, end_rev,
1124                                          TRUE, /* look for copyfrom revs */
1125                                          validate_props,
1126                                          uuid_action,
1127                                          parent_dir,
1128                                          notify_func,
1129                                          notify_baton,
1130                                          pool));
1131
1132   /* Heh.  We know this is a parse_baton.  This file made it.  So
1133      cast away, and set our hook booleans.  */
1134   pb = parse_baton;
1135   pb->use_pre_commit_hook = use_pre_commit_hook;
1136   pb->use_post_commit_hook = use_post_commit_hook;
1137
1138   return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1139                                      cancel_func, cancel_baton, pool);
1140 }