load_editor.c revision 289166
1/* 2 * load_editor.c: The svn_delta_editor_t editor used by svnrdump to 3 * load revisions. 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include "svn_cmdline.h" 26#include "svn_pools.h" 27#include "svn_delta.h" 28#include "svn_repos.h" 29#include "svn_props.h" 30#include "svn_path.h" 31#include "svn_ra.h" 32#include "svn_subst.h" 33#include "svn_io.h" 34#include "svn_private_config.h" 35#include "private/svn_repos_private.h" 36#include "private/svn_ra_private.h" 37#include "private/svn_mergeinfo_private.h" 38#include "private/svn_fspath.h" 39 40#include "svnrdump.h" 41 42#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock" 43 44#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 45 46#if 0 47#define LDR_DBG(x) SVN_DBG(x) 48#else 49#define LDR_DBG(x) while(0) 50#endif 51 52 53 54/** 55 * General baton used by the parser functions. 56 */ 57struct parse_baton 58{ 59 /* Commit editor and baton used to transfer loaded revisions to 60 the target repository. */ 61 const svn_delta_editor_t *commit_editor; 62 void *commit_edit_baton; 63 64 /* RA session(s) for committing to the target repository. */ 65 svn_ra_session_t *session; 66 svn_ra_session_t *aux_session; 67 68 /* To bleep, or not to bleep? (What kind of question is that?) */ 69 svn_boolean_t quiet; 70 71 /* UUID found in the dumpstream, if any; NULL otherwise. */ 72 const char *uuid; 73 74 /* Root URL of the target repository. */ 75 const char *root_url; 76 77 /* The "parent directory" of the target repository in which to load. 78 (This is essentially the difference between ROOT_URL and 79 SESSION's url, and roughly equivalent to the 'svnadmin load 80 --parent-dir' option.) */ 81 const char *parent_dir; 82 83 /* A mapping of svn_revnum_t * dump stream revisions to their 84 corresponding svn_revnum_t * target repository revisions. */ 85 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903 86 ### for discussion about improving the memory costs of this mapping. */ 87 apr_hash_t *rev_map; 88 89 /* The most recent (youngest) revision from the dump stream mapped in 90 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */ 91 svn_revnum_t last_rev_mapped; 92 93 /* The oldest revision loaded from the dump stream, or 94 SVN_INVALID_REVNUM if none have been loaded. */ 95 svn_revnum_t oldest_dumpstream_rev; 96}; 97 98/** 99 * Use to wrap the dir_context_t in commit.c so we can keep track of 100 * depth, relpath and parent for open_directory and close_directory. 101 */ 102struct directory_baton 103{ 104 void *baton; 105 const char *relpath; 106 int depth; 107 108 /* The copy-from source of this directory, no matter whether it is 109 copied explicitly (the root node of a copy) or implicitly (being an 110 existing child of a copied directory). For a node that is newly 111 added (without history), even inside a copied parent, these are 112 NULL and SVN_INVALID_REVNUM. */ 113 const char *copyfrom_path; 114 svn_revnum_t copyfrom_rev; 115 116 struct directory_baton *parent; 117}; 118 119/** 120 * Baton used to represent a node; to be used by the parser 121 * functions. Contains a link to the revision baton. 122 */ 123struct node_baton 124{ 125 const char *path; 126 svn_node_kind_t kind; 127 enum svn_node_action action; 128 129 /* Is this directory explicitly added? If not, then it already existed 130 or is a child of a copy. */ 131 svn_boolean_t is_added; 132 133 svn_revnum_t copyfrom_rev; 134 const char *copyfrom_path; 135 const char *copyfrom_url; 136 137 void *file_baton; 138 const char *base_checksum; 139 140 /* (const char *name) -> (svn_prop_t *) */ 141 apr_hash_t *prop_changes; 142 143 struct revision_baton *rb; 144}; 145 146/** 147 * Baton used to represet a revision; used by the parser 148 * functions. Contains a link to the parser baton. 149 */ 150struct revision_baton 151{ 152 svn_revnum_t rev; 153 apr_hash_t *revprop_table; 154 apr_int32_t rev_offset; 155 156 const svn_string_t *datestamp; 157 const svn_string_t *author; 158 159 struct parse_baton *pb; 160 struct directory_baton *db; 161 apr_pool_t *pool; 162}; 163 164 165 166/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that 167 anything added to the hash is allocated in the hash's pool. */ 168static void 169set_revision_mapping(apr_hash_t *rev_map, 170 svn_revnum_t from_rev, 171 svn_revnum_t to_rev) 172{ 173 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map), 174 sizeof(svn_revnum_t) * 2); 175 mapped_revs[0] = from_rev; 176 mapped_revs[1] = to_rev; 177 apr_hash_set(rev_map, mapped_revs, 178 sizeof(svn_revnum_t), mapped_revs + 1); 179} 180 181/* Return the revision to which FROM_REV maps in REV_MAP, or 182 SVN_INVALID_REVNUM if no such mapping exists. */ 183static svn_revnum_t 184get_revision_mapping(apr_hash_t *rev_map, 185 svn_revnum_t from_rev) 186{ 187 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev, 188 sizeof(from_rev)); 189 return to_rev ? *to_rev : SVN_INVALID_REVNUM; 190} 191 192 193/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with 194 PARENT_DIR, and return it in *MERGEINFO_VAL. */ 195/* ### FIXME: Consider somehow sharing code with 196 ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */ 197static svn_error_t * 198prefix_mergeinfo_paths(svn_string_t **mergeinfo_val, 199 const svn_string_t *mergeinfo_orig, 200 const char *parent_dir, 201 apr_pool_t *pool) 202{ 203 apr_hash_t *prefixed_mergeinfo, *mergeinfo; 204 apr_hash_index_t *hi; 205 void *rangelist; 206 207 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool)); 208 prefixed_mergeinfo = apr_hash_make(pool); 209 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) 210 { 211 const void *key; 212 const char *path, *merge_source; 213 214 apr_hash_this(hi, &key, NULL, &rangelist); 215 merge_source = svn_relpath_canonicalize(key, pool); 216 217 /* The svn:mergeinfo property syntax demands a repos abspath */ 218 path = svn_fspath__canonicalize(svn_relpath_join(parent_dir, 219 merge_source, pool), 220 pool); 221 svn_hash_sets(prefixed_mergeinfo, path, rangelist); 222 } 223 return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool); 224} 225 226 227/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists 228 as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL 229 (allocated from POOL). */ 230/* ### FIXME: Consider somehow sharing code with 231 ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */ 232static svn_error_t * 233renumber_mergeinfo_revs(svn_string_t **final_val, 234 const svn_string_t *initial_val, 235 struct revision_baton *rb, 236 apr_pool_t *pool) 237{ 238 apr_pool_t *subpool = svn_pool_create(pool); 239 svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo; 240 svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool); 241 apr_hash_index_t *hi; 242 243 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool)); 244 245 /* Issue #3020 246 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16 247 Remove mergeinfo older than the oldest revision in the dump stream 248 and adjust its revisions by the difference between the head rev of 249 the target repository and the current dump stream rev. */ 250 if (rb->pb->oldest_dumpstream_rev > 1) 251 { 252 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 253 &predates_stream_mergeinfo, mergeinfo, 254 rb->pb->oldest_dumpstream_rev - 1, 0, 255 TRUE, subpool, subpool)); 256 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 257 &mergeinfo, mergeinfo, 258 rb->pb->oldest_dumpstream_rev - 1, 0, 259 FALSE, subpool, subpool)); 260 SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists( 261 &predates_stream_mergeinfo, 262 predates_stream_mergeinfo, 263 -rb->rev_offset, subpool, subpool)); 264 } 265 else 266 { 267 predates_stream_mergeinfo = NULL; 268 } 269 270 for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi)) 271 { 272 svn_rangelist_t *rangelist; 273 struct parse_baton *pb = rb->pb; 274 int i; 275 const void *path; 276 apr_ssize_t pathlen; 277 void *val; 278 279 apr_hash_this(hi, &path, &pathlen, &val); 280 rangelist = val; 281 282 /* Possibly renumber revisions in merge source's rangelist. */ 283 for (i = 0; i < rangelist->nelts; i++) 284 { 285 svn_revnum_t rev_from_map; 286 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, 287 svn_merge_range_t *); 288 rev_from_map = get_revision_mapping(pb->rev_map, range->start); 289 if (SVN_IS_VALID_REVNUM(rev_from_map)) 290 { 291 range->start = rev_from_map; 292 } 293 else if (range->start == pb->oldest_dumpstream_rev - 1) 294 { 295 /* Since the start revision of svn_merge_range_t are not 296 inclusive there is one possible valid start revision that 297 won't be found in the PB->REV_MAP mapping of load stream 298 revsions to loaded revisions: The revision immediately 299 preceeding the oldest revision from the load stream. 300 This is a valid revision for mergeinfo, but not a valid 301 copy from revision (which PB->REV_MAP also maps for) so it 302 will never be in the mapping. 303 304 If that is what we have here, then find the mapping for the 305 oldest rev from the load stream and subtract 1 to get the 306 renumbered, non-inclusive, start revision. */ 307 rev_from_map = get_revision_mapping(pb->rev_map, 308 pb->oldest_dumpstream_rev); 309 if (SVN_IS_VALID_REVNUM(rev_from_map)) 310 range->start = rev_from_map - 1; 311 } 312 else 313 { 314 /* If we can't remap the start revision then don't even bother 315 trying to remap the end revision. It's possible we might 316 actually succeed at the latter, which can result in invalid 317 mergeinfo with a start rev > end rev. If that gets into the 318 repository then a world of bustage breaks loose anytime that 319 bogus mergeinfo is parsed. See 320 http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. 321 */ 322 continue; 323 } 324 325 rev_from_map = get_revision_mapping(pb->rev_map, range->end); 326 if (SVN_IS_VALID_REVNUM(rev_from_map)) 327 range->end = rev_from_map; 328 } 329 apr_hash_set(final_mergeinfo, path, pathlen, rangelist); 330 } 331 332 if (predates_stream_mergeinfo) 333 { 334 SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo, 335 subpool, subpool)); 336 } 337 338 SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool)); 339 340 SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool)); 341 svn_pool_destroy(subpool); 342 343 return SVN_NO_ERROR; 344} 345 346 347static svn_error_t * 348commit_callback(const svn_commit_info_t *commit_info, 349 void *baton, 350 apr_pool_t *pool) 351{ 352 struct revision_baton *rb = baton; 353 struct parse_baton *pb = rb->pb; 354 355 /* ### Don't print directly; generate a notification. */ 356 if (! pb->quiet) 357 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n", 358 commit_info->revision)); 359 360 /* Add the mapping of the dumpstream revision to the committed revision. */ 361 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision); 362 363 /* If the incoming dump stream has non-contiguous revisions (e.g. from 364 using svndumpfilter --drop-empty-revs without --renumber-revs) then 365 we must account for the missing gaps in PB->REV_MAP. Otherwise we 366 might not be able to map all mergeinfo source revisions to the correct 367 revisions in the target repos. */ 368 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) 369 && (rb->rev != pb->last_rev_mapped + 1)) 370 { 371 svn_revnum_t i; 372 373 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) 374 { 375 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); 376 } 377 } 378 379 /* Update our "last revision mapped". */ 380 pb->last_rev_mapped = rb->rev; 381 382 return SVN_NO_ERROR; 383} 384 385/* Implements `svn_ra__lock_retry_func_t'. */ 386static svn_error_t * 387lock_retry_func(void *baton, 388 const svn_string_t *reposlocktoken, 389 apr_pool_t *pool) 390{ 391 return svn_cmdline_printf(pool, 392 _("Failed to get lock on destination " 393 "repos, currently held by '%s'\n"), 394 reposlocktoken->data); 395} 396 397 398static svn_error_t * 399fetch_base_func(const char **filename, 400 void *baton, 401 const char *path, 402 svn_revnum_t base_revision, 403 apr_pool_t *result_pool, 404 apr_pool_t *scratch_pool) 405{ 406 struct revision_baton *rb = baton; 407 svn_stream_t *fstream; 408 svn_error_t *err; 409 410 if (! SVN_IS_VALID_REVNUM(base_revision)) 411 base_revision = rb->rev - 1; 412 413 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 414 svn_io_file_del_on_pool_cleanup, 415 result_pool, scratch_pool)); 416 417 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision, 418 fstream, NULL, NULL, scratch_pool); 419 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 420 { 421 svn_error_clear(err); 422 SVN_ERR(svn_stream_close(fstream)); 423 424 *filename = NULL; 425 return SVN_NO_ERROR; 426 } 427 else if (err) 428 return svn_error_trace(err); 429 430 SVN_ERR(svn_stream_close(fstream)); 431 432 return SVN_NO_ERROR; 433} 434 435static svn_error_t * 436fetch_props_func(apr_hash_t **props, 437 void *baton, 438 const char *path, 439 svn_revnum_t base_revision, 440 apr_pool_t *result_pool, 441 apr_pool_t *scratch_pool) 442{ 443 struct revision_baton *rb = baton; 444 svn_node_kind_t node_kind; 445 446 if (! SVN_IS_VALID_REVNUM(base_revision)) 447 base_revision = rb->rev - 1; 448 449 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 450 &node_kind, scratch_pool)); 451 452 if (node_kind == svn_node_file) 453 { 454 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision, 455 NULL, NULL, props, result_pool)); 456 } 457 else if (node_kind == svn_node_dir) 458 { 459 apr_array_header_t *tmp_props; 460 461 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path, 462 base_revision, 0 /* Dirent fields */, 463 result_pool)); 464 tmp_props = svn_prop_hash_to_array(*props, result_pool); 465 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 466 result_pool)); 467 *props = svn_prop_array_to_hash(tmp_props, result_pool); 468 } 469 else 470 { 471 *props = apr_hash_make(result_pool); 472 } 473 474 return SVN_NO_ERROR; 475} 476 477static svn_error_t * 478fetch_kind_func(svn_node_kind_t *kind, 479 void *baton, 480 const char *path, 481 svn_revnum_t base_revision, 482 apr_pool_t *scratch_pool) 483{ 484 struct revision_baton *rb = baton; 485 486 if (! SVN_IS_VALID_REVNUM(base_revision)) 487 base_revision = rb->rev - 1; 488 489 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 490 kind, scratch_pool)); 491 492 return SVN_NO_ERROR; 493} 494 495static svn_delta_shim_callbacks_t * 496get_shim_callbacks(struct revision_baton *rb, 497 apr_pool_t *pool) 498{ 499 svn_delta_shim_callbacks_t *callbacks = 500 svn_delta_shim_callbacks_default(pool); 501 502 callbacks->fetch_props_func = fetch_props_func; 503 callbacks->fetch_kind_func = fetch_kind_func; 504 callbacks->fetch_base_func = fetch_base_func; 505 callbacks->fetch_baton = rb; 506 507 return callbacks; 508} 509 510/* Acquire a lock (of sorts) on the repository associated with the 511 * given RA SESSION. This lock is just a revprop change attempt in a 512 * time-delay loop. This function is duplicated by svnsync in 513 * svnsync/svnsync.c 514 * 515 * ### TODO: Make this function more generic and 516 * expose it through a header for use by other Subversion 517 * applications to avoid duplication. 518 */ 519static svn_error_t * 520get_lock(const svn_string_t **lock_string_p, 521 svn_ra_session_t *session, 522 svn_cancel_func_t cancel_func, 523 void *cancel_baton, 524 apr_pool_t *pool) 525{ 526 svn_boolean_t be_atomic; 527 528 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 529 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 530 pool)); 531 if (! be_atomic) 532 { 533 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */ 534 svn_error_t *err = 535 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 536 _("Target server does not support atomic revision " 537 "property edits; consider upgrading it to 1.7.")); 538 svn_handle_warning2(stderr, err, "svnrdump: "); 539 svn_error_clear(err); 540 } 541 542 return svn_ra__get_operational_lock(lock_string_p, NULL, session, 543 SVNRDUMP_PROP_LOCK, FALSE, 544 10 /* retries */, lock_retry_func, NULL, 545 cancel_func, cancel_baton, pool); 546} 547 548static svn_error_t * 549new_revision_record(void **revision_baton, 550 apr_hash_t *headers, 551 void *parse_baton, 552 apr_pool_t *pool) 553{ 554 struct revision_baton *rb; 555 struct parse_baton *pb; 556 apr_hash_index_t *hi; 557 svn_revnum_t head_rev; 558 559 rb = apr_pcalloc(pool, sizeof(*rb)); 560 pb = parse_baton; 561 rb->pool = svn_pool_create(pool); 562 rb->pb = pb; 563 rb->db = NULL; 564 565 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi)) 566 { 567 const char *hname = svn__apr_hash_index_key(hi); 568 const char *hval = svn__apr_hash_index_val(hi); 569 570 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0) 571 rb->rev = atoi(hval); 572 } 573 574 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool)); 575 576 /* FIXME: This is a lame fallback loading multiple segments of dump in 577 several separate operations. It is highly susceptible to race conditions. 578 Calculate the revision 'offset' for finding copyfrom sources. 579 It might be positive or negative. */ 580 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1)); 581 582 /* Stash the oldest (non-zero) dumpstream revision seen. */ 583 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))) 584 pb->oldest_dumpstream_rev = rb->rev; 585 586 /* Set the commit_editor/ commit_edit_baton to NULL and wait for 587 them to be created in new_node_record */ 588 rb->pb->commit_editor = NULL; 589 rb->pb->commit_edit_baton = NULL; 590 rb->revprop_table = apr_hash_make(rb->pool); 591 592 *revision_baton = rb; 593 return SVN_NO_ERROR; 594} 595 596static svn_error_t * 597magic_header_record(int version, 598 void *parse_baton, 599 apr_pool_t *pool) 600{ 601 return SVN_NO_ERROR; 602} 603 604static svn_error_t * 605uuid_record(const char *uuid, 606 void *parse_baton, 607 apr_pool_t *pool) 608{ 609 struct parse_baton *pb; 610 pb = parse_baton; 611 pb->uuid = apr_pstrdup(pool, uuid); 612 return SVN_NO_ERROR; 613} 614 615/* Push information about another directory onto the linked list RB->db. 616 * 617 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the 618 * repository-relative path of this directory. IS_ADDED is true iff this 619 * directory is being added (with or without history). If added with 620 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else 621 * are NULL/SVN_INVALID_REVNUM. 622 */ 623static void 624push_directory(struct revision_baton *rb, 625 void *child_baton, 626 const char *relpath, 627 svn_boolean_t is_added, 628 const char *copyfrom_path, 629 svn_revnum_t copyfrom_rev) 630{ 631 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db)); 632 633 SVN_ERR_ASSERT_NO_RETURN( 634 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM)); 635 636 /* If this node is an existing (not newly added) child of a copied node, 637 calculate where it was copied from. */ 638 if (!is_added 639 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 640 { 641 const char *name = svn_relpath_basename(relpath, NULL); 642 643 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name, 644 rb->pool); 645 copyfrom_rev = rb->db->copyfrom_rev; 646 } 647 648 child_db->baton = child_baton; 649 child_db->relpath = relpath; 650 child_db->copyfrom_path = copyfrom_path; 651 child_db->copyfrom_rev = copyfrom_rev; 652 child_db->parent = rb->db; 653 rb->db = child_db; 654} 655 656static svn_error_t * 657new_node_record(void **node_baton, 658 apr_hash_t *headers, 659 void *revision_baton, 660 apr_pool_t *pool) 661{ 662 struct revision_baton *rb = revision_baton; 663 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 664 void *commit_edit_baton = rb->pb->commit_edit_baton; 665 struct node_baton *nb; 666 apr_hash_index_t *hi; 667 void *child_baton; 668 const char *nb_dirname; 669 670 nb = apr_pcalloc(rb->pool, sizeof(*nb)); 671 nb->rb = rb; 672 nb->is_added = FALSE; 673 nb->copyfrom_path = NULL; 674 nb->copyfrom_url = NULL; 675 nb->copyfrom_rev = SVN_INVALID_REVNUM; 676 nb->prop_changes = apr_hash_make(rb->pool); 677 678 /* If the creation of commit_editor is pending, create it now and 679 open_root on it; also create a top-level directory baton. */ 680 681 if (!commit_editor) 682 { 683 /* The revprop_table should have been filled in with important 684 information like svn:log in set_revision_property. We can now 685 use it all this information to create our commit_editor. But 686 first, clear revprops that we aren't allowed to set with the 687 commit_editor. We'll set them separately using the RA API 688 after closing the editor (see close_revision). */ 689 690 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL); 691 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL); 692 693 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session, 694 get_shim_callbacks(rb, rb->pool))); 695 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 696 &commit_edit_baton, rb->revprop_table, 697 commit_callback, revision_baton, 698 NULL, FALSE, rb->pool)); 699 700 rb->pb->commit_editor = commit_editor; 701 rb->pb->commit_edit_baton = commit_edit_baton; 702 703 SVN_ERR(commit_editor->open_root(commit_edit_baton, 704 rb->rev - rb->rev_offset - 1, 705 rb->pool, &child_baton)); 706 707 LDR_DBG(("Opened root %p\n", child_baton)); 708 709 /* child_baton corresponds to the root directory baton here */ 710 push_directory(rb, child_baton, "", TRUE /*is_added*/, 711 NULL, SVN_INVALID_REVNUM); 712 } 713 714 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi)) 715 { 716 const char *hname = svn__apr_hash_index_key(hi); 717 const char *hval = svn__apr_hash_index_val(hi); 718 719 /* Parse the different kinds of headers we can encounter and 720 stuff them into the node_baton for writing later */ 721 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0) 722 nb->path = apr_pstrdup(rb->pool, hval); 723 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0) 724 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir; 725 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0) 726 { 727 if (strcmp(hval, "add") == 0) 728 nb->action = svn_node_action_add; 729 if (strcmp(hval, "change") == 0) 730 nb->action = svn_node_action_change; 731 if (strcmp(hval, "delete") == 0) 732 nb->action = svn_node_action_delete; 733 if (strcmp(hval, "replace") == 0) 734 nb->action = svn_node_action_replace; 735 } 736 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0) 737 nb->base_checksum = apr_pstrdup(rb->pool, hval); 738 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0) 739 nb->copyfrom_rev = atoi(hval); 740 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0) 741 nb->copyfrom_path = apr_pstrdup(rb->pool, hval); 742 } 743 744 nb_dirname = svn_relpath_dirname(nb->path, pool); 745 if (svn_path_compare_paths(nb_dirname, 746 rb->db->relpath) != 0) 747 { 748 char *ancestor_path; 749 apr_size_t residual_close_count; 750 apr_array_header_t *residual_open_path; 751 int i; 752 apr_size_t n; 753 754 /* Before attempting to handle the action, call open_directory 755 for all the path components and set the directory baton 756 accordingly */ 757 ancestor_path = 758 svn_relpath_get_longest_ancestor(nb_dirname, 759 rb->db->relpath, pool); 760 residual_close_count = 761 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path, 762 rb->db->relpath)); 763 residual_open_path = 764 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path, 765 nb_dirname), pool); 766 767 /* First close all as many directories as there are after 768 skip_ancestor, and then open fresh directories */ 769 for (n = 0; n < residual_close_count; n ++) 770 { 771 /* Don't worry about destroying the actual rb->db object, 772 since the pool we're using has the lifetime of one 773 revision anyway */ 774 LDR_DBG(("Closing dir %p\n", rb->db->baton)); 775 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 776 rb->db = rb->db->parent; 777 } 778 779 for (i = 0; i < residual_open_path->nelts; i ++) 780 { 781 char *relpath_compose = 782 svn_relpath_join(rb->db->relpath, 783 APR_ARRAY_IDX(residual_open_path, i, const char *), 784 rb->pool); 785 SVN_ERR(commit_editor->open_directory(relpath_compose, 786 rb->db->baton, 787 rb->rev - rb->rev_offset - 1, 788 rb->pool, &child_baton)); 789 LDR_DBG(("Opened dir %p\n", child_baton)); 790 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/, 791 NULL, SVN_INVALID_REVNUM); 792 } 793 } 794 795 /* Fix up the copyfrom information in light of mapped revisions and 796 non-root load targets, and convert copyfrom path into a full 797 URL. */ 798 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev)) 799 { 800 svn_revnum_t copyfrom_rev; 801 802 /* Try to find the copyfrom revision in the revision map; 803 failing that, fall back to the revision offset approach. */ 804 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev); 805 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 806 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset; 807 808 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 809 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 810 _("Relative source revision %ld is not" 811 " available in current repository"), 812 copyfrom_rev); 813 814 nb->copyfrom_rev = copyfrom_rev; 815 816 if (rb->pb->parent_dir) 817 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, 818 nb->copyfrom_path, rb->pool); 819 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url, 820 nb->copyfrom_path, 821 rb->pool); 822 } 823 824 825 switch (nb->action) 826 { 827 case svn_node_action_delete: 828 case svn_node_action_replace: 829 LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton)); 830 SVN_ERR(commit_editor->delete_entry(nb->path, 831 rb->rev - rb->rev_offset - 1, 832 rb->db->baton, rb->pool)); 833 if (nb->action == svn_node_action_delete) 834 break; 835 else 836 /* FALL THROUGH */; 837 case svn_node_action_add: 838 nb->is_added = TRUE; 839 switch (nb->kind) 840 { 841 case svn_node_file: 842 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton, 843 nb->copyfrom_url, 844 nb->copyfrom_rev, 845 rb->pool, &(nb->file_baton))); 846 LDR_DBG(("Added file %s to dir %p as %p\n", 847 nb->path, rb->db->baton, nb->file_baton)); 848 break; 849 case svn_node_dir: 850 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton, 851 nb->copyfrom_url, 852 nb->copyfrom_rev, 853 rb->pool, &child_baton)); 854 LDR_DBG(("Added dir %s to dir %p as %p\n", 855 nb->path, rb->db->baton, child_baton)); 856 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/, 857 nb->copyfrom_path, nb->copyfrom_rev); 858 break; 859 default: 860 break; 861 } 862 break; 863 case svn_node_action_change: 864 switch (nb->kind) 865 { 866 case svn_node_file: 867 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton, 868 SVN_INVALID_REVNUM, rb->pool, 869 &(nb->file_baton))); 870 break; 871 default: 872 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton, 873 rb->rev - rb->rev_offset - 1, 874 rb->pool, &child_baton)); 875 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/, 876 NULL, SVN_INVALID_REVNUM); 877 break; 878 } 879 break; 880 } 881 882 *node_baton = nb; 883 return SVN_NO_ERROR; 884} 885 886static svn_error_t * 887set_revision_property(void *baton, 888 const char *name, 889 const svn_string_t *value) 890{ 891 struct revision_baton *rb = baton; 892 893 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool)); 894 895 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool)); 896 897 if (rb->rev > 0) 898 { 899 svn_hash_sets(rb->revprop_table, 900 apr_pstrdup(rb->pool, name), 901 svn_string_dup(value, rb->pool)); 902 } 903 else if (rb->rev_offset == -1) 904 { 905 /* Special case: set revision 0 properties directly (which is 906 safe because the commit_editor hasn't been created yet), but 907 only when loading into an 'empty' filesystem. */ 908 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0, 909 name, NULL, value, rb->pool)); 910 } 911 912 /* Remember any datestamp/ author that passes through (see comment 913 in close_revision). */ 914 if (!strcmp(name, SVN_PROP_REVISION_DATE)) 915 rb->datestamp = svn_string_dup(value, rb->pool); 916 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR)) 917 rb->author = svn_string_dup(value, rb->pool); 918 919 return SVN_NO_ERROR; 920} 921 922static svn_error_t * 923set_node_property(void *baton, 924 const char *name, 925 const svn_string_t *value) 926{ 927 struct node_baton *nb = baton; 928 apr_pool_t *pool = nb->rb->pool; 929 svn_prop_t *prop; 930 931 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0) 932 { 933 svn_string_t *renumbered_mergeinfo; 934 svn_string_t prop_val; 935 936 /* Tolerate mergeinfo with "\r\n" line endings because some 937 dumpstream sources might contain as much. If so normalize 938 the line endings to '\n' and make a notification to 939 PARSE_BATON->FEEDBACK_STREAM that we have made this 940 correction. */ 941 if (strstr(value->data, "\r")) 942 { 943 const char *prop_eol_normalized; 944 945 SVN_ERR(svn_subst_translate_cstring2(value->data, 946 &prop_eol_normalized, 947 "\n", /* translate to LF */ 948 FALSE, /* no repair */ 949 NULL, /* no keywords */ 950 FALSE, /* no expansion */ 951 pool)); 952 prop_val.data = prop_eol_normalized; 953 prop_val.len = strlen(prop_eol_normalized); 954 value = &prop_val; 955 956 /* ### TODO: notify? */ 957 } 958 959 /* Renumber mergeinfo as appropriate. */ 960 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value, 961 nb->rb, pool)); 962 value = renumbered_mergeinfo; 963 964 if (nb->rb->pb->parent_dir) 965 { 966 /* Prefix the merge source paths with PB->parent_dir. */ 967 /* ASSUMPTION: All source paths are included in the dump stream. */ 968 svn_string_t *mergeinfo_val; 969 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value, 970 nb->rb->pb->parent_dir, pool)); 971 value = mergeinfo_val; 972 } 973 } 974 975 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool)); 976 977 SVN_ERR(svn_repos__validate_prop(name, value, pool)); 978 979 prop = apr_palloc(nb->rb->pool, sizeof (*prop)); 980 prop->name = apr_pstrdup(pool, name); 981 prop->value = value ? svn_string_dup(value, pool) : NULL; 982 svn_hash_sets(nb->prop_changes, prop->name, prop); 983 984 return SVN_NO_ERROR; 985} 986 987static svn_error_t * 988delete_node_property(void *baton, 989 const char *name) 990{ 991 struct node_baton *nb = baton; 992 apr_pool_t *pool = nb->rb->pool; 993 svn_prop_t *prop; 994 995 SVN_ERR(svn_repos__validate_prop(name, NULL, pool)); 996 997 prop = apr_palloc(pool, sizeof (*prop)); 998 prop->name = apr_pstrdup(pool, name); 999 prop->value = NULL; 1000 svn_hash_sets(nb->prop_changes, prop->name, prop); 1001 1002 return SVN_NO_ERROR; 1003} 1004 1005/* Delete all the properties of the node, if any. 1006 * 1007 * The commit editor doesn't have a method to delete a node's properties 1008 * without knowing what they are, so we have to first find out what 1009 * properties the node would have had. If it's copied (explicitly or 1010 * implicitly), we look at the copy source. If it's only being changed, 1011 * we look at the node's current path in the head revision. 1012 */ 1013static svn_error_t * 1014remove_node_props(void *baton) 1015{ 1016 struct node_baton *nb = baton; 1017 struct revision_baton *rb = nb->rb; 1018 apr_pool_t *pool = nb->rb->pool; 1019 apr_hash_index_t *hi; 1020 apr_hash_t *props; 1021 const char *orig_path; 1022 svn_revnum_t orig_rev; 1023 1024 /* Find the path and revision that has the node's original properties */ 1025 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev)) 1026 { 1027 LDR_DBG(("using nb->copyfrom %s@%ld", nb->copyfrom_path, nb->copyfrom_rev)); 1028 orig_path = nb->copyfrom_path; 1029 orig_rev = nb->copyfrom_rev; 1030 } 1031 else if (!nb->is_added 1032 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 1033 { 1034 /* If this is a dir, then it's described by rb->db; 1035 if this is a file, then it's a child of the dir in rb->db. */ 1036 LDR_DBG(("using rb->db->copyfrom (k=%d) %s@%ld", 1037 nb->kind, rb->db->copyfrom_path, rb->db->copyfrom_rev)); 1038 orig_path = (nb->kind == svn_node_dir) 1039 ? rb->db->copyfrom_path 1040 : svn_relpath_join(rb->db->copyfrom_path, 1041 svn_relpath_basename(nb->path, NULL), 1042 rb->pool); 1043 orig_rev = rb->db->copyfrom_rev; 1044 } 1045 else 1046 { 1047 LDR_DBG(("using self.path@head %s@%ld", nb->path, SVN_INVALID_REVNUM)); 1048 /* ### Should we query at a known, fixed, "head" revision number 1049 instead of passing SVN_INVALID_REVNUM and getting a moving target? */ 1050 orig_path = nb->path; 1051 orig_rev = SVN_INVALID_REVNUM; 1052 } 1053 LDR_DBG(("Trying %s@%ld", orig_path, orig_rev)); 1054 1055 if ((nb->action == svn_node_action_add 1056 || nb->action == svn_node_action_replace) 1057 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev)) 1058 /* Add-without-history; no "old" properties to worry about. */ 1059 return SVN_NO_ERROR; 1060 1061 if (nb->kind == svn_node_file) 1062 { 1063 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, 1064 orig_path, orig_rev, NULL, NULL, &props, pool)); 1065 } 1066 else /* nb->kind == svn_node_dir */ 1067 { 1068 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props, 1069 orig_path, orig_rev, 0, pool)); 1070 } 1071 1072 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 1073 { 1074 const char *name = svn__apr_hash_index_key(hi); 1075 svn_prop_kind_t kind = svn_property_kind2(name); 1076 1077 if (kind == svn_prop_regular_kind) 1078 SVN_ERR(set_node_property(nb, name, NULL)); 1079 } 1080 1081 return SVN_NO_ERROR; 1082} 1083 1084static svn_error_t * 1085set_fulltext(svn_stream_t **stream, 1086 void *node_baton) 1087{ 1088 struct node_baton *nb = node_baton; 1089 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1090 svn_txdelta_window_handler_t handler; 1091 void *handler_baton; 1092 apr_pool_t *pool = nb->rb->pool; 1093 1094 LDR_DBG(("Setting fulltext for %p\n", nb->file_baton)); 1095 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 1096 pool, &handler, &handler_baton)); 1097 *stream = svn_txdelta_target_push(handler, handler_baton, 1098 svn_stream_empty(pool), pool); 1099 return SVN_NO_ERROR; 1100} 1101 1102static svn_error_t * 1103apply_textdelta(svn_txdelta_window_handler_t *handler, 1104 void **handler_baton, 1105 void *node_baton) 1106{ 1107 struct node_baton *nb = node_baton; 1108 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1109 apr_pool_t *pool = nb->rb->pool; 1110 1111 LDR_DBG(("Applying textdelta to %p\n", nb->file_baton)); 1112 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 1113 pool, handler, handler_baton)); 1114 1115 return SVN_NO_ERROR; 1116} 1117 1118static svn_error_t * 1119close_node(void *baton) 1120{ 1121 struct node_baton *nb = baton; 1122 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1123 apr_pool_t *pool = nb->rb->pool; 1124 apr_hash_index_t *hi; 1125 1126 for (hi = apr_hash_first(pool, nb->prop_changes); 1127 hi; hi = apr_hash_next(hi)) 1128 { 1129 const char *name = svn__apr_hash_index_key(hi); 1130 svn_prop_t *prop = svn__apr_hash_index_val(hi); 1131 1132 switch (nb->kind) 1133 { 1134 case svn_node_file: 1135 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, 1136 name, prop->value, pool)); 1137 break; 1138 case svn_node_dir: 1139 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, 1140 name, prop->value, pool)); 1141 break; 1142 default: 1143 break; 1144 } 1145 } 1146 1147 /* Pass a file node closure through to the editor *unless* we 1148 deleted the file (which doesn't require us to open it). */ 1149 if ((nb->kind == svn_node_file) && (nb->file_baton)) 1150 { 1151 LDR_DBG(("Closing file %p\n", nb->file_baton)); 1152 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool)); 1153 } 1154 1155 /* The svn_node_dir case is handled in close_revision */ 1156 1157 return SVN_NO_ERROR; 1158} 1159 1160static svn_error_t * 1161close_revision(void *baton) 1162{ 1163 struct revision_baton *rb = baton; 1164 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 1165 void *commit_edit_baton = rb->pb->commit_edit_baton; 1166 svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 1167 1168 /* Fake revision 0 */ 1169 if (rb->rev == 0) 1170 { 1171 /* ### Don't print directly; generate a notification. */ 1172 if (! rb->pb->quiet) 1173 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n")); 1174 } 1175 else if (commit_editor) 1176 { 1177 /* Close all pending open directories, and then close the edit 1178 session itself */ 1179 while (rb->db && rb->db->parent) 1180 { 1181 LDR_DBG(("Closing dir %p\n", rb->db->baton)); 1182 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 1183 rb->db = rb->db->parent; 1184 } 1185 /* root dir's baton */ 1186 LDR_DBG(("Closing edit on %p\n", commit_edit_baton)); 1187 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 1188 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 1189 } 1190 else 1191 { 1192 void *child_baton; 1193 1194 /* Legitimate revision with no node information */ 1195 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 1196 &commit_edit_baton, rb->revprop_table, 1197 commit_callback, baton, 1198 NULL, FALSE, rb->pool)); 1199 1200 SVN_ERR(commit_editor->open_root(commit_edit_baton, 1201 rb->rev - rb->rev_offset - 1, 1202 rb->pool, &child_baton)); 1203 1204 LDR_DBG(("Opened root %p\n", child_baton)); 1205 LDR_DBG(("Closing edit on %p\n", commit_edit_baton)); 1206 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool)); 1207 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 1208 } 1209 1210 /* svn_fs_commit_txn() rewrites the datestamp and author properties; 1211 we'll rewrite them again by hand after closing the commit_editor. 1212 The only time we don't do this is for revision 0 when loaded into 1213 a non-empty repository. */ 1214 if (rb->rev > 0) 1215 { 1216 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev); 1217 } 1218 else if (rb->rev_offset == -1) 1219 { 1220 committed_rev = 0; 1221 } 1222 1223 if (SVN_IS_VALID_REVNUM(committed_rev)) 1224 { 1225 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE, 1226 rb->datestamp, rb->pool)); 1227 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1228 SVN_PROP_REVISION_DATE, 1229 NULL, rb->datestamp, rb->pool)); 1230 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR, 1231 rb->author, rb->pool)); 1232 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1233 SVN_PROP_REVISION_AUTHOR, 1234 NULL, rb->author, rb->pool)); 1235 } 1236 1237 svn_pool_destroy(rb->pool); 1238 1239 return SVN_NO_ERROR; 1240} 1241 1242svn_error_t * 1243svn_rdump__load_dumpstream(svn_stream_t *stream, 1244 svn_ra_session_t *session, 1245 svn_ra_session_t *aux_session, 1246 svn_boolean_t quiet, 1247 svn_cancel_func_t cancel_func, 1248 void *cancel_baton, 1249 apr_pool_t *pool) 1250{ 1251 svn_repos_parse_fns3_t *parser; 1252 struct parse_baton *parse_baton; 1253 const svn_string_t *lock_string; 1254 svn_boolean_t be_atomic; 1255 svn_error_t *err; 1256 const char *session_url, *root_url, *parent_dir; 1257 1258 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 1259 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 1260 pool)); 1261 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool)); 1262 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool)); 1263 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); 1264 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir, 1265 session_url, pool)); 1266 1267 parser = apr_pcalloc(pool, sizeof(*parser)); 1268 parser->magic_header_record = magic_header_record; 1269 parser->uuid_record = uuid_record; 1270 parser->new_revision_record = new_revision_record; 1271 parser->new_node_record = new_node_record; 1272 parser->set_revision_property = set_revision_property; 1273 parser->set_node_property = set_node_property; 1274 parser->delete_node_property = delete_node_property; 1275 parser->remove_node_props = remove_node_props; 1276 parser->set_fulltext = set_fulltext; 1277 parser->apply_textdelta = apply_textdelta; 1278 parser->close_node = close_node; 1279 parser->close_revision = close_revision; 1280 1281 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton)); 1282 parse_baton->session = session; 1283 parse_baton->aux_session = aux_session; 1284 parse_baton->quiet = quiet; 1285 parse_baton->root_url = root_url; 1286 parse_baton->parent_dir = parent_dir; 1287 parse_baton->rev_map = apr_hash_make(pool); 1288 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM; 1289 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM; 1290 1291 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE, 1292 cancel_func, cancel_baton, pool); 1293 1294 /* If all goes well, or if we're cancelled cleanly, don't leave a 1295 stray lock behind. */ 1296 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED))) 1297 err = svn_error_compose_create( 1298 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK, 1299 lock_string, pool), 1300 err); 1301 return err; 1302} 1303