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