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