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
48/** Batons used herein **/
49
50struct 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
91struct 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
111struct 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. */
132static void
133set_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. */
147static svn_revnum_t
148get_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. */
160static svn_error_t *
161change_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. */
180static svn_error_t *
181change_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. */
196static svn_error_t *
197prefix_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 */
235static svn_error_t *
236renumber_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
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 */
358static svn_error_t *
359make_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 */
448static struct revision_baton *
449make_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
475static svn_error_t *
476new_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 */
559static svn_error_t *
560maybe_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
626static svn_error_t *
627magic_header_record(int version,
628                    void *parse_baton,
629                    apr_pool_t *pool)
630{
631  return SVN_NO_ERROR;
632}
633
634static svn_error_t *
635uuid_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
655static svn_error_t *
656new_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
721static svn_error_t *
722set_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
754svn_error_t *
755svn_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
815static svn_error_t *
816set_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
879static svn_error_t *
880delete_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
895static svn_error_t *
896remove_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
922static svn_error_t *
923apply_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
947static svn_error_t *
948set_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
969static svn_error_t *
970close_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
995static svn_error_t *
996close_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
1164/** The public routines **/
1165
1166
1167svn_error_t *
1168svn_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
1235svn_error_t *
1236svn_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}
1274