]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/load-fs-vtable.c
Update Subversion and dependencies to 1.14.0 LTS.
[FreeBSD/FreeBSD.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_mergeinfo.h"
35 #include "svn_checksum.h"
36 #include "svn_subst.h"
37 #include "svn_dirent_uri.h"
38
39 #include <apr_lib.h>
40
41 #include "private/svn_fspath.h"
42 #include "private/svn_dep_compat.h"
43 #include "private/svn_mergeinfo_private.h"
44 #include "private/svn_repos_private.h"
45
46 /*----------------------------------------------------------------------*/
47 \f
48 /** Batons used herein **/
49
50 struct parse_baton
51 {
52   svn_repos_t *repos;
53   svn_fs_t *fs;
54
55   svn_boolean_t use_history;
56   svn_boolean_t validate_props;
57   svn_boolean_t ignore_dates;
58   svn_boolean_t normalize_props;
59   svn_boolean_t use_pre_commit_hook;
60   svn_boolean_t use_post_commit_hook;
61   enum svn_repos_load_uuid uuid_action;
62   const char *parent_dir; /* repository relpath, or NULL */
63   svn_repos_notify_func_t notify_func;
64   void *notify_baton;
65   apr_pool_t *notify_pool; /* scratch pool for notifications */
66   apr_pool_t *pool;
67
68   /* Start and end (inclusive) of revision range we'll pay attention
69      to, or a pair of SVN_INVALID_REVNUMs if we're not filtering by
70      revisions. */
71   svn_revnum_t start_rev;
72   svn_revnum_t end_rev;
73
74   /* A hash mapping copy-from revisions and mergeinfo range revisions
75      (svn_revnum_t *) in the dump stream to their corresponding revisions
76      (svn_revnum_t *) in the loaded repository.  The hash and its
77      contents are allocated in POOL. */
78   /* ### See https://issues.apache.org/jira/browse/SVN-3903
79      ### for discussion about improving the memory costs of this mapping. */
80   apr_hash_t *rev_map;
81
82   /* The most recent (youngest) revision from the dump stream mapped in
83      REV_MAP.  If no revisions have been mapped yet, this is set to
84      SVN_INVALID_REVNUM. */
85   svn_revnum_t last_rev_mapped;
86
87   /* The oldest revision loaded from the dump stream.  If no revisions
88      have been loaded yet, this is set to SVN_INVALID_REVNUM. */
89   svn_revnum_t oldest_dumpstream_rev;
90 };
91
92 struct revision_baton
93 {
94   /* rev num from dump file */
95   svn_revnum_t rev;
96   svn_fs_txn_t *txn;
97   svn_fs_root_t *txn_root;
98
99   const svn_string_t *datestamp;
100
101   /* (rev num from dump file) minus (rev num to be committed) */
102   apr_int32_t rev_offset;
103   svn_boolean_t skipped;
104
105   /* Array of svn_prop_t with revision properties. */
106   apr_array_header_t *revprops;
107
108   struct parse_baton *pb;
109   apr_pool_t *pool;
110 };
111
112 struct node_baton
113 {
114   const char *path;
115   svn_node_kind_t kind;
116   enum svn_node_action action;
117   svn_checksum_t *base_checksum;        /* null, if not available */
118   svn_checksum_t *result_checksum;      /* null, if not available */
119   svn_checksum_t *copy_source_checksum; /* null, if not available */
120
121   svn_revnum_t copyfrom_rev;
122   const char *copyfrom_path;
123
124   struct revision_baton *rb;
125   apr_pool_t *pool;
126 };
127
128
129 /*----------------------------------------------------------------------*/
130
131 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
132    anything added to the hash is allocated in the hash's pool. */
133 static void
134 set_revision_mapping(apr_hash_t *rev_map,
135                      svn_revnum_t from_rev,
136                      svn_revnum_t to_rev)
137 {
138   svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
139                                          sizeof(svn_revnum_t) * 2);
140   mapped_revs[0] = from_rev;
141   mapped_revs[1] = to_rev;
142   apr_hash_set(rev_map, mapped_revs,
143                sizeof(svn_revnum_t), mapped_revs + 1);
144 }
145
146 /* Return the revision to which FROM_REV maps in REV_MAP, or
147    SVN_INVALID_REVNUM if no such mapping exists. */
148 static svn_revnum_t
149 get_revision_mapping(apr_hash_t *rev_map,
150                      svn_revnum_t from_rev)
151 {
152   svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
153                                       sizeof(from_rev));
154   return to_rev ? *to_rev : SVN_INVALID_REVNUM;
155 }
156
157
158 /* Change revision property NAME to VALUE for REVISION in REPOS.
159    If NORMALIZE_PROPS is set, attempt to normalize properties before
160    changing them, if that is needed.  If VALIDATE_PROPS is set, use
161    functions which perform validation of the property value.
162    Otherwise, bypass those checks. */
163 static svn_error_t *
164 change_rev_prop(svn_repos_t *repos,
165                 svn_revnum_t revision,
166                 const char *name,
167                 const svn_string_t *value,
168                 svn_boolean_t validate_props,
169                 svn_boolean_t normalize_props,
170                 apr_pool_t *pool)
171 {
172   if (normalize_props)
173     SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
174
175   if (validate_props)
176     return svn_repos_fs_change_rev_prop4(repos, revision, NULL, name,
177                                          NULL, value, FALSE, FALSE,
178                                          NULL, NULL, pool);
179   else
180     return svn_fs_change_rev_prop2(svn_repos_fs(repos), revision, name,
181                                    NULL, value, pool);
182 }
183
184 /* Change property NAME to VALUE for PATH in TXN_ROOT.
185    If NORMALIZE_PROPS is set, attempt to normalize properties before
186    changing them, if that is needed.  If VALIDATE_PROPS is set, use
187    functions which perform validation of the property value.
188    Otherwise, bypass those checks. */
189 static svn_error_t *
190 change_node_prop(svn_fs_root_t *txn_root,
191                  const char *path,
192                  const char *name,
193                  const svn_string_t *value,
194                  svn_boolean_t validate_props,
195                  svn_boolean_t normalize_props,
196                  apr_pool_t *pool)
197 {
198   if (normalize_props)
199     SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
200
201   if (validate_props)
202     return svn_repos_fs_change_node_prop(txn_root, path, name, value, pool);
203   else
204     return svn_fs_change_node_prop(txn_root, path, name, value, pool);
205 }
206
207 /* Prepend the mergeinfo source paths in MERGEINFO_ORIG with PARENT_DIR, and
208    return it in *MERGEINFO_VAL. */
209 static svn_error_t *
210 prefix_mergeinfo_paths(svn_string_t **mergeinfo_val,
211                        const svn_string_t *mergeinfo_orig,
212                        const char *parent_dir,
213                        apr_pool_t *pool)
214 {
215   apr_hash_t *prefixed_mergeinfo, *mergeinfo;
216   apr_hash_index_t *hi;
217
218   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool));
219   prefixed_mergeinfo = apr_hash_make(pool);
220   for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
221     {
222       const char *merge_source = apr_hash_this_key(hi);
223       svn_rangelist_t *rangelist = apr_hash_this_val(hi);
224       const char *path, *canonicalized_path;
225
226       SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
227                                        merge_source, pool, pool));
228       merge_source = canonicalized_path;
229
230       /* The svn:mergeinfo property syntax demands a repos abspath */
231       path = svn_fspath__canonicalize(svn_relpath_join(parent_dir,
232                                                        merge_source, pool),
233                                       pool);
234       svn_hash_sets(prefixed_mergeinfo, path, rangelist);
235     }
236   return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool);
237 }
238
239
240 /* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists
241    as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL
242    (allocated from POOL).
243
244    Adjust any mergeinfo revisions not older than OLDEST_DUMPSTREAM_REV by
245    using REV_MAP which maps (svn_revnum_t) old rev to (svn_revnum_t) new rev.
246
247    Adjust any mergeinfo revisions older than OLDEST_DUMPSTREAM_REV by
248    (-OLDER_REVS_OFFSET), dropping any that become <= 0.
249  */
250 static svn_error_t *
251 renumber_mergeinfo_revs(svn_string_t **final_val,
252                         const svn_string_t *initial_val,
253                         apr_hash_t *rev_map,
254                         svn_revnum_t oldest_dumpstream_rev,
255                         apr_int32_t older_revs_offset,
256                         apr_pool_t *pool)
257 {
258   apr_pool_t *subpool = svn_pool_create(pool);
259   svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo;
260   svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool);
261   apr_hash_index_t *hi;
262
263   SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool));
264
265   /* Issue #3020
266      https://issues.apache.org/jira/browse/SVN-3020#desc16
267      Remove mergeinfo older than the oldest revision in the dump stream
268      and adjust its revisions by the difference between the head rev of
269      the target repository and the current dump stream rev. */
270   if (oldest_dumpstream_rev > 1)
271     {
272       /* predates_stream_mergeinfo := mergeinfo that refers to revs before
273          oldest_dumpstream_rev */
274       SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
275         &predates_stream_mergeinfo, mergeinfo,
276         oldest_dumpstream_rev - 1, 0,
277         TRUE, subpool, subpool));
278       /* mergeinfo := mergeinfo that refers to revs >= oldest_dumpstream_rev */
279       SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
280         &mergeinfo, mergeinfo,
281         oldest_dumpstream_rev - 1, 0,
282         FALSE, subpool, subpool));
283       SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists(
284         &predates_stream_mergeinfo, predates_stream_mergeinfo,
285         -older_revs_offset, subpool, subpool));
286     }
287   else
288     {
289       predates_stream_mergeinfo = NULL;
290     }
291
292   for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi))
293     {
294       const char *merge_source = apr_hash_this_key(hi);
295       svn_rangelist_t *rangelist = apr_hash_this_val(hi);
296       int i;
297
298       /* Possibly renumber revisions in merge source's rangelist. */
299       for (i = 0; i < rangelist->nelts; i++)
300         {
301           svn_revnum_t rev_from_map;
302           svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
303                                                    svn_merge_range_t *);
304           rev_from_map = get_revision_mapping(rev_map, range->start);
305           if (SVN_IS_VALID_REVNUM(rev_from_map))
306             {
307               range->start = rev_from_map;
308             }
309           else if (range->start == oldest_dumpstream_rev - 1)
310             {
311               /* Since the start revision of svn_merge_range_t are not
312                  inclusive there is one possible valid start revision that
313                  won't be found in the REV_MAP mapping of load stream
314                  revsions to loaded revisions: The revision immediately
315                  preceding the oldest revision from the load stream.
316                  This is a valid revision for mergeinfo, but not a valid
317                  copy from revision (which REV_MAP also maps for) so it
318                  will never be in the mapping.
319
320                  If that is what we have here, then find the mapping for the
321                  oldest rev from the load stream and subtract 1 to get the
322                  renumbered, non-inclusive, start revision. */
323               rev_from_map = get_revision_mapping(rev_map,
324                                                   oldest_dumpstream_rev);
325               if (SVN_IS_VALID_REVNUM(rev_from_map))
326                 range->start = rev_from_map - 1;
327             }
328           else
329             {
330               /* If we can't remap the start revision then don't even bother
331                  trying to remap the end revision.  It's possible we might
332                  actually succeed at the latter, which can result in invalid
333                  mergeinfo with a start rev > end rev.  If that gets into the
334                  repository then a world of bustage breaks loose anytime that
335                  bogus mergeinfo is parsed.  See
336                  https://issues.apache.org/jira/browse/SVN-3020#desc16.
337                  */
338               continue;
339             }
340
341           rev_from_map = get_revision_mapping(rev_map, range->end);
342           if (SVN_IS_VALID_REVNUM(rev_from_map))
343             range->end = rev_from_map;
344         }
345       svn_hash_sets(final_mergeinfo, merge_source, rangelist);
346     }
347
348   if (predates_stream_mergeinfo)
349     {
350       SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo,
351                                    subpool, subpool));
352     }
353
354   SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool));
355
356   SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool));
357   svn_pool_destroy(subpool);
358
359   return SVN_NO_ERROR;
360 }
361
362 /*----------------------------------------------------------------------*/
363 \f
364 /** vtable for doing commits to a fs **/
365
366
367 /* Make a node baton, parsing the relevant HEADERS.
368  *
369  * If RB->pb->parent_dir:
370  *   prefix it to NB->path
371  *   prefix it to NB->copyfrom_path (if present)
372  */
373 static svn_error_t *
374 make_node_baton(struct node_baton **node_baton_p,
375                 apr_hash_t *headers,
376                 struct revision_baton *rb,
377                 apr_pool_t *pool)
378 {
379   struct node_baton *nb = apr_pcalloc(pool, sizeof(*nb));
380   const char *val;
381
382   /* Start with sensible defaults. */
383   nb->rb = rb;
384   nb->pool = pool;
385   nb->kind = svn_node_unknown;
386
387   /* Then add info from the headers.  */
388   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH)))
389   {
390     const char *canonicalized_path;
391     SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
392                                           val, pool, pool));
393     val = canonicalized_path;
394     if (rb->pb->parent_dir)
395       nb->path = svn_relpath_join(rb->pb->parent_dir, val, pool);
396     else
397       nb->path = val;
398   }
399
400   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_KIND)))
401     {
402       if (! strcmp(val, "file"))
403         nb->kind = svn_node_file;
404       else if (! strcmp(val, "dir"))
405         nb->kind = svn_node_dir;
406     }
407
408   nb->action = (enum svn_node_action)(-1);  /* an invalid action code */
409   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_ACTION)))
410     {
411       if (! strcmp(val, "change"))
412         nb->action = svn_node_action_change;
413       else if (! strcmp(val, "add"))
414         nb->action = svn_node_action_add;
415       else if (! strcmp(val, "delete"))
416         nb->action = svn_node_action_delete;
417       else if (! strcmp(val, "replace"))
418         nb->action = svn_node_action_replace;
419     }
420
421   nb->copyfrom_rev = SVN_INVALID_REVNUM;
422   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV)))
423     {
424       nb->copyfrom_rev = SVN_STR_TO_REV(val);
425     }
426   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH)))
427     {
428       val = svn_relpath_canonicalize(val, pool);
429       if (rb->pb->parent_dir)
430         nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, val, pool);
431       else
432         nb->copyfrom_path = val;
433     }
434
435   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM)))
436     {
437       SVN_ERR(svn_checksum_parse_hex(&nb->result_checksum, svn_checksum_md5,
438                                      val, pool));
439     }
440
441   if ((val = svn_hash_gets(headers,
442                            SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM)))
443     {
444       SVN_ERR(svn_checksum_parse_hex(&nb->base_checksum, svn_checksum_md5, val,
445                                      pool));
446     }
447
448   if ((val = svn_hash_gets(headers,
449                            SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM)))
450     {
451       SVN_ERR(svn_checksum_parse_hex(&nb->copy_source_checksum,
452                                      svn_checksum_md5, val, pool));
453     }
454
455   /* What's cool about this dump format is that the parser just
456      ignores any unrecognized headers.  :-)  */
457
458   *node_baton_p = nb;
459   return SVN_NO_ERROR;
460 }
461
462 /* Make a revision baton, parsing the relevant HEADERS.
463  *
464  * Set RB->skipped iff the revision number is outside the range given in PB.
465  */
466 static struct revision_baton *
467 make_revision_baton(apr_hash_t *headers,
468                     struct parse_baton *pb,
469                     apr_pool_t *pool)
470 {
471   struct revision_baton *rb = apr_pcalloc(pool, sizeof(*rb));
472   const char *val;
473
474   rb->pb = pb;
475   rb->pool = pool;
476   rb->rev = SVN_INVALID_REVNUM;
477   rb->revprops = apr_array_make(rb->pool, 8, sizeof(svn_prop_t));
478
479   if ((val = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER)))
480     {
481       rb->rev = SVN_STR_TO_REV(val);
482
483       /* If we're filtering revisions, is this one we'll skip? */
484       rb->skipped = (SVN_IS_VALID_REVNUM(pb->start_rev)
485                      && ((rb->rev < pb->start_rev) ||
486                          (rb->rev > pb->end_rev)));
487     }
488
489   return rb;
490 }
491
492
493 static svn_error_t *
494 new_revision_record(void **revision_baton,
495                     apr_hash_t *headers,
496                     void *parse_baton,
497                     apr_pool_t *pool)
498 {
499   struct parse_baton *pb = parse_baton;
500   struct revision_baton *rb;
501   svn_revnum_t head_rev;
502
503   rb = make_revision_baton(headers, pb, pool);
504
505   /* ### If we're filtering revisions, and this is one we've skipped,
506      ### and we've skipped it because it has a revision number younger
507      ### than the youngest in our acceptable range, then should we
508      ### just bail out here? */
509   /*
510   if (rb->skipped && (rb->rev > pb->end_rev))
511     return svn_error_createf(SVN_ERR_CEASE_INVOCATION, 0,
512                              _("Finished processing acceptable load "
513                                "revision range"));
514   */
515
516   SVN_ERR(svn_fs_youngest_rev(&head_rev, pb->fs, pool));
517
518   /* FIXME: This is a lame fallback loading multiple segments of dump in
519      several separate operations. It is highly susceptible to race conditions.
520      Calculate the revision 'offset' for finding copyfrom sources.
521      It might be positive or negative. */
522   rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
523
524   if ((rb->rev > 0) && (! rb->skipped))
525     {
526       /* Create a new fs txn. */
527       SVN_ERR(svn_fs_begin_txn2(&(rb->txn), pb->fs, head_rev,
528                                 SVN_FS_TXN_CLIENT_DATE, pool));
529       SVN_ERR(svn_fs_txn_root(&(rb->txn_root), rb->txn, pool));
530
531       if (pb->notify_func)
532         {
533           /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
534           svn_repos_notify_t *notify = svn_repos_notify_create(
535                                             svn_repos_notify_load_txn_start,
536                                             pb->notify_pool);
537
538           notify->old_revision = rb->rev;
539           pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
540           svn_pool_clear(pb->notify_pool);
541         }
542
543       /* Stash the oldest "old" revision committed from the load stream. */
544       if (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))
545         pb->oldest_dumpstream_rev = rb->rev;
546     }
547
548   /* If we're skipping this revision, try to notify someone. */
549   if (rb->skipped && pb->notify_func)
550     {
551       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
552       svn_repos_notify_t *notify = svn_repos_notify_create(
553                                         svn_repos_notify_load_skipped_rev,
554                                         pb->notify_pool);
555
556       notify->old_revision = rb->rev;
557       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
558       svn_pool_clear(pb->notify_pool);
559     }
560
561   /* If we're parsing revision 0, only the revision props are (possibly)
562      interesting to us: when loading the stream into an empty
563      filesystem, then we want new filesystem's revision 0 to have the
564      same props.  Otherwise, we just ignore revision 0 in the stream. */
565
566   *revision_baton = rb;
567   return SVN_NO_ERROR;
568 }
569
570
571
572 /* Perform a copy or a plain add.
573  *
574  * For a copy, also adjust the copy-from rev, check any copy-source checksum,
575  * and send a notification.
576  */
577 static svn_error_t *
578 maybe_add_with_history(struct node_baton *nb,
579                        struct revision_baton *rb,
580                        apr_pool_t *pool)
581 {
582   struct parse_baton *pb = rb->pb;
583
584   if ((nb->copyfrom_path == NULL) || (! pb->use_history))
585     {
586       /* Add empty file or dir, without history. */
587       if (nb->kind == svn_node_file)
588         SVN_ERR(svn_fs_make_file(rb->txn_root, nb->path, pool));
589
590       else if (nb->kind == svn_node_dir)
591         SVN_ERR(svn_fs_make_dir(rb->txn_root, nb->path, pool));
592     }
593   else
594     {
595       /* Hunt down the source revision in this fs. */
596       svn_fs_root_t *copy_root;
597       svn_revnum_t copyfrom_rev;
598
599       /* Try to find the copyfrom revision in the revision map;
600          failing that, fall back to the revision offset approach. */
601       copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
602       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
603         copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
604
605       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
606         return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
607                                  _("Relative source revision %ld is not"
608                                    " available in current repository"),
609                                  copyfrom_rev);
610
611       SVN_ERR(svn_fs_revision_root(&copy_root, pb->fs, copyfrom_rev, pool));
612
613       if (nb->copy_source_checksum)
614         {
615           svn_checksum_t *checksum;
616           SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, copy_root,
617                                        nb->copyfrom_path, TRUE, pool));
618           if (!svn_checksum_match(nb->copy_source_checksum, checksum))
619             return svn_checksum_mismatch_err(nb->copy_source_checksum,
620                       checksum, pool,
621                       _("Copy source checksum mismatch on copy from '%s'@%ld\n"
622                         "to '%s' in rev based on r%ld"),
623                       nb->copyfrom_path, copyfrom_rev, nb->path, rb->rev);
624         }
625
626       SVN_ERR(svn_fs_copy(copy_root, nb->copyfrom_path,
627                           rb->txn_root, nb->path, pool));
628
629       if (pb->notify_func)
630         {
631           /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
632           svn_repos_notify_t *notify = svn_repos_notify_create(
633                                             svn_repos_notify_load_copied_node,
634                                             pb->notify_pool);
635
636           pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
637           svn_pool_clear(pb->notify_pool);
638         }
639     }
640
641   return SVN_NO_ERROR;
642 }
643
644 static svn_error_t *
645 uuid_record(const char *uuid,
646             void *parse_baton,
647             apr_pool_t *pool)
648 {
649   struct parse_baton *pb = parse_baton;
650   svn_revnum_t youngest_rev;
651
652   if (pb->uuid_action == svn_repos_load_uuid_ignore)
653     return SVN_NO_ERROR;
654
655   if (pb->uuid_action != svn_repos_load_uuid_force)
656     {
657       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, pool));
658       if (youngest_rev != 0)
659         return SVN_NO_ERROR;
660     }
661
662   return svn_fs_set_uuid(pb->fs, uuid, pool);
663 }
664
665 static svn_error_t *
666 new_node_record(void **node_baton,
667                 apr_hash_t *headers,
668                 void *revision_baton,
669                 apr_pool_t *pool)
670 {
671   struct revision_baton *rb = revision_baton;
672   struct parse_baton *pb = rb->pb;
673   struct node_baton *nb;
674
675   if (rb->rev == 0)
676     return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
677                             _("Malformed dumpstream: "
678                               "Revision 0 must not contain node records"));
679
680   SVN_ERR(make_node_baton(&nb, headers, rb, pool));
681
682   /* If we're skipping this revision, we're done here. */
683   if (rb->skipped)
684     {
685       *node_baton = nb;
686       return SVN_NO_ERROR;
687     }
688
689   /* Make sure we have an action we recognize. */
690   if (nb->action < svn_node_action_change
691         || nb->action > svn_node_action_replace)
692       return svn_error_createf(SVN_ERR_STREAM_UNRECOGNIZED_DATA, NULL,
693                                _("Unrecognized node-action on node '%s'"),
694                                nb->path);
695
696   if (pb->notify_func)
697     {
698       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
699       svn_repos_notify_t *notify = svn_repos_notify_create(
700                                         svn_repos_notify_load_node_start,
701                                         pb->notify_pool);
702
703       notify->path = nb->path;
704       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
705       svn_pool_clear(pb->notify_pool);
706     }
707
708   switch (nb->action)
709     {
710     case svn_node_action_change:
711       break;
712
713     case svn_node_action_delete:
714       SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
715       break;
716
717     case svn_node_action_add:
718       SVN_ERR(maybe_add_with_history(nb, rb, pool));
719       break;
720
721     case svn_node_action_replace:
722       SVN_ERR(svn_fs_delete(rb->txn_root, nb->path, pool));
723       SVN_ERR(maybe_add_with_history(nb, rb, pool));
724       break;
725     }
726
727   *node_baton = nb;
728   return SVN_NO_ERROR;
729 }
730
731 static svn_error_t *
732 set_revision_property(void *baton,
733                       const char *name,
734                       const svn_string_t *value)
735 {
736   struct revision_baton *rb = baton;
737   struct parse_baton *pb = rb->pb;
738   svn_boolean_t is_date = strcmp(name, SVN_PROP_REVISION_DATE) == 0;
739   svn_prop_t *prop;
740
741   /* If we're skipping this revision, we're done here. */
742   if (rb->skipped)
743     return SVN_NO_ERROR;
744
745   /* If we're ignoring dates, and this is one, we're done here. */
746   if (is_date && pb->ignore_dates)
747     return SVN_NO_ERROR;
748
749   /* Collect property changes to apply them in one FS call in
750      close_revision. */
751   prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
752   prop->name = apr_pstrdup(rb->pool, name);
753   prop->value = svn_string_dup(value, rb->pool);
754
755   /* Remember any datestamp that passes through!  (See comment in
756      close_revision() below.) */
757   if (is_date)
758     rb->datestamp = svn_string_dup(value, rb->pool);
759
760   return SVN_NO_ERROR;
761 }
762
763
764 svn_error_t *
765 svn_repos__adjust_mergeinfo_property(svn_string_t **new_value_p,
766                                      const svn_string_t *old_value,
767                                      const char *parent_dir,
768                                      apr_hash_t *rev_map,
769                                      svn_revnum_t oldest_dumpstream_rev,
770                                      apr_int32_t older_revs_offset,
771                                      svn_repos_notify_func_t notify_func,
772                                      void *notify_baton,
773                                      apr_pool_t *result_pool,
774                                      apr_pool_t *scratch_pool)
775 {
776   svn_string_t prop_val = *old_value;
777
778   /* Tolerate mergeinfo with "\r\n" line endings because some
779      dumpstream sources might contain as much.  If so normalize
780      the line endings to '\n' and notify that we have made this
781      correction. */
782   if (strstr(prop_val.data, "\r"))
783     {
784       const char *prop_eol_normalized;
785
786       SVN_ERR(svn_subst_translate_cstring2(prop_val.data,
787                                            &prop_eol_normalized,
788                                            "\n",  /* translate to LF */
789                                            FALSE, /* no repair */
790                                            NULL,  /* no keywords */
791                                            FALSE, /* no expansion */
792                                            result_pool));
793       prop_val.data = prop_eol_normalized;
794       prop_val.len = strlen(prop_eol_normalized);
795
796       if (notify_func)
797         {
798           svn_repos_notify_t *notify
799                   = svn_repos_notify_create(
800                                 svn_repos_notify_load_normalized_mergeinfo,
801                                 scratch_pool);
802
803           notify_func(notify_baton, notify, scratch_pool);
804         }
805     }
806
807   /* Renumber mergeinfo as appropriate. */
808   SVN_ERR(renumber_mergeinfo_revs(new_value_p, &prop_val,
809                                   rev_map, oldest_dumpstream_rev,
810                                   older_revs_offset,
811                                   result_pool));
812
813   if (parent_dir)
814     {
815       /* Prefix the merge source paths with PARENT_DIR. */
816       /* ASSUMPTION: All source paths are included in the dump stream. */
817       SVN_ERR(prefix_mergeinfo_paths(new_value_p, *new_value_p,
818                                      parent_dir, result_pool));
819     }
820
821   return SVN_NO_ERROR;
822 }
823
824
825 static svn_error_t *
826 set_node_property(void *baton,
827                   const char *name,
828                   const svn_string_t *value)
829 {
830   struct node_baton *nb = baton;
831   struct revision_baton *rb = nb->rb;
832   struct parse_baton *pb = rb->pb;
833
834   /* If we're skipping this revision, we're done here. */
835   if (rb->skipped)
836     return SVN_NO_ERROR;
837
838   /* Adjust mergeinfo. If this fails, presumably because the mergeinfo
839      property has an ill-formed value, then we must not fail to load
840      the repository (at least if it's a simple load with no revision
841      offset adjustments, path changes, etc.) so just warn and leave it
842      as it is. */
843   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
844     {
845       svn_string_t *new_value;
846       svn_error_t *err;
847
848       err = svn_repos__adjust_mergeinfo_property(&new_value, value,
849                                                  pb->parent_dir,
850                                                  pb->rev_map,
851                                                  pb->oldest_dumpstream_rev,
852                                                  rb->rev_offset,
853                                                  pb->notify_func, pb->notify_baton,
854                                                  nb->pool, pb->notify_pool);
855       svn_pool_clear(pb->notify_pool);
856       if (err)
857         {
858           if (pb->validate_props)
859             {
860               return svn_error_quick_wrap(
861                        err,
862                        _("Invalid svn:mergeinfo value"));
863             }
864           if (pb->notify_func)
865             {
866               svn_repos_notify_t *notify
867                 = svn_repos_notify_create(svn_repos_notify_warning,
868                                           pb->notify_pool);
869
870               notify->warning = svn_repos_notify_warning_invalid_mergeinfo;
871               notify->warning_str = _("Invalid svn:mergeinfo value; "
872                                       "leaving unchanged");
873               pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
874               svn_pool_clear(pb->notify_pool);
875             }
876           svn_error_clear(err);
877         }
878       else
879         {
880           value = new_value;
881         }
882     }
883
884   return change_node_prop(rb->txn_root, nb->path, name, value,
885                           pb->validate_props, rb->pb->normalize_props,
886                           nb->pool);
887 }
888
889
890 static svn_error_t *
891 delete_node_property(void *baton,
892                      const char *name)
893 {
894   struct node_baton *nb = baton;
895   struct revision_baton *rb = nb->rb;
896
897   /* If we're skipping this revision, we're done here. */
898   if (rb->skipped)
899     return SVN_NO_ERROR;
900
901   return change_node_prop(rb->txn_root, nb->path, name, NULL,
902                           rb->pb->validate_props, rb->pb->normalize_props,
903                           nb->pool);
904 }
905
906
907 static svn_error_t *
908 remove_node_props(void *baton)
909 {
910   struct node_baton *nb = baton;
911   struct revision_baton *rb = nb->rb;
912   apr_hash_t *proplist;
913   apr_hash_index_t *hi;
914
915   /* If we're skipping this revision, we're done here. */
916   if (rb->skipped)
917     return SVN_NO_ERROR;
918
919   SVN_ERR(svn_fs_node_proplist(&proplist,
920                                rb->txn_root, nb->path, nb->pool));
921
922   for (hi = apr_hash_first(nb->pool, proplist); hi; hi = apr_hash_next(hi))
923     {
924       const char *key = apr_hash_this_key(hi);
925
926       SVN_ERR(change_node_prop(rb->txn_root, nb->path, key, NULL,
927                                rb->pb->validate_props, rb->pb->normalize_props,
928                                nb->pool));
929     }
930
931   return SVN_NO_ERROR;
932 }
933
934
935 static svn_error_t *
936 apply_textdelta(svn_txdelta_window_handler_t *handler,
937                 void **handler_baton,
938                 void *node_baton)
939 {
940   struct node_baton *nb = node_baton;
941   struct revision_baton *rb = nb->rb;
942
943   /* If we're skipping this revision, we're done here. */
944   if (rb->skipped)
945     {
946       *handler = NULL;
947       return SVN_NO_ERROR;
948     }
949
950   return svn_fs_apply_textdelta(handler, handler_baton,
951                                 rb->txn_root, nb->path,
952                                 svn_checksum_to_cstring(nb->base_checksum,
953                                                         nb->pool),
954                                 svn_checksum_to_cstring(nb->result_checksum,
955                                                         nb->pool),
956                                 nb->pool);
957 }
958
959
960 static svn_error_t *
961 set_fulltext(svn_stream_t **stream,
962              void *node_baton)
963 {
964   struct node_baton *nb = node_baton;
965   struct revision_baton *rb = nb->rb;
966
967   /* If we're skipping this revision, we're done here. */
968   if (rb->skipped)
969     {
970       *stream = NULL;
971       return SVN_NO_ERROR;
972     }
973
974   return svn_fs_apply_text(stream,
975                            rb->txn_root, nb->path,
976                            svn_checksum_to_cstring(nb->result_checksum,
977                                                    nb->pool),
978                            nb->pool);
979 }
980
981
982 static svn_error_t *
983 close_node(void *baton)
984 {
985   struct node_baton *nb = baton;
986   struct revision_baton *rb = nb->rb;
987   struct parse_baton *pb = rb->pb;
988
989   /* If we're skipping this revision, we're done here. */
990   if (rb->skipped)
991     return SVN_NO_ERROR;
992
993   if (pb->notify_func)
994     {
995       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
996       svn_repos_notify_t *notify = svn_repos_notify_create(
997                                             svn_repos_notify_load_node_done,
998                                             pb->notify_pool);
999
1000       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1001       svn_pool_clear(pb->notify_pool);
1002     }
1003
1004   return SVN_NO_ERROR;
1005 }
1006
1007
1008 static svn_error_t *
1009 close_revision(void *baton)
1010 {
1011   struct revision_baton *rb = baton;
1012   struct parse_baton *pb = rb->pb;
1013   const char *conflict_msg = NULL;
1014   svn_revnum_t committed_rev;
1015   svn_error_t *err;
1016   const char *txn_name = NULL;
1017   apr_hash_t *hooks_env;
1018
1019   /* If we're skipping this revision we're done here. */
1020   if (rb->skipped)
1021     return SVN_NO_ERROR;
1022
1023   if (rb->rev == 0)
1024     {
1025       /* Special case: set revision 0 properties when loading into an
1026          'empty' filesystem. */
1027       svn_revnum_t youngest_rev;
1028
1029       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));
1030
1031       if (youngest_rev == 0)
1032         {
1033           apr_hash_t *orig_props;
1034           apr_hash_t *new_props;
1035           apr_array_header_t *diff;
1036           int i;
1037
1038           SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, 0, TRUE,
1039                                             rb->pool, rb->pool));
1040           new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1041           SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1042
1043           for (i = 0; i < diff->nelts; i++)
1044           {
1045               const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1046
1047               SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
1048                                       pb->validate_props, pb->normalize_props,
1049                                       rb->pool));
1050           }
1051         }
1052
1053       return SVN_NO_ERROR;
1054     }
1055
1056   /* If the dumpstream doesn't have an 'svn:date' property and we
1057      aren't ignoring the dates in the dumpstream altogether, remove
1058      any 'svn:date' revision property that was set by FS layer when
1059      the TXN was created.  */
1060   if (! (pb->ignore_dates || rb->datestamp))
1061     {
1062       svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1063       prop->name = SVN_PROP_REVISION_DATE;
1064       prop->value = NULL;
1065     }
1066
1067   if (rb->pb->normalize_props)
1068     {
1069       apr_pool_t *iterpool;
1070       int i;
1071
1072       iterpool = svn_pool_create(rb->pool);
1073       for (i = 0; i < rb->revprops->nelts; i++)
1074         {
1075           svn_prop_t *prop = &APR_ARRAY_IDX(rb->revprops, i, svn_prop_t);
1076
1077           svn_pool_clear(iterpool);
1078           SVN_ERR(svn_repos__normalize_prop(&prop->value, NULL, prop->name,
1079                                             prop->value, rb->pool, iterpool));
1080         }
1081       svn_pool_destroy(iterpool);
1082     }
1083
1084   /* Apply revision property changes. */
1085   if (rb->pb->validate_props)
1086     SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1087   else
1088     SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
1089
1090   /* Get the txn name and hooks environment if they will be needed. */
1091   if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
1092     {
1093       SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
1094                                          rb->pool, rb->pool));
1095
1096       err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
1097       if (err)
1098         {
1099           svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1100           return svn_error_trace(err);
1101         }
1102     }
1103
1104   /* Run the pre-commit hook, if so commanded. */
1105   if (pb->use_pre_commit_hook)
1106     {
1107       err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
1108                                         txn_name, rb->pool);
1109       if (err)
1110         {
1111           svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1112           return svn_error_trace(err);
1113         }
1114     }
1115
1116   /* Commit. */
1117   err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
1118   if (SVN_IS_VALID_REVNUM(committed_rev))
1119     {
1120       if (err)
1121         {
1122           /* ### Log any error, but better yet is to rev
1123              ### close_revision()'s API to allow both committed_rev and err
1124              ### to be returned, see #3768. */
1125           svn_error_clear(err);
1126         }
1127     }
1128   else
1129     {
1130       svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
1131       if (conflict_msg)
1132         return svn_error_quick_wrap(err, conflict_msg);
1133       else
1134         return svn_error_trace(err);
1135     }
1136
1137   /* Run post-commit hook, if so commanded.  */
1138   if (pb->use_post_commit_hook)
1139     {
1140       if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
1141                                               committed_rev, txn_name,
1142                                               rb->pool)))
1143         return svn_error_create
1144           (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
1145            _("Commit succeeded, but post-commit hook failed"));
1146     }
1147
1148   /* After a successful commit, must record the dump-rev -> in-repos-rev
1149      mapping, so that copyfrom instructions in the dump file can look up the
1150      correct repository revision to copy from. */
1151   set_revision_mapping(pb->rev_map, rb->rev, committed_rev);
1152
1153   /* If the incoming dump stream has non-contiguous revisions (e.g. from
1154      using svndumpfilter --drop-empty-revs without --renumber-revs) then
1155      we must account for the missing gaps in PB->REV_MAP.  Otherwise we
1156      might not be able to map all mergeinfo source revisions to the correct
1157      revisions in the target repos. */
1158   if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
1159       && (rb->rev != pb->last_rev_mapped + 1))
1160     {
1161       svn_revnum_t i;
1162
1163       for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
1164         {
1165           set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
1166         }
1167     }
1168
1169   /* Update our "last revision mapped". */
1170   pb->last_rev_mapped = rb->rev;
1171
1172   /* Deltify the predecessors of paths changed in this revision. */
1173   SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));
1174
1175   if (pb->notify_func)
1176     {
1177       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1178       svn_repos_notify_t *notify = svn_repos_notify_create(
1179                                         svn_repos_notify_load_txn_committed,
1180                                         pb->notify_pool);
1181
1182       notify->new_revision = committed_rev;
1183       notify->old_revision = ((committed_rev == rb->rev)
1184                                     ? SVN_INVALID_REVNUM
1185                                     : rb->rev);
1186       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1187       svn_pool_clear(pb->notify_pool);
1188     }
1189
1190   return SVN_NO_ERROR;
1191 }
1192
1193
1194 /*----------------------------------------------------------------------*/
1195 \f
1196 /** The public routines **/
1197
1198
1199 svn_error_t *
1200 svn_repos_get_fs_build_parser6(const svn_repos_parse_fns3_t **callbacks,
1201                                void **parse_baton,
1202                                svn_repos_t *repos,
1203                                svn_revnum_t start_rev,
1204                                svn_revnum_t end_rev,
1205                                svn_boolean_t use_history,
1206                                svn_boolean_t validate_props,
1207                                enum svn_repos_load_uuid uuid_action,
1208                                const char *parent_dir,
1209                                svn_boolean_t use_pre_commit_hook,
1210                                svn_boolean_t use_post_commit_hook,
1211                                svn_boolean_t ignore_dates,
1212                                svn_boolean_t normalize_props,
1213                                svn_repos_notify_func_t notify_func,
1214                                void *notify_baton,
1215                                apr_pool_t *pool)
1216 {
1217   svn_repos_parse_fns3_t *parser = apr_pcalloc(pool, sizeof(*parser));
1218   struct parse_baton *pb = apr_pcalloc(pool, sizeof(*pb));
1219
1220   if (parent_dir)
1221     {
1222       const char *canonicalized_path;
1223       SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL,
1224                                             parent_dir, pool, pool));
1225       parent_dir = canonicalized_path;
1226     }
1227
1228   SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1229                   SVN_IS_VALID_REVNUM(end_rev))
1230                  || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1231                      (! SVN_IS_VALID_REVNUM(end_rev))));
1232   if (SVN_IS_VALID_REVNUM(start_rev))
1233     SVN_ERR_ASSERT(start_rev <= end_rev);
1234
1235   parser->magic_header_record = NULL;
1236   parser->uuid_record = uuid_record;
1237   parser->new_revision_record = new_revision_record;
1238   parser->new_node_record = new_node_record;
1239   parser->set_revision_property = set_revision_property;
1240   parser->set_node_property = set_node_property;
1241   parser->remove_node_props = remove_node_props;
1242   parser->set_fulltext = set_fulltext;
1243   parser->close_node = close_node;
1244   parser->close_revision = close_revision;
1245   parser->delete_node_property = delete_node_property;
1246   parser->apply_textdelta = apply_textdelta;
1247
1248   pb->repos = repos;
1249   pb->fs = svn_repos_fs(repos);
1250   pb->use_history = use_history;
1251   pb->validate_props = validate_props;
1252   pb->notify_func = notify_func;
1253   pb->notify_baton = notify_baton;
1254   pb->uuid_action = uuid_action;
1255   pb->parent_dir = parent_dir;
1256   pb->pool = pool;
1257   pb->notify_pool = svn_pool_create(pool);
1258   pb->rev_map = apr_hash_make(pool);
1259   pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1260   pb->last_rev_mapped = SVN_INVALID_REVNUM;
1261   pb->start_rev = start_rev;
1262   pb->end_rev = end_rev;
1263   pb->use_pre_commit_hook = use_pre_commit_hook;
1264   pb->use_post_commit_hook = use_post_commit_hook;
1265   pb->ignore_dates = ignore_dates;
1266   pb->normalize_props = normalize_props;
1267
1268   *callbacks = parser;
1269   *parse_baton = pb;
1270   return SVN_NO_ERROR;
1271 }
1272
1273
1274 svn_error_t *
1275 svn_repos_load_fs6(svn_repos_t *repos,
1276                    svn_stream_t *dumpstream,
1277                    svn_revnum_t start_rev,
1278                    svn_revnum_t end_rev,
1279                    enum svn_repos_load_uuid uuid_action,
1280                    const char *parent_dir,
1281                    svn_boolean_t use_pre_commit_hook,
1282                    svn_boolean_t use_post_commit_hook,
1283                    svn_boolean_t validate_props,
1284                    svn_boolean_t ignore_dates,
1285                    svn_boolean_t normalize_props,
1286                    svn_repos_notify_func_t notify_func,
1287                    void *notify_baton,
1288                    svn_cancel_func_t cancel_func,
1289                    void *cancel_baton,
1290                    apr_pool_t *pool)
1291 {
1292   const svn_repos_parse_fns3_t *parser;
1293   void *parse_baton;
1294
1295   /* This is really simple. */
1296
1297   SVN_ERR(svn_repos_get_fs_build_parser6(&parser, &parse_baton,
1298                                          repos,
1299                                          start_rev, end_rev,
1300                                          TRUE, /* look for copyfrom revs */
1301                                          validate_props,
1302                                          uuid_action,
1303                                          parent_dir,
1304                                          use_pre_commit_hook,
1305                                          use_post_commit_hook,
1306                                          ignore_dates,
1307                                          normalize_props,
1308                                          notify_func,
1309                                          notify_baton,
1310                                          pool));
1311
1312   return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1313                                      cancel_func, cancel_baton, pool);
1314 }
1315
1316 /*----------------------------------------------------------------------*/
1317 \f
1318 /** The same functionality for revprops only **/
1319
1320 /* Implement svn_repos_parse_fns3_t.new_revision_record.
1321  *
1322  * Because the revision is supposed to already exist, we don't need to
1323  * start transactions etc. */
1324 static svn_error_t *
1325 revprops_new_revision_record(void **revision_baton,
1326                              apr_hash_t *headers,
1327                              void *parse_baton,
1328                              apr_pool_t *pool)
1329 {
1330   struct parse_baton *pb = parse_baton;
1331   struct revision_baton *rb;
1332
1333   rb = make_revision_baton(headers, pb, pool);
1334
1335   /* If we're skipping this revision, try to notify someone. */
1336   if (rb->skipped && pb->notify_func)
1337     {
1338       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1339       svn_repos_notify_t *notify = svn_repos_notify_create(
1340                                         svn_repos_notify_load_skipped_rev,
1341                                         pb->notify_pool);
1342
1343       notify->old_revision = rb->rev;
1344       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1345       svn_pool_clear(pb->notify_pool);
1346     }
1347
1348   /* If we're parsing revision 0, only the revision props are (possibly)
1349      interesting to us: when loading the stream into an empty
1350      filesystem, then we want new filesystem's revision 0 to have the
1351      same props.  Otherwise, we just ignore revision 0 in the stream. */
1352
1353   *revision_baton = rb;
1354   return SVN_NO_ERROR;
1355 }
1356
1357 /* Implement svn_repos_parse_fns3_t.close_revision.
1358  *
1359  * Simply set the revprops we previously parsed and send notifications.
1360  * This is the place where we will detect missing revisions. */
1361 static svn_error_t *
1362 revprops_close_revision(void *baton)
1363 {
1364   struct revision_baton *rb = baton;
1365   struct parse_baton *pb = rb->pb;
1366   apr_hash_t *orig_props;
1367   apr_hash_t *new_props;
1368   apr_array_header_t *diff;
1369   int i;
1370
1371   /* If we're skipping this revision we're done here. */
1372   if (rb->skipped)
1373     return SVN_NO_ERROR;
1374
1375   /* If the dumpstream doesn't have an 'svn:date' property and we
1376      aren't ignoring the dates in the dumpstream altogether, remove
1377      any 'svn:date' revision property that was set by FS layer when
1378      the TXN was created.  */
1379   if (! (pb->ignore_dates || rb->datestamp))
1380     {
1381       svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
1382       prop->name = SVN_PROP_REVISION_DATE;
1383       prop->value = NULL;
1384     }
1385
1386   SVN_ERR(svn_fs_revision_proplist2(&orig_props, pb->fs, rb->rev, TRUE,
1387                                     rb->pool, rb->pool));
1388   new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
1389   SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));
1390
1391   for (i = 0; i < diff->nelts; i++)
1392     {
1393       const svn_prop_t *prop = &APR_ARRAY_IDX(diff, i, svn_prop_t);
1394
1395       SVN_ERR(change_rev_prop(pb->repos, rb->rev, prop->name, prop->value,
1396                               pb->validate_props, pb->normalize_props,
1397                               rb->pool));
1398     }
1399
1400   if (pb->notify_func)
1401     {
1402       /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
1403       svn_repos_notify_t *notify = svn_repos_notify_create(
1404                                         svn_repos_notify_load_revprop_set,
1405                                         pb->notify_pool);
1406
1407       notify->new_revision = rb->rev;
1408       notify->old_revision = SVN_INVALID_REVNUM;
1409       pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
1410       svn_pool_clear(pb->notify_pool);
1411     }
1412
1413   return SVN_NO_ERROR;
1414 }
1415
1416 /* Set *CALLBACKS and *PARSE_BATON to a vtable parser which commits new
1417  * revisions to the fs in REPOS.  Allocate the objects in RESULT_POOL.
1418  *
1419  * START_REV and END_REV act as filters, the lower and upper (inclusive)
1420  * range values of revisions in DUMPSTREAM which will be loaded.  Either
1421  * both of these values are #SVN_INVALID_REVNUM (in  which case no
1422  * revision-based filtering occurs at all), or both are valid revisions
1423  * (where START_REV is older than or equivalent to END_REV).
1424  *
1425  * START_REV and END_REV act as filters, the lower and upper (inclusive)
1426  * range values of revisions which will
1427  * be loaded.  Either both of these values are #SVN_INVALID_REVNUM (in
1428  * which case no revision-based filtering occurs at all), or both are
1429  * valid revisions (where START_REV is older than or equivalent to
1430  * END_REV).  They refer to dump stream revision numbers rather than
1431  * committed revision numbers.
1432  *
1433  * If VALIDATE_PROPS is set, then validate Subversion revision properties
1434  * (those in the svn: namespace) against established rules for those things.
1435  *
1436  * If IGNORE_DATES is set, ignore any revision datestamps found in
1437  * DUMPSTREAM, keeping whatever timestamps the revisions currently have.
1438  *
1439  * If NORMALIZE_PROPS is set, attempt to normalize invalid Subversion
1440  * revision and node properties (those in the svn: namespace) so that
1441  * their values would follow the established rules for them.  Currently,
1442  * this means translating non-LF line endings in the property values to LF.
1443  */
1444 static svn_error_t *
1445 build_revprop_parser(const svn_repos_parse_fns3_t **callbacks,
1446                      void **parse_baton,
1447                      svn_repos_t *repos,
1448                      svn_revnum_t start_rev,
1449                      svn_revnum_t end_rev,
1450                      svn_boolean_t validate_props,
1451                      svn_boolean_t ignore_dates,
1452                      svn_boolean_t normalize_props,
1453                      svn_repos_notify_func_t notify_func,
1454                      void *notify_baton,
1455                      apr_pool_t *result_pool)
1456 {
1457   svn_repos_parse_fns3_t *parser = apr_pcalloc(result_pool, sizeof(*parser));
1458   struct parse_baton *pb = apr_pcalloc(result_pool, sizeof(*pb));
1459
1460   SVN_ERR_ASSERT((SVN_IS_VALID_REVNUM(start_rev) &&
1461                   SVN_IS_VALID_REVNUM(end_rev))
1462                  || ((! SVN_IS_VALID_REVNUM(start_rev)) &&
1463                      (! SVN_IS_VALID_REVNUM(end_rev))));
1464   if (SVN_IS_VALID_REVNUM(start_rev))
1465     SVN_ERR_ASSERT(start_rev <= end_rev);
1466
1467   parser->magic_header_record = NULL;
1468   parser->uuid_record = uuid_record;
1469   parser->new_revision_record = revprops_new_revision_record;
1470   parser->new_node_record = NULL;
1471   parser->set_revision_property = set_revision_property;
1472   parser->set_node_property = NULL;
1473   parser->remove_node_props = NULL;
1474   parser->set_fulltext = NULL;
1475   parser->close_node = NULL;
1476   parser->close_revision = revprops_close_revision;
1477   parser->delete_node_property = NULL;
1478   parser->apply_textdelta = NULL;
1479
1480   pb->repos = repos;
1481   pb->fs = svn_repos_fs(repos);
1482   pb->use_history = FALSE;
1483   pb->validate_props = validate_props;
1484   pb->notify_func = notify_func;
1485   pb->notify_baton = notify_baton;
1486   pb->uuid_action = svn_repos_load_uuid_ignore; /* Never touch the UUID. */
1487   pb->parent_dir = NULL;
1488   pb->pool = result_pool;
1489   pb->notify_pool = svn_pool_create(result_pool);
1490   pb->rev_map = NULL;
1491   pb->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1492   pb->last_rev_mapped = SVN_INVALID_REVNUM;
1493   pb->start_rev = start_rev;
1494   pb->end_rev = end_rev;
1495   pb->use_pre_commit_hook = FALSE;
1496   pb->use_post_commit_hook = FALSE;
1497   pb->ignore_dates = ignore_dates;
1498   pb->normalize_props = normalize_props;
1499
1500   *callbacks = parser;
1501   *parse_baton = pb;
1502   return SVN_NO_ERROR;
1503 }
1504
1505
1506 svn_error_t *
1507 svn_repos_load_fs_revprops(svn_repos_t *repos,
1508                            svn_stream_t *dumpstream,
1509                            svn_revnum_t start_rev,
1510                            svn_revnum_t end_rev,
1511                            svn_boolean_t validate_props,
1512                            svn_boolean_t ignore_dates,
1513                            svn_boolean_t normalize_props,
1514                            svn_repos_notify_func_t notify_func,
1515                            void *notify_baton,
1516                            svn_cancel_func_t cancel_func,
1517                            void *cancel_baton,
1518                            apr_pool_t *scratch_pool)
1519 {
1520   const svn_repos_parse_fns3_t *parser;
1521   void *parse_baton;
1522
1523   /* This is really simple. */
1524
1525   SVN_ERR(build_revprop_parser(&parser, &parse_baton,
1526                                repos,
1527                                start_rev, end_rev,
1528                                validate_props,
1529                                ignore_dates,
1530                                normalize_props,
1531                                notify_func,
1532                                notify_baton,
1533                                scratch_pool));
1534
1535   return svn_repos_parse_dumpstream3(dumpstream, parser, parse_baton, FALSE,
1536                                      cancel_func, cancel_baton, scratch_pool);
1537 }