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(©_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