replay.c revision 289180
1/* 2 * replay.c: an editor driver for changes made in a given revision 3 * or transaction 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 26#include <apr_hash.h> 27 28#include "svn_types.h" 29#include "svn_delta.h" 30#include "svn_hash.h" 31#include "svn_fs.h" 32#include "svn_checksum.h" 33#include "svn_repos.h" 34#include "svn_sorts.h" 35#include "svn_props.h" 36#include "svn_pools.h" 37#include "svn_path.h" 38#include "svn_private_config.h" 39#include "private/svn_fspath.h" 40#include "private/svn_repos_private.h" 41#include "private/svn_delta_private.h" 42#include "private/svn_sorts_private.h" 43 44 45/*** Backstory ***/ 46 47/* The year was 2003. Subversion usage was rampant in the world, and 48 there was a rapidly growing issues database to prove it. To make 49 matters worse, svn_repos_dir_delta() had simply outgrown itself. 50 No longer content to simply describe the differences between two 51 trees, the function had been slowly bearing the added 52 responsibility of representing the actions that had been taken to 53 cause those differences -- a burden it was never meant to bear. 54 Now grown into a twisted mess of razor-sharp metal and glass, and 55 trembling with a sort of momentarily stayed spring force, 56 svn_repos_dir_delta was a timebomb poised for total annihilation of 57 the American Midwest. 58 59 Subversion needed a change. 60 61 Changes, in fact. And not just in the literary segue sense. What 62 Subversion desperately needed was a new mechanism solely 63 responsible for replaying repository actions back to some 64 interested party -- to translate and retransmit the contents of the 65 Berkeley 'changes' database file. */ 66 67/*** Overview ***/ 68 69/* The filesystem keeps a record of high-level actions that affect the 70 files and directories in itself. The 'changes' table records 71 additions, deletions, textual and property modifications, and so 72 on. The goal of the functions in this file is to examine those 73 change records, and use them to drive an editor interface in such a 74 way as to effectively replay those actions. 75 76 This is critically different than what svn_repos_dir_delta() was 77 designed to do. That function describes, in the simplest way it 78 can, how to transform one tree into another. It doesn't care 79 whether or not this was the same way a user might have done this 80 transformation. More to the point, it doesn't care if this is how 81 those differences *did* come into being. And it is for this reason 82 that it cannot be relied upon for tasks such as the repository 83 dumpfile-generation code, which is supposed to represent not 84 changes, but actions that cause changes. 85 86 So, what's the plan here? 87 88 First, we fetch the changes for a particular revision or 89 transaction. We get these as an array, sorted chronologically. 90 From this array we will build a hash, keyed on the path associated 91 with each change item, and whose values are arrays of changes made 92 to that path, again preserving the chronological ordering. 93 94 Once our hash is built, we then sort all the keys of the hash (the 95 paths) using a depth-first directory sort routine. 96 97 Finally, we drive an editor, moving down our list of sorted paths, 98 and manufacturing any intermediate editor calls (directory openings 99 and closures) needed to navigate between each successive path. For 100 each path, we replay the sorted actions that occurred at that path. 101 102 When we've finished the editor drive, we should have fully replayed 103 the filesystem events that occurred in that revision or transaction 104 (though not necessarily in the same order in which they 105 occurred). */ 106 107/* #define USE_EV2_IMPL */ 108 109 110/*** Helper functions. ***/ 111 112 113/* Information for an active copy, that is a directory which we are currently 114 working on and which was added with history. */ 115struct copy_info 116{ 117 /* Destination relpath (relative to the root of the . */ 118 const char *path; 119 120 /* Copy source path (expressed as an absolute FS path) or revision. 121 NULL and SVN_INVALID_REVNUM if this is an add without history, 122 nested inside an add with history. */ 123 const char *copyfrom_path; 124 svn_revnum_t copyfrom_rev; 125}; 126 127struct path_driver_cb_baton 128{ 129 const svn_delta_editor_t *editor; 130 void *edit_baton; 131 132 /* The root of the revision we're replaying. */ 133 svn_fs_root_t *root; 134 135 /* The root of the previous revision. If this is non-NULL it means that 136 we are supposed to generate props and text deltas relative to it. */ 137 svn_fs_root_t *compare_root; 138 139 apr_hash_t *changed_paths; 140 141 svn_repos_authz_func_t authz_read_func; 142 void *authz_read_baton; 143 144 const char *base_path; /* relpath */ 145 146 svn_revnum_t low_water_mark; 147 /* Stack of active copy operations. */ 148 apr_array_header_t *copies; 149 150 /* The global pool for this replay operation. */ 151 apr_pool_t *pool; 152}; 153 154/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting 155 the appropriate editor calls to add it and its children without any 156 history. This is meant to be used when either a subset of the tree 157 has been ignored and we need to copy something from that subset to 158 the part of the tree we do care about, or if a subset of the tree is 159 unavailable because of authz and we need to use it as the source of 160 a copy. */ 161static svn_error_t * 162add_subdir(svn_fs_root_t *source_root, 163 svn_fs_root_t *target_root, 164 const svn_delta_editor_t *editor, 165 void *edit_baton, 166 const char *edit_path, 167 void *parent_baton, 168 const char *source_fspath, 169 svn_repos_authz_func_t authz_read_func, 170 void *authz_read_baton, 171 apr_hash_t *changed_paths, 172 apr_pool_t *pool, 173 void **dir_baton) 174{ 175 apr_pool_t *subpool = svn_pool_create(pool); 176 apr_hash_index_t *hi, *phi; 177 apr_hash_t *dirents; 178 apr_hash_t *props; 179 180 SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL, 181 SVN_INVALID_REVNUM, pool, dir_baton)); 182 183 SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool)); 184 185 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 186 { 187 const char *key = apr_hash_this_key(phi); 188 svn_string_t *val = apr_hash_this_val(phi); 189 190 svn_pool_clear(subpool); 191 SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool)); 192 } 193 194 /* We have to get the dirents from the source path, not the target, 195 because we want nested copies from *readable* paths to be handled by 196 path_driver_cb_func, not add_subdir (in order to preserve history). */ 197 SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool)); 198 199 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 200 { 201 svn_fs_path_change2_t *change; 202 svn_boolean_t readable = TRUE; 203 svn_fs_dirent_t *dent = apr_hash_this_val(hi); 204 const char *copyfrom_path = NULL; 205 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 206 const char *new_edit_path; 207 208 svn_pool_clear(subpool); 209 210 new_edit_path = svn_relpath_join(edit_path, dent->name, subpool); 211 212 /* If a file or subdirectory of the copied directory is listed as a 213 changed path (because it was modified after the copy but before the 214 commit), we remove it from the changed_paths hash so that future 215 calls to path_driver_cb_func will ignore it. */ 216 change = svn_hash_gets(changed_paths, new_edit_path); 217 if (change) 218 { 219 svn_hash_sets(changed_paths, new_edit_path, NULL); 220 221 /* If it's a delete, skip this entry. */ 222 if (change->change_kind == svn_fs_path_change_delete) 223 continue; 224 225 /* If it's a replacement, check for copyfrom info (if we 226 don't have it already. */ 227 if (change->change_kind == svn_fs_path_change_replace) 228 { 229 if (! change->copyfrom_known) 230 { 231 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, 232 &change->copyfrom_path, 233 target_root, new_edit_path, pool)); 234 change->copyfrom_known = TRUE; 235 } 236 copyfrom_path = change->copyfrom_path; 237 copyfrom_rev = change->copyfrom_rev; 238 } 239 } 240 241 if (authz_read_func) 242 SVN_ERR(authz_read_func(&readable, target_root, new_edit_path, 243 authz_read_baton, pool)); 244 245 if (! readable) 246 continue; 247 248 if (dent->kind == svn_node_dir) 249 { 250 svn_fs_root_t *new_source_root; 251 const char *new_source_fspath; 252 void *new_dir_baton; 253 254 if (copyfrom_path) 255 { 256 svn_fs_t *fs = svn_fs_root_fs(source_root); 257 SVN_ERR(svn_fs_revision_root(&new_source_root, fs, 258 copyfrom_rev, pool)); 259 new_source_fspath = copyfrom_path; 260 } 261 else 262 { 263 new_source_root = source_root; 264 new_source_fspath = svn_fspath__join(source_fspath, dent->name, 265 subpool); 266 } 267 268 /* ### authz considerations? 269 * 270 * I think not; when path_driver_cb_func() calls add_subdir(), it 271 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. 272 */ 273 if (change && change->change_kind == svn_fs_path_change_replace 274 && copyfrom_path == NULL) 275 { 276 SVN_ERR(editor->add_directory(new_edit_path, *dir_baton, 277 NULL, SVN_INVALID_REVNUM, 278 subpool, &new_dir_baton)); 279 } 280 else 281 { 282 SVN_ERR(add_subdir(new_source_root, target_root, 283 editor, edit_baton, new_edit_path, 284 *dir_baton, new_source_fspath, 285 authz_read_func, authz_read_baton, 286 changed_paths, subpool, &new_dir_baton)); 287 } 288 289 SVN_ERR(editor->close_directory(new_dir_baton, subpool)); 290 } 291 else if (dent->kind == svn_node_file) 292 { 293 svn_txdelta_window_handler_t delta_handler; 294 void *delta_handler_baton, *file_baton; 295 svn_txdelta_stream_t *delta_stream; 296 svn_checksum_t *checksum; 297 298 SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL, 299 SVN_INVALID_REVNUM, pool, &file_baton)); 300 301 SVN_ERR(svn_fs_node_proplist(&props, target_root, 302 new_edit_path, subpool)); 303 304 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 305 { 306 const char *key = apr_hash_this_key(phi); 307 svn_string_t *val = apr_hash_this_val(phi); 308 309 SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool)); 310 } 311 312 SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool, 313 &delta_handler, 314 &delta_handler_baton)); 315 316 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL, 317 target_root, new_edit_path, 318 pool)); 319 320 SVN_ERR(svn_txdelta_send_txstream(delta_stream, 321 delta_handler, 322 delta_handler_baton, 323 pool)); 324 325 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root, 326 new_edit_path, TRUE, pool)); 327 SVN_ERR(editor->close_file(file_baton, 328 svn_checksum_to_cstring(checksum, pool), 329 pool)); 330 } 331 else 332 SVN_ERR_MALFUNCTION(); 333 } 334 335 svn_pool_destroy(subpool); 336 337 return SVN_NO_ERROR; 338} 339 340/* Given PATH deleted under ROOT, return in READABLE whether the path was 341 readable prior to the deletion. Consult COPIES (a stack of 'struct 342 copy_info') and AUTHZ_READ_FUNC. */ 343static svn_error_t * 344was_readable(svn_boolean_t *readable, 345 svn_fs_root_t *root, 346 const char *path, 347 apr_array_header_t *copies, 348 svn_repos_authz_func_t authz_read_func, 349 void *authz_read_baton, 350 apr_pool_t *result_pool, 351 apr_pool_t *scratch_pool) 352{ 353 svn_fs_root_t *inquire_root; 354 const char *inquire_path; 355 struct copy_info *info = NULL; 356 const char *relpath; 357 358 /* Short circuit. */ 359 if (! authz_read_func) 360 { 361 *readable = TRUE; 362 return SVN_NO_ERROR; 363 } 364 365 if (copies->nelts != 0) 366 info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *); 367 368 /* Are we under a copy? */ 369 if (info && (relpath = svn_relpath_skip_ancestor(info->path, path))) 370 { 371 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 372 info->copyfrom_rev, scratch_pool)); 373 inquire_path = svn_fspath__join(info->copyfrom_path, relpath, 374 scratch_pool); 375 } 376 else 377 { 378 /* Compute the revision that ROOT is based on. (Note that ROOT is not 379 r0's root, since this function is only called for deletions.) 380 ### Need a more succinct way to express this */ 381 svn_revnum_t inquire_rev = SVN_INVALID_REVNUM; 382 if (svn_fs_is_txn_root(root)) 383 inquire_rev = svn_fs_txn_root_base_revision(root); 384 if (svn_fs_is_revision_root(root)) 385 inquire_rev = svn_fs_revision_root_revision(root)-1; 386 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev)); 387 388 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 389 inquire_rev, scratch_pool)); 390 inquire_path = path; 391 } 392 393 SVN_ERR(authz_read_func(readable, inquire_root, inquire_path, 394 authz_read_baton, result_pool)); 395 396 return SVN_NO_ERROR; 397} 398 399/* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the 400 revision root, fspath, and revnum of the copyfrom of CHANGE, which 401 corresponds to PATH under ROOT. If the copyfrom info is valid 402 (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE 403 too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided. 404 405 NOTE: If the copyfrom information in CHANGE is marked as unknown 406 (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be 407 trusted), this function will also update those members of the 408 CHANGE structure to carry accurate copyfrom information. */ 409static svn_error_t * 410fill_copyfrom(svn_fs_root_t **copyfrom_root, 411 const char **copyfrom_path, 412 svn_revnum_t *copyfrom_rev, 413 svn_boolean_t *src_readable, 414 svn_fs_root_t *root, 415 svn_fs_path_change2_t *change, 416 svn_repos_authz_func_t authz_read_func, 417 void *authz_read_baton, 418 const char *path, 419 apr_pool_t *result_pool, 420 apr_pool_t *scratch_pool) 421{ 422 if (! change->copyfrom_known) 423 { 424 SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev), 425 &(change->copyfrom_path), 426 root, path, result_pool)); 427 change->copyfrom_known = TRUE; 428 } 429 *copyfrom_rev = change->copyfrom_rev; 430 *copyfrom_path = change->copyfrom_path; 431 432 if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev)) 433 { 434 SVN_ERR(svn_fs_revision_root(copyfrom_root, 435 svn_fs_root_fs(root), 436 *copyfrom_rev, result_pool)); 437 438 if (authz_read_func) 439 { 440 SVN_ERR(authz_read_func(src_readable, *copyfrom_root, 441 *copyfrom_path, 442 authz_read_baton, result_pool)); 443 } 444 else 445 *src_readable = TRUE; 446 } 447 else 448 { 449 *copyfrom_root = NULL; 450 /* SRC_READABLE left uninitialized */ 451 } 452 return SVN_NO_ERROR; 453} 454 455static svn_error_t * 456path_driver_cb_func(void **dir_baton, 457 void *parent_baton, 458 void *callback_baton, 459 const char *edit_path, 460 apr_pool_t *pool) 461{ 462 struct path_driver_cb_baton *cb = callback_baton; 463 const svn_delta_editor_t *editor = cb->editor; 464 void *edit_baton = cb->edit_baton; 465 svn_fs_root_t *root = cb->root; 466 svn_fs_path_change2_t *change; 467 svn_boolean_t do_add = FALSE, do_delete = FALSE; 468 void *file_baton = NULL; 469 svn_revnum_t copyfrom_rev; 470 const char *copyfrom_path; 471 svn_fs_root_t *source_root = cb->compare_root; 472 const char *source_fspath = NULL; 473 const char *base_path = cb->base_path; 474 475 *dir_baton = NULL; 476 477 /* Initialize SOURCE_FSPATH. */ 478 if (source_root) 479 source_fspath = svn_fspath__canonicalize(edit_path, pool); 480 481 /* First, flush the copies stack so it only contains ancestors of path. */ 482 while (cb->copies->nelts > 0 483 && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies, 484 cb->copies->nelts - 1, 485 struct copy_info *)->path, 486 edit_path)) 487 apr_array_pop(cb->copies); 488 489 change = svn_hash_gets(cb->changed_paths, edit_path); 490 if (! change) 491 { 492 /* This can only happen if the path was removed from cb->changed_paths 493 by an earlier call to add_subdir, which means the path was already 494 handled and we should simply ignore it. */ 495 return SVN_NO_ERROR; 496 } 497 switch (change->change_kind) 498 { 499 case svn_fs_path_change_add: 500 do_add = TRUE; 501 break; 502 503 case svn_fs_path_change_delete: 504 do_delete = TRUE; 505 break; 506 507 case svn_fs_path_change_replace: 508 do_add = TRUE; 509 do_delete = TRUE; 510 break; 511 512 case svn_fs_path_change_modify: 513 default: 514 /* do nothing */ 515 break; 516 } 517 518 /* Handle any deletions. */ 519 if (do_delete) 520 { 521 svn_boolean_t readable; 522 523 /* Issue #4121: delete under under a copy, of a path that was unreadable 524 at its pre-copy location. */ 525 SVN_ERR(was_readable(&readable, root, edit_path, cb->copies, 526 cb->authz_read_func, cb->authz_read_baton, 527 pool, pool)); 528 if (readable) 529 SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 530 parent_baton, pool)); 531 } 532 533 /* Fetch the node kind if it makes sense to do so. */ 534 if (! do_delete || do_add) 535 { 536 if (change->node_kind == svn_node_unknown) 537 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool)); 538 if ((change->node_kind != svn_node_dir) && 539 (change->node_kind != svn_node_file)) 540 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 541 _("Filesystem path '%s' is neither a file " 542 "nor a directory"), edit_path); 543 } 544 545 /* Handle any adds/opens. */ 546 if (do_add) 547 { 548 svn_boolean_t src_readable; 549 svn_fs_root_t *copyfrom_root; 550 551 /* E.g. when verifying corrupted repositories, their changed path 552 lists may contain an ADD for "/". The delta path driver will 553 call us with a NULL parent in that case. */ 554 if (*edit_path == 0) 555 return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL, 556 _("Root directory already exists.")); 557 558 /* A NULL parent_baton will cause a segfault. It should never be 559 NULL for non-root paths. */ 560 SVN_ERR_ASSERT(parent_baton); 561 562 /* Was this node copied? */ 563 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev, 564 &src_readable, root, change, 565 cb->authz_read_func, cb->authz_read_baton, 566 edit_path, pool, pool)); 567 568 /* If we have a copyfrom path, and we can't read it or we're just 569 ignoring it, or the copyfrom rev is prior to the low water mark 570 then we just null them out and do a raw add with no history at 571 all. */ 572 if (copyfrom_path 573 && ((! src_readable) 574 || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL) 575 || (cb->low_water_mark > copyfrom_rev))) 576 { 577 copyfrom_path = NULL; 578 copyfrom_rev = SVN_INVALID_REVNUM; 579 } 580 581 /* Do the right thing based on the path KIND. */ 582 if (change->node_kind == svn_node_dir) 583 { 584 /* If this is a copy, but we can't represent it as such, 585 then we just do a recursive add of the source path 586 contents. */ 587 if (change->copyfrom_path && ! copyfrom_path) 588 { 589 SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton, 590 edit_path, parent_baton, change->copyfrom_path, 591 cb->authz_read_func, cb->authz_read_baton, 592 cb->changed_paths, pool, dir_baton)); 593 } 594 else 595 { 596 SVN_ERR(editor->add_directory(edit_path, parent_baton, 597 copyfrom_path, copyfrom_rev, 598 pool, dir_baton)); 599 } 600 } 601 else 602 { 603 SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path, 604 copyfrom_rev, pool, &file_baton)); 605 } 606 607 /* If we represent this as a copy... */ 608 if (copyfrom_path) 609 { 610 /* If it is a directory, make sure descendants get the correct 611 delta source by remembering that we are operating inside a 612 (possibly nested) copy operation. */ 613 if (change->node_kind == svn_node_dir) 614 { 615 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 616 617 info->path = apr_pstrdup(cb->pool, edit_path); 618 info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path); 619 info->copyfrom_rev = copyfrom_rev; 620 621 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 622 } 623 624 /* Save the source so that we can use it later, when we 625 need to generate text and prop deltas. */ 626 source_root = copyfrom_root; 627 source_fspath = copyfrom_path; 628 } 629 else 630 /* Else, we are an add without history... */ 631 { 632 /* If an ancestor is added with history, we need to forget about 633 that here, go on with life and repeat all the mistakes of our 634 past... */ 635 if (change->node_kind == svn_node_dir && cb->copies->nelts > 0) 636 { 637 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 638 639 info->path = apr_pstrdup(cb->pool, edit_path); 640 info->copyfrom_path = NULL; 641 info->copyfrom_rev = SVN_INVALID_REVNUM; 642 643 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 644 } 645 source_root = NULL; 646 source_fspath = NULL; 647 } 648 } 649 else if (! do_delete) 650 { 651 /* Do the right thing based on the path KIND (and the presence 652 of a PARENT_BATON). */ 653 if (change->node_kind == svn_node_dir) 654 { 655 if (parent_baton) 656 { 657 SVN_ERR(editor->open_directory(edit_path, parent_baton, 658 SVN_INVALID_REVNUM, 659 pool, dir_baton)); 660 } 661 else 662 { 663 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, 664 pool, dir_baton)); 665 } 666 } 667 else 668 { 669 SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM, 670 pool, &file_baton)); 671 } 672 /* If we are inside an add with history, we need to adjust the 673 delta source. */ 674 if (cb->copies->nelts > 0) 675 { 676 struct copy_info *info = APR_ARRAY_IDX(cb->copies, 677 cb->copies->nelts - 1, 678 struct copy_info *); 679 if (info->copyfrom_path) 680 { 681 const char *relpath = svn_relpath_skip_ancestor(info->path, 682 edit_path); 683 SVN_ERR_ASSERT(relpath && *relpath); 684 SVN_ERR(svn_fs_revision_root(&source_root, 685 svn_fs_root_fs(root), 686 info->copyfrom_rev, pool)); 687 source_fspath = svn_fspath__join(info->copyfrom_path, 688 relpath, pool); 689 } 690 else 691 { 692 /* This is an add without history, nested inside an 693 add with history. We have no delta source in this case. */ 694 source_root = NULL; 695 source_fspath = NULL; 696 } 697 } 698 } 699 700 if (! do_delete || do_add) 701 { 702 /* Is this a copy that was downgraded to a raw add? (If so, 703 we'll need to transmit properties and file contents and such 704 for it regardless of what the CHANGE structure's text_mod 705 and prop_mod flags say.) */ 706 svn_boolean_t downgraded_copy = (change->copyfrom_known 707 && change->copyfrom_path 708 && (! copyfrom_path)); 709 710 /* Handle property modifications. */ 711 if (change->prop_mod || downgraded_copy) 712 { 713 if (cb->compare_root) 714 { 715 apr_array_header_t *prop_diffs; 716 apr_hash_t *old_props; 717 apr_hash_t *new_props; 718 int i; 719 720 if (source_root) 721 SVN_ERR(svn_fs_node_proplist(&old_props, source_root, 722 source_fspath, pool)); 723 else 724 old_props = apr_hash_make(pool); 725 726 SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool)); 727 728 SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props, 729 pool)); 730 731 for (i = 0; i < prop_diffs->nelts; ++i) 732 { 733 svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 734 if (change->node_kind == svn_node_dir) 735 SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name, 736 pc->value, pool)); 737 else if (change->node_kind == svn_node_file) 738 SVN_ERR(editor->change_file_prop(file_baton, pc->name, 739 pc->value, pool)); 740 } 741 } 742 else 743 { 744 /* Just do a dummy prop change to signal that there are *any* 745 propmods. */ 746 if (change->node_kind == svn_node_dir) 747 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL, 748 pool)); 749 else if (change->node_kind == svn_node_file) 750 SVN_ERR(editor->change_file_prop(file_baton, "", NULL, 751 pool)); 752 } 753 } 754 755 /* Handle textual modifications. */ 756 if (change->node_kind == svn_node_file 757 && (change->text_mod || downgraded_copy)) 758 { 759 svn_txdelta_window_handler_t delta_handler; 760 void *delta_handler_baton; 761 const char *hex_digest = NULL; 762 763 if (cb->compare_root && source_root && source_fspath) 764 { 765 svn_checksum_t *checksum; 766 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 767 source_root, source_fspath, TRUE, 768 pool)); 769 hex_digest = svn_checksum_to_cstring(checksum, pool); 770 } 771 772 SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool, 773 &delta_handler, 774 &delta_handler_baton)); 775 if (cb->compare_root) 776 { 777 svn_txdelta_stream_t *delta_stream; 778 779 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root, 780 source_fspath, root, 781 edit_path, pool)); 782 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, 783 delta_handler_baton, pool)); 784 } 785 else 786 SVN_ERR(delta_handler(NULL, delta_handler_baton)); 787 } 788 } 789 790 /* Close the file baton if we opened it. */ 791 if (file_baton) 792 { 793 svn_checksum_t *checksum; 794 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path, 795 TRUE, pool)); 796 SVN_ERR(editor->close_file(file_baton, 797 svn_checksum_to_cstring(checksum, pool), 798 pool)); 799 } 800 801 return SVN_NO_ERROR; 802} 803 804#ifdef USE_EV2_IMPL 805static svn_error_t * 806fetch_kind_func(svn_node_kind_t *kind, 807 void *baton, 808 const char *path, 809 svn_revnum_t base_revision, 810 apr_pool_t *scratch_pool) 811{ 812 svn_fs_root_t *root = baton; 813 svn_fs_root_t *prev_root; 814 svn_fs_t *fs = svn_fs_root_fs(root); 815 816 if (!SVN_IS_VALID_REVNUM(base_revision)) 817 base_revision = svn_fs_revision_root_revision(root) - 1; 818 819 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 820 SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool)); 821 822 return SVN_NO_ERROR; 823} 824 825static svn_error_t * 826fetch_props_func(apr_hash_t **props, 827 void *baton, 828 const char *path, 829 svn_revnum_t base_revision, 830 apr_pool_t *result_pool, 831 apr_pool_t *scratch_pool) 832{ 833 svn_fs_root_t *root = baton; 834 svn_fs_root_t *prev_root; 835 svn_fs_t *fs = svn_fs_root_fs(root); 836 837 if (!SVN_IS_VALID_REVNUM(base_revision)) 838 base_revision = svn_fs_revision_root_revision(root) - 1; 839 840 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 841 SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool)); 842 843 return SVN_NO_ERROR; 844} 845#endif 846 847 848 849 850svn_error_t * 851svn_repos_replay2(svn_fs_root_t *root, 852 const char *base_path, 853 svn_revnum_t low_water_mark, 854 svn_boolean_t send_deltas, 855 const svn_delta_editor_t *editor, 856 void *edit_baton, 857 svn_repos_authz_func_t authz_read_func, 858 void *authz_read_baton, 859 apr_pool_t *pool) 860{ 861#ifndef USE_EV2_IMPL 862 apr_hash_t *fs_changes; 863 apr_hash_t *changed_paths; 864 apr_hash_index_t *hi; 865 apr_array_header_t *paths; 866 struct path_driver_cb_baton cb_baton; 867 868 /* Special-case r0, which we know is an empty revision; if we don't 869 special-case it we might end up trying to compare it to "r-1". */ 870 if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0) 871 { 872 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool)); 873 return SVN_NO_ERROR; 874 } 875 876 /* Fetch the paths changed under ROOT. */ 877 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool)); 878 879 if (! base_path) 880 base_path = ""; 881 else if (base_path[0] == '/') 882 ++base_path; 883 884 /* Make an array from the keys of our CHANGED_PATHS hash, and copy 885 the values into a new hash whose keys have no leading slashes. */ 886 paths = apr_array_make(pool, apr_hash_count(fs_changes), 887 sizeof(const char *)); 888 changed_paths = apr_hash_make(pool); 889 for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi)) 890 { 891 const char *path = apr_hash_this_key(hi); 892 apr_ssize_t keylen = apr_hash_this_key_len(hi); 893 svn_fs_path_change2_t *change = apr_hash_this_val(hi); 894 svn_boolean_t allowed = TRUE; 895 896 if (authz_read_func) 897 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, 898 pool)); 899 900 if (allowed) 901 { 902 if (path[0] == '/') 903 { 904 path++; 905 keylen--; 906 } 907 908 /* If the base_path doesn't match the top directory of this path 909 we don't want anything to do with it... */ 910 if (svn_relpath_skip_ancestor(base_path, path) != NULL) 911 { 912 APR_ARRAY_PUSH(paths, const char *) = path; 913 apr_hash_set(changed_paths, path, keylen, change); 914 } 915 /* ...unless this was a change to one of the parent directories of 916 base_path. */ 917 else if (svn_relpath_skip_ancestor(path, base_path) != NULL) 918 { 919 APR_ARRAY_PUSH(paths, const char *) = path; 920 apr_hash_set(changed_paths, path, keylen, change); 921 } 922 } 923 } 924 925 /* If we were not given a low water mark, assume that everything is there, 926 all the way back to revision 0. */ 927 if (! SVN_IS_VALID_REVNUM(low_water_mark)) 928 low_water_mark = 0; 929 930 /* Initialize our callback baton. */ 931 cb_baton.editor = editor; 932 cb_baton.edit_baton = edit_baton; 933 cb_baton.root = root; 934 cb_baton.changed_paths = changed_paths; 935 cb_baton.authz_read_func = authz_read_func; 936 cb_baton.authz_read_baton = authz_read_baton; 937 cb_baton.base_path = base_path; 938 cb_baton.low_water_mark = low_water_mark; 939 cb_baton.compare_root = NULL; 940 941 if (send_deltas) 942 { 943 SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root, 944 svn_fs_root_fs(root), 945 svn_fs_is_revision_root(root) 946 ? svn_fs_revision_root_revision(root) - 1 947 : svn_fs_txn_root_base_revision(root), 948 pool)); 949 } 950 951 cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *)); 952 cb_baton.pool = pool; 953 954 /* Determine the revision to use throughout the edit, and call 955 EDITOR's set_target_revision() function. */ 956 if (svn_fs_is_revision_root(root)) 957 { 958 svn_revnum_t revision = svn_fs_revision_root_revision(root); 959 SVN_ERR(editor->set_target_revision(edit_baton, revision, pool)); 960 } 961 962 /* Call the path-based editor driver. */ 963 return svn_delta_path_driver2(editor, edit_baton, 964 paths, TRUE, 965 path_driver_cb_func, &cb_baton, pool); 966#else 967 svn_editor_t *editorv2; 968 struct svn_delta__extra_baton *exb; 969 svn_delta__unlock_func_t unlock_func; 970 svn_boolean_t send_abs_paths; 971 const char *repos_root = ""; 972 void *unlock_baton; 973 974 /* Special-case r0, which we know is an empty revision; if we don't 975 special-case it we might end up trying to compare it to "r-1". */ 976 if (svn_fs_is_revision_root(root) 977 && svn_fs_revision_root_revision(root) == 0) 978 { 979 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool)); 980 return SVN_NO_ERROR; 981 } 982 983 /* Determine the revision to use throughout the edit, and call 984 EDITOR's set_target_revision() function. */ 985 if (svn_fs_is_revision_root(root)) 986 { 987 svn_revnum_t revision = svn_fs_revision_root_revision(root); 988 SVN_ERR(editor->set_target_revision(edit_baton, revision, pool)); 989 } 990 991 if (! base_path) 992 base_path = ""; 993 else if (base_path[0] == '/') 994 ++base_path; 995 996 /* Use the shim to convert our editor to an Ev2 editor, and pass it down 997 the stack. */ 998 SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb, 999 &unlock_func, &unlock_baton, 1000 editor, edit_baton, 1001 &send_abs_paths, 1002 repos_root, "", 1003 NULL, NULL, 1004 fetch_kind_func, root, 1005 fetch_props_func, root, 1006 pool, pool)); 1007 1008 /* Tell the shim that we're starting the process. */ 1009 SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root))); 1010 1011 /* ### We're ignoring SEND_DELTAS here. */ 1012 SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark, 1013 editorv2, authz_read_func, authz_read_baton, 1014 pool)); 1015 1016 return SVN_NO_ERROR; 1017#endif 1018} 1019 1020 1021/***************************************************************** 1022 * Ev2 Implementation * 1023 *****************************************************************/ 1024 1025/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting 1026 the appropriate editor calls to add it and its children without any 1027 history. This is meant to be used when either a subset of the tree 1028 has been ignored and we need to copy something from that subset to 1029 the part of the tree we do care about, or if a subset of the tree is 1030 unavailable because of authz and we need to use it as the source of 1031 a copy. */ 1032static svn_error_t * 1033add_subdir_ev2(svn_fs_root_t *source_root, 1034 svn_fs_root_t *target_root, 1035 svn_editor_t *editor, 1036 const char *repos_relpath, 1037 const char *source_fspath, 1038 svn_repos_authz_func_t authz_read_func, 1039 void *authz_read_baton, 1040 apr_hash_t *changed_paths, 1041 apr_pool_t *result_pool, 1042 apr_pool_t *scratch_pool) 1043{ 1044 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1045 apr_hash_index_t *hi; 1046 apr_hash_t *dirents; 1047 apr_hash_t *props = NULL; 1048 apr_array_header_t *children = NULL; 1049 1050 SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath, 1051 scratch_pool)); 1052 1053 SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children, 1054 props, SVN_INVALID_REVNUM)); 1055 1056 /* We have to get the dirents from the source path, not the target, 1057 because we want nested copies from *readable* paths to be handled by 1058 path_driver_cb_func, not add_subdir (in order to preserve history). */ 1059 SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, 1060 scratch_pool)); 1061 1062 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) 1063 { 1064 svn_fs_path_change2_t *change; 1065 svn_boolean_t readable = TRUE; 1066 svn_fs_dirent_t *dent = apr_hash_this_val(hi); 1067 const char *copyfrom_path = NULL; 1068 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 1069 const char *child_relpath; 1070 1071 svn_pool_clear(iterpool); 1072 1073 child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool); 1074 1075 /* If a file or subdirectory of the copied directory is listed as a 1076 changed path (because it was modified after the copy but before the 1077 commit), we remove it from the changed_paths hash so that future 1078 calls to path_driver_cb_func will ignore it. */ 1079 change = svn_hash_gets(changed_paths, child_relpath); 1080 if (change) 1081 { 1082 svn_hash_sets(changed_paths, child_relpath, NULL); 1083 1084 /* If it's a delete, skip this entry. */ 1085 if (change->change_kind == svn_fs_path_change_delete) 1086 continue; 1087 1088 /* If it's a replacement, check for copyfrom info (if we 1089 don't have it already. */ 1090 if (change->change_kind == svn_fs_path_change_replace) 1091 { 1092 if (! change->copyfrom_known) 1093 { 1094 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, 1095 &change->copyfrom_path, 1096 target_root, child_relpath, 1097 result_pool)); 1098 change->copyfrom_known = TRUE; 1099 } 1100 copyfrom_path = change->copyfrom_path; 1101 copyfrom_rev = change->copyfrom_rev; 1102 } 1103 } 1104 1105 if (authz_read_func) 1106 SVN_ERR(authz_read_func(&readable, target_root, child_relpath, 1107 authz_read_baton, iterpool)); 1108 1109 if (! readable) 1110 continue; 1111 1112 if (dent->kind == svn_node_dir) 1113 { 1114 svn_fs_root_t *new_source_root; 1115 const char *new_source_fspath; 1116 1117 if (copyfrom_path) 1118 { 1119 svn_fs_t *fs = svn_fs_root_fs(source_root); 1120 SVN_ERR(svn_fs_revision_root(&new_source_root, fs, 1121 copyfrom_rev, result_pool)); 1122 new_source_fspath = copyfrom_path; 1123 } 1124 else 1125 { 1126 new_source_root = source_root; 1127 new_source_fspath = svn_fspath__join(source_fspath, dent->name, 1128 iterpool); 1129 } 1130 1131 /* ### authz considerations? 1132 * 1133 * I think not; when path_driver_cb_func() calls add_subdir(), it 1134 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. 1135 */ 1136 if (change && change->change_kind == svn_fs_path_change_replace 1137 && copyfrom_path == NULL) 1138 { 1139 SVN_ERR(svn_editor_add_directory(editor, child_relpath, 1140 children, props, 1141 SVN_INVALID_REVNUM)); 1142 } 1143 else 1144 { 1145 SVN_ERR(add_subdir_ev2(new_source_root, target_root, 1146 editor, child_relpath, 1147 new_source_fspath, 1148 authz_read_func, authz_read_baton, 1149 changed_paths, result_pool, iterpool)); 1150 } 1151 } 1152 else if (dent->kind == svn_node_file) 1153 { 1154 svn_checksum_t *checksum; 1155 svn_stream_t *contents; 1156 1157 SVN_ERR(svn_fs_node_proplist(&props, target_root, 1158 child_relpath, iterpool)); 1159 1160 SVN_ERR(svn_fs_file_contents(&contents, target_root, 1161 child_relpath, iterpool)); 1162 1163 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1164 target_root, 1165 child_relpath, TRUE, iterpool)); 1166 1167 SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum, 1168 contents, props, SVN_INVALID_REVNUM)); 1169 } 1170 else 1171 SVN_ERR_MALFUNCTION(); 1172 } 1173 1174 svn_pool_destroy(iterpool); 1175 1176 return SVN_NO_ERROR; 1177} 1178 1179static svn_error_t * 1180replay_node(svn_fs_root_t *root, 1181 const char *repos_relpath, 1182 svn_editor_t *editor, 1183 svn_revnum_t low_water_mark, 1184 const char *base_repos_relpath, 1185 apr_array_header_t *copies, 1186 apr_hash_t *changed_paths, 1187 svn_repos_authz_func_t authz_read_func, 1188 void *authz_read_baton, 1189 apr_pool_t *result_pool, 1190 apr_pool_t *scratch_pool) 1191{ 1192 svn_fs_path_change2_t *change; 1193 svn_boolean_t do_add = FALSE; 1194 svn_boolean_t do_delete = FALSE; 1195 svn_revnum_t copyfrom_rev; 1196 const char *copyfrom_path; 1197 svn_revnum_t replaces_rev; 1198 1199 /* First, flush the copies stack so it only contains ancestors of path. */ 1200 while (copies->nelts > 0 1201 && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies, 1202 copies->nelts - 1, 1203 struct copy_info *)->path, 1204 repos_relpath) == NULL) ) 1205 apr_array_pop(copies); 1206 1207 change = svn_hash_gets(changed_paths, repos_relpath); 1208 if (! change) 1209 { 1210 /* This can only happen if the path was removed from changed_paths 1211 by an earlier call to add_subdir, which means the path was already 1212 handled and we should simply ignore it. */ 1213 return SVN_NO_ERROR; 1214 } 1215 switch (change->change_kind) 1216 { 1217 case svn_fs_path_change_add: 1218 do_add = TRUE; 1219 break; 1220 1221 case svn_fs_path_change_delete: 1222 do_delete = TRUE; 1223 break; 1224 1225 case svn_fs_path_change_replace: 1226 do_add = TRUE; 1227 do_delete = TRUE; 1228 break; 1229 1230 case svn_fs_path_change_modify: 1231 default: 1232 /* do nothing */ 1233 break; 1234 } 1235 1236 /* Handle any deletions. */ 1237 if (do_delete && ! do_add) 1238 { 1239 svn_boolean_t readable; 1240 1241 /* Issue #4121: delete under under a copy, of a path that was unreadable 1242 at its pre-copy location. */ 1243 SVN_ERR(was_readable(&readable, root, repos_relpath, copies, 1244 authz_read_func, authz_read_baton, 1245 scratch_pool, scratch_pool)); 1246 if (readable) 1247 SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM)); 1248 1249 return SVN_NO_ERROR; 1250 } 1251 1252 /* Handle replacements. */ 1253 if (do_delete && do_add) 1254 replaces_rev = svn_fs_revision_root_revision(root); 1255 else 1256 replaces_rev = SVN_INVALID_REVNUM; 1257 1258 /* Fetch the node kind if it makes sense to do so. */ 1259 if (! do_delete || do_add) 1260 { 1261 if (change->node_kind == svn_node_unknown) 1262 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath, 1263 scratch_pool)); 1264 if ((change->node_kind != svn_node_dir) && 1265 (change->node_kind != svn_node_file)) 1266 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1267 _("Filesystem path '%s' is neither a file " 1268 "nor a directory"), repos_relpath); 1269 } 1270 1271 /* Handle any adds/opens. */ 1272 if (do_add) 1273 { 1274 svn_boolean_t src_readable; 1275 svn_fs_root_t *copyfrom_root; 1276 1277 /* Was this node copied? */ 1278 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev, 1279 &src_readable, root, change, 1280 authz_read_func, authz_read_baton, 1281 repos_relpath, scratch_pool, scratch_pool)); 1282 1283 /* If we have a copyfrom path, and we can't read it or we're just 1284 ignoring it, or the copyfrom rev is prior to the low water mark 1285 then we just null them out and do a raw add with no history at 1286 all. */ 1287 if (copyfrom_path 1288 && ((! src_readable) 1289 || (svn_relpath_skip_ancestor(base_repos_relpath, 1290 copyfrom_path + 1) == NULL) 1291 || (low_water_mark > copyfrom_rev))) 1292 { 1293 copyfrom_path = NULL; 1294 copyfrom_rev = SVN_INVALID_REVNUM; 1295 } 1296 1297 /* Do the right thing based on the path KIND. */ 1298 if (change->node_kind == svn_node_dir) 1299 { 1300 /* If this is a copy, but we can't represent it as such, 1301 then we just do a recursive add of the source path 1302 contents. */ 1303 if (change->copyfrom_path && ! copyfrom_path) 1304 { 1305 SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor, 1306 repos_relpath, change->copyfrom_path, 1307 authz_read_func, authz_read_baton, 1308 changed_paths, result_pool, 1309 scratch_pool)); 1310 } 1311 else 1312 { 1313 if (copyfrom_path) 1314 { 1315 if (copyfrom_path[0] == '/') 1316 ++copyfrom_path; 1317 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev, 1318 repos_relpath, replaces_rev)); 1319 } 1320 else 1321 { 1322 apr_array_header_t *children; 1323 apr_hash_t *props; 1324 apr_hash_t *dirents; 1325 1326 SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath, 1327 scratch_pool)); 1328 SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool)); 1329 1330 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1331 scratch_pool)); 1332 1333 SVN_ERR(svn_editor_add_directory(editor, repos_relpath, 1334 children, props, 1335 replaces_rev)); 1336 } 1337 } 1338 } 1339 else 1340 { 1341 if (copyfrom_path) 1342 { 1343 if (copyfrom_path[0] == '/') 1344 ++copyfrom_path; 1345 SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev, 1346 repos_relpath, replaces_rev)); 1347 } 1348 else 1349 { 1350 apr_hash_t *props; 1351 svn_checksum_t *checksum; 1352 svn_stream_t *contents; 1353 1354 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1355 scratch_pool)); 1356 1357 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath, 1358 scratch_pool)); 1359 1360 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, 1361 repos_relpath, TRUE, scratch_pool)); 1362 1363 SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum, 1364 contents, props, replaces_rev)); 1365 } 1366 } 1367 1368 /* If we represent this as a copy... */ 1369 if (copyfrom_path) 1370 { 1371 /* If it is a directory, make sure descendants get the correct 1372 delta source by remembering that we are operating inside a 1373 (possibly nested) copy operation. */ 1374 if (change->node_kind == svn_node_dir) 1375 { 1376 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info)); 1377 1378 info->path = apr_pstrdup(result_pool, repos_relpath); 1379 info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path); 1380 info->copyfrom_rev = copyfrom_rev; 1381 1382 APR_ARRAY_PUSH(copies, struct copy_info *) = info; 1383 } 1384 } 1385 else 1386 /* Else, we are an add without history... */ 1387 { 1388 /* If an ancestor is added with history, we need to forget about 1389 that here, go on with life and repeat all the mistakes of our 1390 past... */ 1391 if (change->node_kind == svn_node_dir && copies->nelts > 0) 1392 { 1393 struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info)); 1394 1395 info->path = apr_pstrdup(result_pool, repos_relpath); 1396 info->copyfrom_path = NULL; 1397 info->copyfrom_rev = SVN_INVALID_REVNUM; 1398 1399 APR_ARRAY_PUSH(copies, struct copy_info *) = info; 1400 } 1401 } 1402 } 1403 else if (! do_delete) 1404 { 1405 /* If we are inside an add with history, we need to adjust the 1406 delta source. */ 1407 if (copies->nelts > 0) 1408 { 1409 struct copy_info *info = APR_ARRAY_IDX(copies, 1410 copies->nelts - 1, 1411 struct copy_info *); 1412 if (info->copyfrom_path) 1413 { 1414 const char *relpath = svn_relpath_skip_ancestor(info->path, 1415 repos_relpath); 1416 SVN_ERR_ASSERT(relpath && *relpath); 1417 repos_relpath = svn_relpath_join(info->copyfrom_path, 1418 relpath, scratch_pool); 1419 } 1420 } 1421 } 1422 1423 if (! do_delete && !do_add) 1424 { 1425 apr_hash_t *props = NULL; 1426 1427 /* Is this a copy that was downgraded to a raw add? (If so, 1428 we'll need to transmit properties and file contents and such 1429 for it regardless of what the CHANGE structure's text_mod 1430 and prop_mod flags say.) */ 1431 svn_boolean_t downgraded_copy = (change->copyfrom_known 1432 && change->copyfrom_path 1433 && (! copyfrom_path)); 1434 1435 /* Handle property modifications. */ 1436 if (change->prop_mod || downgraded_copy) 1437 { 1438 SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1439 scratch_pool)); 1440 } 1441 1442 /* Handle textual modifications. */ 1443 if (change->node_kind == svn_node_file 1444 && (change->text_mod || change->prop_mod || downgraded_copy)) 1445 { 1446 svn_checksum_t *checksum = NULL; 1447 svn_stream_t *contents = NULL; 1448 1449 if (change->text_mod) 1450 { 1451 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1452 root, repos_relpath, TRUE, 1453 scratch_pool)); 1454 1455 SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath, 1456 scratch_pool)); 1457 } 1458 1459 SVN_ERR(svn_editor_alter_file(editor, repos_relpath, 1460 SVN_INVALID_REVNUM, 1461 checksum, contents, props)); 1462 } 1463 1464 if (change->node_kind == svn_node_dir 1465 && (change->prop_mod || downgraded_copy)) 1466 { 1467 apr_array_header_t *children = NULL; 1468 1469 SVN_ERR(svn_editor_alter_directory(editor, repos_relpath, 1470 SVN_INVALID_REVNUM, children, 1471 props)); 1472 } 1473 } 1474 1475 return SVN_NO_ERROR; 1476} 1477 1478svn_error_t * 1479svn_repos__replay_ev2(svn_fs_root_t *root, 1480 const char *base_repos_relpath, 1481 svn_revnum_t low_water_mark, 1482 svn_editor_t *editor, 1483 svn_repos_authz_func_t authz_read_func, 1484 void *authz_read_baton, 1485 apr_pool_t *scratch_pool) 1486{ 1487 apr_hash_t *fs_changes; 1488 apr_hash_t *changed_paths; 1489 apr_hash_index_t *hi; 1490 apr_array_header_t *paths; 1491 apr_array_header_t *copies; 1492 apr_pool_t *iterpool; 1493 svn_error_t *err = SVN_NO_ERROR; 1494 int i; 1495 1496 SVN_ERR_ASSERT(!svn_dirent_is_absolute(base_repos_relpath)); 1497 1498 /* Special-case r0, which we know is an empty revision; if we don't 1499 special-case it we might end up trying to compare it to "r-1". */ 1500 if (svn_fs_is_revision_root(root) 1501 && svn_fs_revision_root_revision(root) == 0) 1502 { 1503 return SVN_NO_ERROR; 1504 } 1505 1506 /* Fetch the paths changed under ROOT. */ 1507 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool)); 1508 1509 /* Make an array from the keys of our CHANGED_PATHS hash, and copy 1510 the values into a new hash whose keys have no leading slashes. */ 1511 paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes), 1512 sizeof(const char *)); 1513 changed_paths = apr_hash_make(scratch_pool); 1514 for (hi = apr_hash_first(scratch_pool, fs_changes); hi; 1515 hi = apr_hash_next(hi)) 1516 { 1517 const char *path = apr_hash_this_key(hi); 1518 apr_ssize_t keylen = apr_hash_this_key_len(hi); 1519 svn_fs_path_change2_t *change = apr_hash_this_val(hi); 1520 svn_boolean_t allowed = TRUE; 1521 1522 if (authz_read_func) 1523 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, 1524 scratch_pool)); 1525 1526 if (allowed) 1527 { 1528 if (path[0] == '/') 1529 { 1530 path++; 1531 keylen--; 1532 } 1533 1534 /* If the base_path doesn't match the top directory of this path 1535 we don't want anything to do with it... */ 1536 if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL) 1537 { 1538 APR_ARRAY_PUSH(paths, const char *) = path; 1539 apr_hash_set(changed_paths, path, keylen, change); 1540 } 1541 /* ...unless this was a change to one of the parent directories of 1542 base_path. */ 1543 else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL) 1544 { 1545 APR_ARRAY_PUSH(paths, const char *) = path; 1546 apr_hash_set(changed_paths, path, keylen, change); 1547 } 1548 } 1549 } 1550 1551 /* If we were not given a low water mark, assume that everything is there, 1552 all the way back to revision 0. */ 1553 if (! SVN_IS_VALID_REVNUM(low_water_mark)) 1554 low_water_mark = 0; 1555 1556 copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *)); 1557 1558 /* Sort the paths. Although not strictly required by the API, this has 1559 the pleasant side effect of maintaining a consistent ordering of 1560 dumpfile contents. */ 1561 svn_sort__array(paths, svn_sort_compare_paths); 1562 1563 /* Now actually handle the various paths. */ 1564 iterpool = svn_pool_create(scratch_pool); 1565 for (i = 0; i < paths->nelts; i++) 1566 { 1567 const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *); 1568 1569 svn_pool_clear(iterpool); 1570 err = replay_node(root, repos_relpath, editor, low_water_mark, 1571 base_repos_relpath, copies, changed_paths, 1572 authz_read_func, authz_read_baton, 1573 scratch_pool, iterpool); 1574 if (err) 1575 break; 1576 } 1577 1578 if (err) 1579 return svn_error_compose_create(err, svn_editor_abort(editor)); 1580 else 1581 SVN_ERR(svn_editor_complete(editor)); 1582 1583 svn_pool_destroy(iterpool); 1584 return SVN_NO_ERROR; 1585} 1586