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