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