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