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 47/** 48 * General baton used by the parser functions. 49 */ 50struct parse_baton 51{ 52 /* Commit editor and baton used to transfer loaded revisions to 53 the target repository. */ 54 const svn_delta_editor_t *commit_editor; 55 void *commit_edit_baton; 56 57 /* RA session(s) for committing to the target repository. */ 58 svn_ra_session_t *session; 59 svn_ra_session_t *aux_session; 60 61 /* To bleep, or not to bleep? (What kind of question is that?) */ 62 svn_boolean_t quiet; 63 64 /* Root URL of the target repository. */ 65 const char *root_url; 66 67 /* The "parent directory" of the target repository in which to load. 68 (This is essentially the difference between ROOT_URL and 69 SESSION's url, and roughly equivalent to the 'svnadmin load 70 --parent-dir' option.) */ 71 const char *parent_dir; 72 73 /* A mapping of svn_revnum_t * dump stream revisions to their 74 corresponding svn_revnum_t * target repository revisions. */ 75 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903 76 ### for discussion about improving the memory costs of this mapping. */ 77 apr_hash_t *rev_map; 78 79 /* The most recent (youngest) revision from the dump stream mapped in 80 REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */ 81 svn_revnum_t last_rev_mapped; 82 83 /* The oldest revision loaded from the dump stream, or 84 SVN_INVALID_REVNUM if none have been loaded. */ 85 svn_revnum_t oldest_dumpstream_rev; 86 87 /* An hash containing specific revision properties to skip while 88 loading. */ 89 apr_hash_t *skip_revprops; 90}; 91 92/** 93 * Use to wrap the dir_context_t in commit.c so we can keep track of 94 * relpath and parent for open_directory and close_directory. 95 */ 96struct directory_baton 97{ 98 void *baton; 99 const char *relpath; 100 101 /* The copy-from source of this directory, no matter whether it is 102 copied explicitly (the root node of a copy) or implicitly (being an 103 existing child of a copied directory). For a node that is newly 104 added (without history), even inside a copied parent, these are 105 NULL and SVN_INVALID_REVNUM. */ 106 const char *copyfrom_path; 107 svn_revnum_t copyfrom_rev; 108 109 struct directory_baton *parent; 110}; 111 112/** 113 * Baton used to represent a node; to be used by the parser 114 * functions. Contains a link to the revision baton. 115 */ 116struct node_baton 117{ 118 const char *path; 119 svn_node_kind_t kind; 120 enum svn_node_action action; 121 122 /* Is this directory explicitly added? If not, then it already existed 123 or is a child of a copy. */ 124 svn_boolean_t is_added; 125 126 svn_revnum_t copyfrom_rev; 127 const char *copyfrom_path; 128 const char *copyfrom_url; 129 130 void *file_baton; 131 const char *base_checksum; 132 133 /* (const char *name) -> (svn_prop_t *) */ 134 apr_hash_t *prop_changes; 135 136 struct revision_baton *rb; 137}; 138 139/** 140 * Baton used to represet a revision; used by the parser 141 * functions. Contains a link to the parser baton. 142 */ 143struct revision_baton 144{ 145 svn_revnum_t rev; 146 apr_hash_t *revprop_table; 147 apr_int32_t rev_offset; 148 149 const svn_string_t *datestamp; 150 const svn_string_t *author; 151 152 struct parse_baton *pb; 153 struct directory_baton *db; 154 apr_pool_t *pool; 155}; 156 157 158 159/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that 160 anything added to the hash is allocated in the hash's pool. */ 161static void 162set_revision_mapping(apr_hash_t *rev_map, 163 svn_revnum_t from_rev, 164 svn_revnum_t to_rev) 165{ 166 svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map), 167 sizeof(svn_revnum_t) * 2); 168 mapped_revs[0] = from_rev; 169 mapped_revs[1] = to_rev; 170 apr_hash_set(rev_map, mapped_revs, 171 sizeof(svn_revnum_t), mapped_revs + 1); 172} 173 174/* Return the revision to which FROM_REV maps in REV_MAP, or 175 SVN_INVALID_REVNUM if no such mapping exists. */ 176static svn_revnum_t 177get_revision_mapping(apr_hash_t *rev_map, 178 svn_revnum_t from_rev) 179{ 180 svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev, 181 sizeof(from_rev)); 182 return to_rev ? *to_rev : SVN_INVALID_REVNUM; 183} 184 185 186static svn_error_t * 187commit_callback(const svn_commit_info_t *commit_info, 188 void *baton, 189 apr_pool_t *pool) 190{ 191 struct revision_baton *rb = baton; 192 struct parse_baton *pb = rb->pb; 193 194 /* ### Don't print directly; generate a notification. */ 195 if (! pb->quiet) 196 SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n", 197 commit_info->revision)); 198 199 /* Add the mapping of the dumpstream revision to the committed revision. */ 200 set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision); 201 202 /* If the incoming dump stream has non-contiguous revisions (e.g. from 203 using svndumpfilter --drop-empty-revs without --renumber-revs) then 204 we must account for the missing gaps in PB->REV_MAP. Otherwise we 205 might not be able to map all mergeinfo source revisions to the correct 206 revisions in the target repos. */ 207 if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) 208 && (rb->rev != pb->last_rev_mapped + 1)) 209 { 210 svn_revnum_t i; 211 212 for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) 213 { 214 set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); 215 } 216 } 217 218 /* Update our "last revision mapped". */ 219 pb->last_rev_mapped = rb->rev; 220 221 return SVN_NO_ERROR; 222} 223 224/* Implements `svn_ra__lock_retry_func_t'. */ 225static svn_error_t * 226lock_retry_func(void *baton, 227 const svn_string_t *reposlocktoken, 228 apr_pool_t *pool) 229{ 230 return svn_cmdline_printf(pool, 231 _("Failed to get lock on destination " 232 "repos, currently held by '%s'\n"), 233 reposlocktoken->data); 234} 235 236 237static svn_error_t * 238fetch_base_func(const char **filename, 239 void *baton, 240 const char *path, 241 svn_revnum_t base_revision, 242 apr_pool_t *result_pool, 243 apr_pool_t *scratch_pool) 244{ 245 struct revision_baton *rb = baton; 246 svn_stream_t *fstream; 247 svn_error_t *err; 248 249 if (! SVN_IS_VALID_REVNUM(base_revision)) 250 base_revision = rb->rev - 1; 251 252 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 253 svn_io_file_del_on_pool_cleanup, 254 result_pool, scratch_pool)); 255 256 err = svn_ra_get_file(rb->pb->aux_session, path, base_revision, 257 fstream, NULL, NULL, scratch_pool); 258 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 259 { 260 svn_error_clear(err); 261 SVN_ERR(svn_stream_close(fstream)); 262 263 *filename = NULL; 264 return SVN_NO_ERROR; 265 } 266 else if (err) 267 return svn_error_trace(err); 268 269 SVN_ERR(svn_stream_close(fstream)); 270 271 return SVN_NO_ERROR; 272} 273 274static svn_error_t * 275fetch_props_func(apr_hash_t **props, 276 void *baton, 277 const char *path, 278 svn_revnum_t base_revision, 279 apr_pool_t *result_pool, 280 apr_pool_t *scratch_pool) 281{ 282 struct revision_baton *rb = baton; 283 svn_node_kind_t node_kind; 284 285 if (! SVN_IS_VALID_REVNUM(base_revision)) 286 base_revision = rb->rev - 1; 287 288 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 289 &node_kind, scratch_pool)); 290 291 if (node_kind == svn_node_file) 292 { 293 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision, 294 NULL, NULL, props, result_pool)); 295 } 296 else if (node_kind == svn_node_dir) 297 { 298 apr_array_header_t *tmp_props; 299 300 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path, 301 base_revision, 0 /* Dirent fields */, 302 result_pool)); 303 tmp_props = svn_prop_hash_to_array(*props, result_pool); 304 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 305 result_pool)); 306 *props = svn_prop_array_to_hash(tmp_props, result_pool); 307 } 308 else 309 { 310 *props = apr_hash_make(result_pool); 311 } 312 313 return SVN_NO_ERROR; 314} 315 316static svn_error_t * 317fetch_kind_func(svn_node_kind_t *kind, 318 void *baton, 319 const char *path, 320 svn_revnum_t base_revision, 321 apr_pool_t *scratch_pool) 322{ 323 struct revision_baton *rb = baton; 324 325 if (! SVN_IS_VALID_REVNUM(base_revision)) 326 base_revision = rb->rev - 1; 327 328 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 329 kind, scratch_pool)); 330 331 return SVN_NO_ERROR; 332} 333 334static svn_delta_shim_callbacks_t * 335get_shim_callbacks(struct revision_baton *rb, 336 apr_pool_t *pool) 337{ 338 svn_delta_shim_callbacks_t *callbacks = 339 svn_delta_shim_callbacks_default(pool); 340 341 callbacks->fetch_props_func = fetch_props_func; 342 callbacks->fetch_kind_func = fetch_kind_func; 343 callbacks->fetch_base_func = fetch_base_func; 344 callbacks->fetch_baton = rb; 345 346 return callbacks; 347} 348 349/* Acquire a lock (of sorts) on the repository associated with the 350 * given RA SESSION. This lock is just a revprop change attempt in a 351 * time-delay loop. This function is duplicated by svnsync in 352 * svnsync/svnsync.c 353 * 354 * ### TODO: Make this function more generic and 355 * expose it through a header for use by other Subversion 356 * applications to avoid duplication. 357 */ 358static svn_error_t * 359get_lock(const svn_string_t **lock_string_p, 360 svn_ra_session_t *session, 361 svn_cancel_func_t cancel_func, 362 void *cancel_baton, 363 apr_pool_t *pool) 364{ 365 svn_boolean_t be_atomic; 366 367 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 368 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 369 pool)); 370 if (! be_atomic) 371 { 372 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */ 373 svn_error_t *err = 374 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 375 _("Target server does not support atomic revision " 376 "property edits; consider upgrading it to 1.7.")); 377 svn_handle_warning2(stderr, err, "svnrdump: "); 378 svn_error_clear(err); 379 } 380 381 return svn_ra__get_operational_lock(lock_string_p, NULL, session, 382 SVNRDUMP_PROP_LOCK, FALSE, 383 10 /* retries */, lock_retry_func, NULL, 384 cancel_func, cancel_baton, pool); 385} 386 387static svn_error_t * 388new_revision_record(void **revision_baton, 389 apr_hash_t *headers, 390 void *parse_baton, 391 apr_pool_t *pool) 392{ 393 struct revision_baton *rb; 394 struct parse_baton *pb; 395 apr_hash_index_t *hi; 396 svn_revnum_t head_rev; 397 398 rb = apr_pcalloc(pool, sizeof(*rb)); 399 pb = parse_baton; 400 rb->pool = svn_pool_create(pool); 401 rb->pb = pb; 402 rb->db = NULL; 403 404 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi)) 405 { 406 const char *hname = apr_hash_this_key(hi); 407 const char *hval = apr_hash_this_val(hi); 408 409 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0) 410 rb->rev = atoi(hval); 411 } 412 413 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool)); 414 415 /* FIXME: This is a lame fallback loading multiple segments of dump in 416 several separate operations. It is highly susceptible to race conditions. 417 Calculate the revision 'offset' for finding copyfrom sources. 418 It might be positive or negative. */ 419 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1)); 420 421 /* Stash the oldest (non-zero) dumpstream revision seen. */ 422 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))) 423 pb->oldest_dumpstream_rev = rb->rev; 424 425 /* Set the commit_editor/ commit_edit_baton to NULL and wait for 426 them to be created in new_node_record */ 427 rb->pb->commit_editor = NULL; 428 rb->pb->commit_edit_baton = NULL; 429 rb->revprop_table = apr_hash_make(rb->pool); 430 431 *revision_baton = rb; 432 return SVN_NO_ERROR; 433} 434 435static svn_error_t * 436magic_header_record(int version, 437 void *parse_baton, 438 apr_pool_t *pool) 439{ 440 return SVN_NO_ERROR; 441} 442 443static svn_error_t * 444uuid_record(const char *uuid, 445 void *parse_baton, 446 apr_pool_t *pool) 447{ 448 return SVN_NO_ERROR; 449} 450 451/* Push information about another directory onto the linked list RB->db. 452 * 453 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the 454 * repository-relative path of this directory. IS_ADDED is true iff this 455 * directory is being added (with or without history). If added with 456 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else 457 * are NULL/SVN_INVALID_REVNUM. 458 */ 459static void 460push_directory(struct revision_baton *rb, 461 void *child_baton, 462 const char *relpath, 463 svn_boolean_t is_added, 464 const char *copyfrom_path, 465 svn_revnum_t copyfrom_rev) 466{ 467 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db)); 468 469 SVN_ERR_ASSERT_NO_RETURN( 470 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM)); 471 472 /* If this node is an existing (not newly added) child of a copied node, 473 calculate where it was copied from. */ 474 if (!is_added 475 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 476 { 477 const char *name = svn_relpath_basename(relpath, NULL); 478 479 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name, 480 rb->pool); 481 copyfrom_rev = rb->db->copyfrom_rev; 482 } 483 484 child_db->baton = child_baton; 485 child_db->relpath = relpath; 486 child_db->copyfrom_path = copyfrom_path; 487 child_db->copyfrom_rev = copyfrom_rev; 488 child_db->parent = rb->db; 489 rb->db = child_db; 490} 491 492static svn_error_t * 493new_node_record(void **node_baton, 494 apr_hash_t *headers, 495 void *revision_baton, 496 apr_pool_t *pool) 497{ 498 struct revision_baton *rb = revision_baton; 499 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 500 void *commit_edit_baton = rb->pb->commit_edit_baton; 501 struct node_baton *nb; 502 apr_hash_index_t *hi; 503 void *child_baton; 504 const char *nb_dirname; 505 506 nb = apr_pcalloc(rb->pool, sizeof(*nb)); 507 nb->rb = rb; 508 nb->is_added = FALSE; 509 nb->copyfrom_path = NULL; 510 nb->copyfrom_url = NULL; 511 nb->copyfrom_rev = SVN_INVALID_REVNUM; 512 nb->prop_changes = apr_hash_make(rb->pool); 513 514 /* If the creation of commit_editor is pending, create it now and 515 open_root on it; also create a top-level directory baton. */ 516 517 if (!commit_editor) 518 { 519 /* The revprop_table should have been filled in with important 520 information like svn:log in set_revision_property. We can now 521 use it all this information to create our commit_editor. But 522 first, clear revprops that we aren't allowed to set with the 523 commit_editor. We'll set them separately using the RA API 524 after closing the editor (see close_revision). */ 525 526 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL); 527 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL); 528 529 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session, 530 get_shim_callbacks(rb, rb->pool))); 531 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 532 &commit_edit_baton, rb->revprop_table, 533 commit_callback, revision_baton, 534 NULL, FALSE, rb->pool)); 535 536 rb->pb->commit_editor = commit_editor; 537 rb->pb->commit_edit_baton = commit_edit_baton; 538 539 SVN_ERR(commit_editor->open_root(commit_edit_baton, 540 rb->rev - rb->rev_offset - 1, 541 rb->pool, &child_baton)); 542 543 /* child_baton corresponds to the root directory baton here */ 544 push_directory(rb, child_baton, "", TRUE /*is_added*/, 545 NULL, SVN_INVALID_REVNUM); 546 } 547 548 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi)) 549 { 550 const char *hname = apr_hash_this_key(hi); 551 const char *hval = apr_hash_this_val(hi); 552 553 /* Parse the different kinds of headers we can encounter and 554 stuff them into the node_baton for writing later */ 555 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0) 556 nb->path = apr_pstrdup(rb->pool, hval); 557 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0) 558 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir; 559 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0) 560 { 561 if (strcmp(hval, "add") == 0) 562 nb->action = svn_node_action_add; 563 if (strcmp(hval, "change") == 0) 564 nb->action = svn_node_action_change; 565 if (strcmp(hval, "delete") == 0) 566 nb->action = svn_node_action_delete; 567 if (strcmp(hval, "replace") == 0) 568 nb->action = svn_node_action_replace; 569 } 570 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0) 571 nb->base_checksum = apr_pstrdup(rb->pool, hval); 572 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0) 573 nb->copyfrom_rev = atoi(hval); 574 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0) 575 nb->copyfrom_path = apr_pstrdup(rb->pool, hval); 576 } 577 578 /* Before handling the new node, ensure depth-first editing order by 579 traversing the directory hierarchy from the old node's to the new 580 node's parent directory. */ 581 nb_dirname = svn_relpath_dirname(nb->path, pool); 582 if (svn_path_compare_paths(nb_dirname, 583 rb->db->relpath) != 0) 584 { 585 char *ancestor_path; 586 apr_size_t residual_close_count; 587 apr_array_header_t *residual_open_path; 588 int i; 589 apr_size_t n; 590 591 ancestor_path = 592 svn_relpath_get_longest_ancestor(nb_dirname, 593 rb->db->relpath, pool); 594 residual_close_count = 595 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path, 596 rb->db->relpath)); 597 residual_open_path = 598 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path, 599 nb_dirname), pool); 600 601 /* First close all as many directories as there are after 602 skip_ancestor, and then open fresh directories */ 603 for (n = 0; n < residual_close_count; n ++) 604 { 605 /* Don't worry about destroying the actual rb->db object, 606 since the pool we're using has the lifetime of one 607 revision anyway */ 608 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 609 rb->db = rb->db->parent; 610 } 611 612 for (i = 0; i < residual_open_path->nelts; i ++) 613 { 614 char *relpath_compose = 615 svn_relpath_join(rb->db->relpath, 616 APR_ARRAY_IDX(residual_open_path, i, const char *), 617 rb->pool); 618 SVN_ERR(commit_editor->open_directory(relpath_compose, 619 rb->db->baton, 620 rb->rev - rb->rev_offset - 1, 621 rb->pool, &child_baton)); 622 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/, 623 NULL, SVN_INVALID_REVNUM); 624 } 625 } 626 627 /* Fix up the copyfrom information in light of mapped revisions and 628 non-root load targets, and convert copyfrom path into a full 629 URL. */ 630 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev)) 631 { 632 svn_revnum_t copyfrom_rev; 633 634 /* Try to find the copyfrom revision in the revision map; 635 failing that, fall back to the revision offset approach. */ 636 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev); 637 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 638 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset; 639 640 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 641 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 642 _("Relative source revision %ld is not" 643 " available in current repository"), 644 copyfrom_rev); 645 646 nb->copyfrom_rev = copyfrom_rev; 647 648 if (rb->pb->parent_dir) 649 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, 650 nb->copyfrom_path, rb->pool); 651 /* Convert to a URL, as the commit editor requires. */ 652 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url, 653 nb->copyfrom_path, 654 rb->pool); 655 } 656 657 658 switch (nb->action) 659 { 660 case svn_node_action_delete: 661 case svn_node_action_replace: 662 SVN_ERR(commit_editor->delete_entry(nb->path, 663 rb->rev - rb->rev_offset - 1, 664 rb->db->baton, rb->pool)); 665 if (nb->action == svn_node_action_delete) 666 break; 667 else 668 /* FALL THROUGH */; 669 case svn_node_action_add: 670 nb->is_added = TRUE; 671 switch (nb->kind) 672 { 673 case svn_node_file: 674 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton, 675 nb->copyfrom_url, 676 nb->copyfrom_rev, 677 rb->pool, &(nb->file_baton))); 678 break; 679 case svn_node_dir: 680 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton, 681 nb->copyfrom_url, 682 nb->copyfrom_rev, 683 rb->pool, &child_baton)); 684 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/, 685 nb->copyfrom_path, nb->copyfrom_rev); 686 break; 687 default: 688 break; 689 } 690 break; 691 case svn_node_action_change: 692 switch (nb->kind) 693 { 694 case svn_node_file: 695 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton, 696 SVN_INVALID_REVNUM, rb->pool, 697 &(nb->file_baton))); 698 break; 699 default: 700 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton, 701 rb->rev - rb->rev_offset - 1, 702 rb->pool, &child_baton)); 703 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/, 704 NULL, SVN_INVALID_REVNUM); 705 break; 706 } 707 break; 708 } 709 710 *node_baton = nb; 711 return SVN_NO_ERROR; 712} 713 714static svn_error_t * 715set_revision_property(void *baton, 716 const char *name, 717 const svn_string_t *value) 718{ 719 struct revision_baton *rb = baton; 720 721 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool)); 722 723 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool)); 724 725 if (rb->rev > 0) 726 { 727 if (! svn_hash_gets(rb->pb->skip_revprops, name)) 728 svn_hash_sets(rb->revprop_table, 729 apr_pstrdup(rb->pool, name), 730 svn_string_dup(value, rb->pool)); 731 } 732 else if (rb->rev_offset == -1 733 && ! svn_hash_gets(rb->pb->skip_revprops, name)) 734 { 735 /* Special case: set revision 0 properties directly (which is 736 safe because the commit_editor hasn't been created yet), but 737 only when loading into an 'empty' filesystem. */ 738 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0, 739 name, NULL, value, rb->pool)); 740 } 741 742 /* Remember any datestamp/ author that passes through (see comment 743 in close_revision). */ 744 if (!strcmp(name, SVN_PROP_REVISION_DATE)) 745 rb->datestamp = svn_string_dup(value, rb->pool); 746 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR)) 747 rb->author = svn_string_dup(value, rb->pool); 748 749 return SVN_NO_ERROR; 750} 751 752static svn_error_t * 753set_node_property(void *baton, 754 const char *name, 755 const svn_string_t *value) 756{ 757 struct node_baton *nb = baton; 758 struct revision_baton *rb = nb->rb; 759 struct parse_baton *pb = rb->pb; 760 apr_pool_t *pool = nb->rb->pool; 761 svn_prop_t *prop; 762 763 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0) 764 { 765 svn_string_t *new_value; 766 svn_error_t *err; 767 768 err = svn_repos__adjust_mergeinfo_property(&new_value, value, 769 pb->parent_dir, 770 pb->rev_map, 771 pb->oldest_dumpstream_rev, 772 rb->rev_offset, 773 NULL, NULL, /*notify*/ 774 pool, pool); 775 if (err) 776 { 777 return svn_error_quick_wrap(err, 778 _("Invalid svn:mergeinfo value")); 779 } 780 781 value = new_value; 782 } 783 784 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool)); 785 786 SVN_ERR(svn_repos__validate_prop(name, value, pool)); 787 788 prop = apr_palloc(nb->rb->pool, sizeof (*prop)); 789 prop->name = apr_pstrdup(pool, name); 790 prop->value = svn_string_dup(value, pool); 791 svn_hash_sets(nb->prop_changes, prop->name, prop); 792 793 return SVN_NO_ERROR; 794} 795 796static svn_error_t * 797delete_node_property(void *baton, 798 const char *name) 799{ 800 struct node_baton *nb = baton; 801 apr_pool_t *pool = nb->rb->pool; 802 svn_prop_t *prop; 803 804 SVN_ERR(svn_repos__validate_prop(name, NULL, pool)); 805 806 prop = apr_palloc(pool, sizeof (*prop)); 807 prop->name = apr_pstrdup(pool, name); 808 prop->value = NULL; 809 svn_hash_sets(nb->prop_changes, prop->name, prop); 810 811 return SVN_NO_ERROR; 812} 813 814/* Delete all the properties of the node, if any. 815 * 816 * The commit editor doesn't have a method to delete a node's properties 817 * without knowing what they are, so we have to first find out what 818 * properties the node would have had. If it's copied (explicitly or 819 * implicitly), we look at the copy source. If it's only being changed, 820 * we look at the node's current path in the head revision. 821 */ 822static svn_error_t * 823remove_node_props(void *baton) 824{ 825 struct node_baton *nb = baton; 826 struct revision_baton *rb = nb->rb; 827 apr_pool_t *pool = nb->rb->pool; 828 apr_hash_index_t *hi; 829 apr_hash_t *props; 830 const char *orig_path; 831 svn_revnum_t orig_rev; 832 833 /* Find the path and revision that has the node's original properties */ 834 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev)) 835 { 836 orig_path = nb->copyfrom_path; 837 orig_rev = nb->copyfrom_rev; 838 } 839 else if (!nb->is_added 840 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 841 { 842 /* If this is a dir, then it's described by rb->db; 843 if this is a file, then it's a child of the dir in rb->db. */ 844 orig_path = (nb->kind == svn_node_dir) 845 ? rb->db->copyfrom_path 846 : svn_relpath_join(rb->db->copyfrom_path, 847 svn_relpath_basename(nb->path, NULL), 848 rb->pool); 849 orig_rev = rb->db->copyfrom_rev; 850 } 851 else 852 { 853 /* ### Should we query at a known, fixed, "head" revision number 854 instead of passing SVN_INVALID_REVNUM and getting a moving target? */ 855 orig_path = nb->path; 856 orig_rev = SVN_INVALID_REVNUM; 857 } 858 859 if ((nb->action == svn_node_action_add 860 || nb->action == svn_node_action_replace) 861 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev)) 862 /* Add-without-history; no "old" properties to worry about. */ 863 return SVN_NO_ERROR; 864 865 if (nb->kind == svn_node_file) 866 { 867 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, 868 orig_path, orig_rev, NULL, NULL, &props, pool)); 869 } 870 else /* nb->kind == svn_node_dir */ 871 { 872 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props, 873 orig_path, orig_rev, 0, pool)); 874 } 875 876 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 877 { 878 const char *name = apr_hash_this_key(hi); 879 svn_prop_kind_t kind = svn_property_kind2(name); 880 881 if (kind == svn_prop_regular_kind) 882 SVN_ERR(set_node_property(nb, name, NULL)); 883 } 884 885 return SVN_NO_ERROR; 886} 887 888static svn_error_t * 889set_fulltext(svn_stream_t **stream, 890 void *node_baton) 891{ 892 struct node_baton *nb = node_baton; 893 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 894 svn_txdelta_window_handler_t handler; 895 void *handler_baton; 896 apr_pool_t *pool = nb->rb->pool; 897 898 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 899 pool, &handler, &handler_baton)); 900 *stream = svn_txdelta_target_push(handler, handler_baton, 901 svn_stream_empty(pool), pool); 902 return SVN_NO_ERROR; 903} 904 905static svn_error_t * 906apply_textdelta(svn_txdelta_window_handler_t *handler, 907 void **handler_baton, 908 void *node_baton) 909{ 910 struct node_baton *nb = node_baton; 911 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 912 apr_pool_t *pool = nb->rb->pool; 913 914 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 915 pool, handler, handler_baton)); 916 917 return SVN_NO_ERROR; 918} 919 920static svn_error_t * 921close_node(void *baton) 922{ 923 struct node_baton *nb = baton; 924 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 925 apr_pool_t *pool = nb->rb->pool; 926 apr_hash_index_t *hi; 927 928 for (hi = apr_hash_first(pool, nb->prop_changes); 929 hi; hi = apr_hash_next(hi)) 930 { 931 const char *name = apr_hash_this_key(hi); 932 svn_prop_t *prop = apr_hash_this_val(hi); 933 934 switch (nb->kind) 935 { 936 case svn_node_file: 937 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, 938 name, prop->value, pool)); 939 break; 940 case svn_node_dir: 941 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, 942 name, prop->value, pool)); 943 break; 944 default: 945 break; 946 } 947 } 948 949 /* Pass a file node closure through to the editor *unless* we 950 deleted the file (which doesn't require us to open it). */ 951 if ((nb->kind == svn_node_file) && (nb->file_baton)) 952 { 953 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool)); 954 } 955 956 /* The svn_node_dir case is handled in close_revision */ 957 958 return SVN_NO_ERROR; 959} 960 961static svn_error_t * 962close_revision(void *baton) 963{ 964 struct revision_baton *rb = baton; 965 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 966 void *commit_edit_baton = rb->pb->commit_edit_baton; 967 svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 968 969 /* Fake revision 0 */ 970 if (rb->rev == 0) 971 { 972 /* ### Don't print directly; generate a notification. */ 973 if (! rb->pb->quiet) 974 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n")); 975 } 976 else if (commit_editor) 977 { 978 /* Close all pending open directories, and then close the edit 979 session itself */ 980 while (rb->db && rb->db->parent) 981 { 982 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 983 rb->db = rb->db->parent; 984 } 985 /* root dir's baton */ 986 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 987 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 988 } 989 else 990 { 991 void *child_baton; 992 993 /* Legitimate revision with no node information */ 994 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 995 &commit_edit_baton, rb->revprop_table, 996 commit_callback, baton, 997 NULL, FALSE, rb->pool)); 998 999 SVN_ERR(commit_editor->open_root(commit_edit_baton, 1000 rb->rev - rb->rev_offset - 1, 1001 rb->pool, &child_baton)); 1002 1003 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool)); 1004 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 1005 } 1006 1007 /* svn_fs_commit_txn() rewrites the datestamp and author properties; 1008 we'll rewrite them again by hand after closing the commit_editor. 1009 The only time we don't do this is for revision 0 when loaded into 1010 a non-empty repository. */ 1011 if (rb->rev > 0) 1012 { 1013 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev); 1014 } 1015 else if (rb->rev_offset == -1) 1016 { 1017 committed_rev = 0; 1018 } 1019 1020 if (SVN_IS_VALID_REVNUM(committed_rev)) 1021 { 1022 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE)) 1023 { 1024 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE, 1025 rb->datestamp, rb->pool)); 1026 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1027 SVN_PROP_REVISION_DATE, 1028 NULL, rb->datestamp, rb->pool)); 1029 } 1030 if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR)) 1031 { 1032 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR, 1033 rb->author, rb->pool)); 1034 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1035 SVN_PROP_REVISION_AUTHOR, 1036 NULL, rb->author, rb->pool)); 1037 } 1038 } 1039 1040 svn_pool_destroy(rb->pool); 1041 1042 return SVN_NO_ERROR; 1043} 1044 1045svn_error_t * 1046svn_rdump__load_dumpstream(svn_stream_t *stream, 1047 svn_ra_session_t *session, 1048 svn_ra_session_t *aux_session, 1049 svn_boolean_t quiet, 1050 apr_hash_t *skip_revprops, 1051 svn_cancel_func_t cancel_func, 1052 void *cancel_baton, 1053 apr_pool_t *pool) 1054{ 1055 svn_repos_parse_fns3_t *parser; 1056 struct parse_baton *parse_baton; 1057 const svn_string_t *lock_string; 1058 svn_boolean_t be_atomic; 1059 svn_error_t *err; 1060 const char *session_url, *root_url, *parent_dir; 1061 1062 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 1063 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 1064 pool)); 1065 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool)); 1066 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool)); 1067 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); 1068 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir, 1069 session_url, pool)); 1070 1071 parser = apr_pcalloc(pool, sizeof(*parser)); 1072 parser->magic_header_record = magic_header_record; 1073 parser->uuid_record = uuid_record; 1074 parser->new_revision_record = new_revision_record; 1075 parser->new_node_record = new_node_record; 1076 parser->set_revision_property = set_revision_property; 1077 parser->set_node_property = set_node_property; 1078 parser->delete_node_property = delete_node_property; 1079 parser->remove_node_props = remove_node_props; 1080 parser->set_fulltext = set_fulltext; 1081 parser->apply_textdelta = apply_textdelta; 1082 parser->close_node = close_node; 1083 parser->close_revision = close_revision; 1084 1085 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton)); 1086 parse_baton->session = session; 1087 parse_baton->aux_session = aux_session; 1088 parse_baton->quiet = quiet; 1089 parse_baton->root_url = root_url; 1090 parse_baton->parent_dir = parent_dir; 1091 parse_baton->rev_map = apr_hash_make(pool); 1092 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM; 1093 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM; 1094 parse_baton->skip_revprops = skip_revprops; 1095 1096 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE, 1097 cancel_func, cancel_baton, pool); 1098 1099 /* If all goes well, or if we're cancelled cleanly, don't leave a 1100 stray lock behind. */ 1101 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED))) 1102 err = svn_error_compose_create( 1103 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK, 1104 lock_string, pool), 1105 err); 1106 return err; 1107} 1108