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