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 43 44/*** Backstory ***/ 45 46/* The year was 2003. Subversion usage was rampant in the world, and 47 there was a rapidly growing issues database to prove it. To make 48 matters worse, svn_repos_dir_delta() had simply outgrown itself. 49 No longer content to simply describe the differences between two 50 trees, the function had been slowly bearing the added 51 responsibility of representing the actions that had been taken to 52 cause those differences -- a burden it was never meant to bear. 53 Now grown into a twisted mess of razor-sharp metal and glass, and 54 trembling with a sort of momentarily stayed spring force, 55 svn_repos_dir_delta was a timebomb poised for total annihilation of 56 the American Midwest. 57 58 Subversion needed a change. 59 60 Changes, in fact. And not just in the literary segue sense. What 61 Subversion desperately needed was a new mechanism solely 62 responsible for replaying repository actions back to some 63 interested party -- to translate and retransmit the contents of the 64 Berkeley 'changes' database file. */ 65 66/*** Overview ***/ 67 68/* The filesystem keeps a record of high-level actions that affect the 69 files and directories in itself. The 'changes' table records 70 additions, deletions, textual and property modifications, and so 71 on. The goal of the functions in this file is to examine those 72 change records, and use them to drive an editor interface in such a 73 way as to effectively replay those actions. 74 75 This is critically different than what svn_repos_dir_delta() was 76 designed to do. That function describes, in the simplest way it 77 can, how to transform one tree into another. It doesn't care 78 whether or not this was the same way a user might have done this 79 transformation. More to the point, it doesn't care if this is how 80 those differences *did* come into being. And it is for this reason 81 that it cannot be relied upon for tasks such as the repository 82 dumpfile-generation code, which is supposed to represent not 83 changes, but actions that cause changes. 84 85 So, what's the plan here? 86 87 First, we fetch the changes for a particular revision or 88 transaction. We get these as an array, sorted chronologically. 89 From this array we will build a hash, keyed on the path associated 90 with each change item, and whose values are arrays of changes made 91 to that path, again preserving the chronological ordering. 92 93 Once our hash is built, we then sort all the keys of the hash (the 94 paths) using a depth-first directory sort routine. 95 96 Finally, we drive an editor, moving down our list of sorted paths, 97 and manufacturing any intermediate editor calls (directory openings 98 and closures) needed to navigate between each successive path. For 99 each path, we replay the sorted actions that occurred at that path. 100 101 When we've finished the editor drive, we should have fully replayed 102 the filesystem events that occurred in that revision or transaction 103 (though not necessarily in the same order in which they 104 occurred). */ 105 106/* #define USE_EV2_IMPL */ 107 108 109/*** Helper functions. ***/ 110 111 112/* Information for an active copy, that is a directory which we are currently 113 working on and which was added with history. */ 114struct copy_info 115{ 116 /* Destination relpath (relative to the root of the . */ 117 const char *path; 118 119 /* Copy source path (expressed as an absolute FS path) or revision. 120 NULL and SVN_INVALID_REVNUM if this is an add without history, 121 nested inside an add with history. */ 122 const char *copyfrom_path; 123 svn_revnum_t copyfrom_rev; 124}; 125 126struct path_driver_cb_baton 127{ 128 const svn_delta_editor_t *editor; 129 void *edit_baton; 130 131 /* The root of the revision we're replaying. */ 132 svn_fs_root_t *root; 133 134 /* The root of the previous revision. If this is non-NULL it means that 135 we are supposed to generate props and text deltas relative to it. */ 136 svn_fs_root_t *compare_root; 137 138 apr_hash_t *changed_paths; 139 140 svn_repos_authz_func_t authz_read_func; 141 void *authz_read_baton; 142 143 const char *base_path; /* relpath */ 144 145 svn_revnum_t low_water_mark; 146 /* Stack of active copy operations. */ 147 apr_array_header_t *copies; 148 149 /* The global pool for this replay operation. */ 150 apr_pool_t *pool; 151}; 152 153/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting 154 the appropriate editor calls to add it and its children without any 155 history. This is meant to be used when either a subset of the tree 156 has been ignored and we need to copy something from that subset to 157 the part of the tree we do care about, or if a subset of the tree is 158 unavailable because of authz and we need to use it as the source of 159 a copy. */ 160static svn_error_t * 161add_subdir(svn_fs_root_t *source_root, 162 svn_fs_root_t *target_root, 163 const svn_delta_editor_t *editor, 164 void *edit_baton, 165 const char *edit_path, 166 void *parent_baton, 167 const char *source_fspath, 168 svn_repos_authz_func_t authz_read_func, 169 void *authz_read_baton, 170 apr_hash_t *changed_paths, 171 apr_pool_t *pool, 172 void **dir_baton) 173{ 174 apr_pool_t *subpool = svn_pool_create(pool); 175 apr_hash_index_t *hi, *phi; 176 apr_hash_t *dirents; 177 apr_hash_t *props; 178 179 SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL, 180 SVN_INVALID_REVNUM, pool, dir_baton)); 181 182 SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool)); 183 184 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 185 { 186 const void *key; 187 void *val; 188 189 svn_pool_clear(subpool); 190 apr_hash_this(phi, &key, NULL, &val); 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; 204 const char *copyfrom_path = NULL; 205 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 206 const char *new_edit_path; 207 void *val; 208 209 svn_pool_clear(subpool); 210 211 apr_hash_this(hi, NULL, NULL, &val); 212 213 dent = val; 214 215 new_edit_path = svn_relpath_join(edit_path, dent->name, subpool); 216 217 /* If a file or subdirectory of the copied directory is listed as a 218 changed path (because it was modified after the copy but before the 219 commit), we remove it from the changed_paths hash so that future 220 calls to path_driver_cb_func will ignore it. */ 221 change = svn_hash_gets(changed_paths, new_edit_path); 222 if (change) 223 { 224 svn_hash_sets(changed_paths, new_edit_path, NULL); 225 226 /* If it's a delete, skip this entry. */ 227 if (change->change_kind == svn_fs_path_change_delete) 228 continue; 229 230 /* If it's a replacement, check for copyfrom info (if we 231 don't have it already. */ 232 if (change->change_kind == svn_fs_path_change_replace) 233 { 234 if (! change->copyfrom_known) 235 { 236 SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, 237 &change->copyfrom_path, 238 target_root, new_edit_path, pool)); 239 change->copyfrom_known = TRUE; 240 } 241 copyfrom_path = change->copyfrom_path; 242 copyfrom_rev = change->copyfrom_rev; 243 } 244 } 245 246 if (authz_read_func) 247 SVN_ERR(authz_read_func(&readable, target_root, new_edit_path, 248 authz_read_baton, pool)); 249 250 if (! readable) 251 continue; 252 253 if (dent->kind == svn_node_dir) 254 { 255 svn_fs_root_t *new_source_root; 256 const char *new_source_fspath; 257 void *new_dir_baton; 258 259 if (copyfrom_path) 260 { 261 svn_fs_t *fs = svn_fs_root_fs(source_root); 262 SVN_ERR(svn_fs_revision_root(&new_source_root, fs, 263 copyfrom_rev, pool)); 264 new_source_fspath = copyfrom_path; 265 } 266 else 267 { 268 new_source_root = source_root; 269 new_source_fspath = svn_fspath__join(source_fspath, dent->name, 270 subpool); 271 } 272 273 /* ### authz considerations? 274 * 275 * I think not; when path_driver_cb_func() calls add_subdir(), it 276 * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. 277 */ 278 if (change && change->change_kind == svn_fs_path_change_replace 279 && copyfrom_path == NULL) 280 { 281 SVN_ERR(editor->add_directory(new_edit_path, *dir_baton, 282 NULL, SVN_INVALID_REVNUM, 283 subpool, &new_dir_baton)); 284 } 285 else 286 { 287 SVN_ERR(add_subdir(new_source_root, target_root, 288 editor, edit_baton, new_edit_path, 289 *dir_baton, new_source_fspath, 290 authz_read_func, authz_read_baton, 291 changed_paths, subpool, &new_dir_baton)); 292 } 293 294 SVN_ERR(editor->close_directory(new_dir_baton, subpool)); 295 } 296 else if (dent->kind == svn_node_file) 297 { 298 svn_txdelta_window_handler_t delta_handler; 299 void *delta_handler_baton, *file_baton; 300 svn_txdelta_stream_t *delta_stream; 301 svn_checksum_t *checksum; 302 303 SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL, 304 SVN_INVALID_REVNUM, pool, &file_baton)); 305 306 SVN_ERR(svn_fs_node_proplist(&props, target_root, 307 new_edit_path, subpool)); 308 309 for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 310 { 311 const void *key; 312 313 apr_hash_this(phi, &key, NULL, &val); 314 SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool)); 315 } 316 317 SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool, 318 &delta_handler, 319 &delta_handler_baton)); 320 321 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL, 322 target_root, new_edit_path, 323 pool)); 324 325 SVN_ERR(svn_txdelta_send_txstream(delta_stream, 326 delta_handler, 327 delta_handler_baton, 328 pool)); 329 330 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root, 331 new_edit_path, TRUE, pool)); 332 SVN_ERR(editor->close_file(file_baton, 333 svn_checksum_to_cstring(checksum, pool), 334 pool)); 335 } 336 else 337 SVN_ERR_MALFUNCTION(); 338 } 339 340 svn_pool_destroy(subpool); 341 342 return SVN_NO_ERROR; 343} 344 345/* Given PATH deleted under ROOT, return in READABLE whether the path was 346 readable prior to the deletion. Consult COPIES (a stack of 'struct 347 copy_info') and AUTHZ_READ_FUNC. */ 348static svn_error_t * 349was_readable(svn_boolean_t *readable, 350 svn_fs_root_t *root, 351 const char *path, 352 apr_array_header_t *copies, 353 svn_repos_authz_func_t authz_read_func, 354 void *authz_read_baton, 355 apr_pool_t *result_pool, 356 apr_pool_t *scratch_pool) 357{ 358 svn_fs_root_t *inquire_root; 359 const char *inquire_path; 360 struct copy_info *info = NULL; 361 const char *relpath; 362 363 /* Short circuit. */ 364 if (! authz_read_func) 365 { 366 *readable = TRUE; 367 return SVN_NO_ERROR; 368 } 369 370 if (copies->nelts != 0) 371 info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *); 372 373 /* Are we under a copy? */ 374 if (info && (relpath = svn_relpath_skip_ancestor(info->path, path))) 375 { 376 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 377 info->copyfrom_rev, scratch_pool)); 378 inquire_path = svn_fspath__join(info->copyfrom_path, relpath, 379 scratch_pool); 380 } 381 else 382 { 383 /* Compute the revision that ROOT is based on. (Note that ROOT is not 384 r0's root, since this function is only called for deletions.) 385 ### Need a more succinct way to express this */ 386 svn_revnum_t inquire_rev = SVN_INVALID_REVNUM; 387 if (svn_fs_is_txn_root(root)) 388 inquire_rev = svn_fs_txn_root_base_revision(root); 389 if (svn_fs_is_revision_root(root)) 390 inquire_rev = svn_fs_revision_root_revision(root)-1; 391 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev)); 392 393 SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 394 inquire_rev, scratch_pool)); 395 inquire_path = path; 396 } 397 398 SVN_ERR(authz_read_func(readable, inquire_root, inquire_path, 399 authz_read_baton, result_pool)); 400 401 return SVN_NO_ERROR; 402} 403 404/* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the 405 revision root, fspath, and revnum of the copyfrom of CHANGE, which 406 corresponds to PATH under ROOT. If the copyfrom info is valid 407 (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE 408 too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided. 409 410 NOTE: If the copyfrom information in CHANGE is marked as unknown 411 (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be 412 trusted), this function will also update those members of the 413 CHANGE structure to carry accurate copyfrom information. */ 414static svn_error_t * 415fill_copyfrom(svn_fs_root_t **copyfrom_root, 416 const char **copyfrom_path, 417 svn_revnum_t *copyfrom_rev, 418 svn_boolean_t *src_readable, 419 svn_fs_root_t *root, 420 svn_fs_path_change2_t *change, 421 svn_repos_authz_func_t authz_read_func, 422 void *authz_read_baton, 423 const char *path, 424 apr_pool_t *result_pool, 425 apr_pool_t *scratch_pool) 426{ 427 if (! change->copyfrom_known) 428 { 429 SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev), 430 &(change->copyfrom_path), 431 root, path, result_pool)); 432 change->copyfrom_known = TRUE; 433 } 434 *copyfrom_rev = change->copyfrom_rev; 435 *copyfrom_path = change->copyfrom_path; 436 437 if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev)) 438 { 439 SVN_ERR(svn_fs_revision_root(copyfrom_root, 440 svn_fs_root_fs(root), 441 *copyfrom_rev, result_pool)); 442 443 if (authz_read_func) 444 { 445 SVN_ERR(authz_read_func(src_readable, *copyfrom_root, 446 *copyfrom_path, 447 authz_read_baton, result_pool)); 448 } 449 else 450 *src_readable = TRUE; 451 } 452 else 453 { 454 *copyfrom_root = NULL; 455 /* SRC_READABLE left uninitialized */ 456 } 457 return SVN_NO_ERROR; 458} 459 460static svn_error_t * 461path_driver_cb_func(void **dir_baton, 462 void *parent_baton, 463 void *callback_baton, 464 const char *edit_path, 465 apr_pool_t *pool) 466{ 467 struct path_driver_cb_baton *cb = callback_baton; 468 const svn_delta_editor_t *editor = cb->editor; 469 void *edit_baton = cb->edit_baton; 470 svn_fs_root_t *root = cb->root; 471 svn_fs_path_change2_t *change; 472 svn_boolean_t do_add = FALSE, do_delete = FALSE; 473 void *file_baton = NULL; 474 svn_revnum_t copyfrom_rev; 475 const char *copyfrom_path; 476 svn_fs_root_t *source_root = cb->compare_root; 477 const char *source_fspath = NULL; 478 const char *base_path = cb->base_path; 479 480 *dir_baton = NULL; 481 482 /* Initialize SOURCE_FSPATH. */ 483 if (source_root) 484 source_fspath = svn_fspath__canonicalize(edit_path, pool); 485 486 /* First, flush the copies stack so it only contains ancestors of path. */ 487 while (cb->copies->nelts > 0 488 && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies, 489 cb->copies->nelts - 1, 490 struct copy_info *)->path, 491 edit_path)) 492 apr_array_pop(cb->copies); 493 494 change = svn_hash_gets(cb->changed_paths, edit_path); 495 if (! change) 496 { 497 /* This can only happen if the path was removed from cb->changed_paths 498 by an earlier call to add_subdir, which means the path was already 499 handled and we should simply ignore it. */ 500 return SVN_NO_ERROR; 501 } 502 switch (change->change_kind) 503 { 504 case svn_fs_path_change_add: 505 do_add = TRUE; 506 break; 507 508 case svn_fs_path_change_delete: 509 do_delete = TRUE; 510 break; 511 512 case svn_fs_path_change_replace: 513 do_add = TRUE; 514 do_delete = TRUE; 515 break; 516 517 case svn_fs_path_change_modify: 518 default: 519 /* do nothing */ 520 break; 521 } 522 523 /* Handle any deletions. */ 524 if (do_delete) 525 { 526 svn_boolean_t readable; 527 528 /* Issue #4121: delete under under a copy, of a path that was unreadable 529 at its pre-copy location. */ 530 SVN_ERR(was_readable(&readable, root, edit_path, cb->copies, 531 cb->authz_read_func, cb->authz_read_baton, 532 pool, pool)); 533 if (readable) 534 SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 535 parent_baton, pool)); 536 } 537 538 /* Fetch the node kind if it makes sense to do so. */ 539 if (! do_delete || do_add) 540 { 541 if (change->node_kind == svn_node_unknown) 542 SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool)); 543 if ((change->node_kind != svn_node_dir) && 544 (change->node_kind != svn_node_file)) 545 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 546 _("Filesystem path '%s' is neither a file " 547 "nor a directory"), edit_path); 548 } 549 550 /* Handle any adds/opens. */ 551 if (do_add) 552 { 553 svn_boolean_t src_readable; 554 svn_fs_root_t *copyfrom_root; 555 556 /* Was this node copied? */ 557 SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev, 558 &src_readable, root, change, 559 cb->authz_read_func, cb->authz_read_baton, 560 edit_path, pool, pool)); 561 562 /* If we have a copyfrom path, and we can't read it or we're just 563 ignoring it, or the copyfrom rev is prior to the low water mark 564 then we just null them out and do a raw add with no history at 565 all. */ 566 if (copyfrom_path 567 && ((! src_readable) 568 || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL) 569 || (cb->low_water_mark > copyfrom_rev))) 570 { 571 copyfrom_path = NULL; 572 copyfrom_rev = SVN_INVALID_REVNUM; 573 } 574 575 /* Do the right thing based on the path KIND. */ 576 if (change->node_kind == svn_node_dir) 577 { 578 /* If this is a copy, but we can't represent it as such, 579 then we just do a recursive add of the source path 580 contents. */ 581 if (change->copyfrom_path && ! copyfrom_path) 582 { 583 SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton, 584 edit_path, parent_baton, change->copyfrom_path, 585 cb->authz_read_func, cb->authz_read_baton, 586 cb->changed_paths, pool, dir_baton)); 587 } 588 else 589 { 590 SVN_ERR(editor->add_directory(edit_path, parent_baton, 591 copyfrom_path, copyfrom_rev, 592 pool, dir_baton)); 593 } 594 } 595 else 596 { 597 SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path, 598 copyfrom_rev, pool, &file_baton)); 599 } 600 601 /* If we represent this as a copy... */ 602 if (copyfrom_path) 603 { 604 /* If it is a directory, make sure descendants get the correct 605 delta source by remembering that we are operating inside a 606 (possibly nested) copy operation. */ 607 if (change->node_kind == svn_node_dir) 608 { 609 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 610 611 info->path = apr_pstrdup(cb->pool, edit_path); 612 info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path); 613 info->copyfrom_rev = copyfrom_rev; 614 615 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 616 } 617 618 /* Save the source so that we can use it later, when we 619 need to generate text and prop deltas. */ 620 source_root = copyfrom_root; 621 source_fspath = copyfrom_path; 622 } 623 else 624 /* Else, we are an add without history... */ 625 { 626 /* If an ancestor is added with history, we need to forget about 627 that here, go on with life and repeat all the mistakes of our 628 past... */ 629 if (change->node_kind == svn_node_dir && cb->copies->nelts > 0) 630 { 631 struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 632 633 info->path = apr_pstrdup(cb->pool, edit_path); 634 info->copyfrom_path = NULL; 635 info->copyfrom_rev = SVN_INVALID_REVNUM; 636 637 APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 638 } 639 source_root = NULL; 640 source_fspath = NULL; 641 } 642 } 643 else if (! do_delete) 644 { 645 /* Do the right thing based on the path KIND (and the presence 646 of a PARENT_BATON). */ 647 if (change->node_kind == svn_node_dir) 648 { 649 if (parent_baton) 650 { 651 SVN_ERR(editor->open_directory(edit_path, parent_baton, 652 SVN_INVALID_REVNUM, 653 pool, dir_baton)); 654 } 655 else 656 { 657 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, 658 pool, dir_baton)); 659 } 660 } 661 else 662 { 663 SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM, 664 pool, &file_baton)); 665 } 666 /* If we are inside an add with history, we need to adjust the 667 delta source. */ 668 if (cb->copies->nelts > 0) 669 { 670 struct copy_info *info = APR_ARRAY_IDX(cb->copies, 671 cb->copies->nelts - 1, 672 struct copy_info *); 673 if (info->copyfrom_path) 674 { 675 const char *relpath = svn_relpath_skip_ancestor(info->path, 676 edit_path); 677 SVN_ERR_ASSERT(relpath && *relpath); 678 SVN_ERR(svn_fs_revision_root(&source_root, 679 svn_fs_root_fs(root), 680 info->copyfrom_rev, pool)); 681 source_fspath = svn_fspath__join(info->copyfrom_path, 682 relpath, pool); 683 } 684 else 685 { 686 /* This is an add without history, nested inside an 687 add with history. We have no delta source in this case. */ 688 source_root = NULL; 689 source_fspath = NULL; 690 } 691 } 692 } 693 694 if (! do_delete || do_add) 695 { 696 /* Is this a copy that was downgraded to a raw add? (If so, 697 we'll need to transmit properties and file contents and such 698 for it regardless of what the CHANGE structure's text_mod 699 and prop_mod flags say.) */ 700 svn_boolean_t downgraded_copy = (change->copyfrom_known 701 && change->copyfrom_path 702 && (! copyfrom_path)); 703 704 /* Handle property modifications. */ 705 if (change->prop_mod || downgraded_copy) 706 { 707 if (cb->compare_root) 708 { 709 apr_array_header_t *prop_diffs; 710 apr_hash_t *old_props; 711 apr_hash_t *new_props; 712 int i; 713 714 if (source_root) 715 SVN_ERR(svn_fs_node_proplist(&old_props, source_root, 716 source_fspath, pool)); 717 else 718 old_props = apr_hash_make(pool); 719 720 SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool)); 721 722 SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props, 723 pool)); 724 725 for (i = 0; i < prop_diffs->nelts; ++i) 726 { 727 svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 728 if (change->node_kind == svn_node_dir) 729 SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name, 730 pc->value, pool)); 731 else if (change->node_kind == svn_node_file) 732 SVN_ERR(editor->change_file_prop(file_baton, pc->name, 733 pc->value, pool)); 734 } 735 } 736 else 737 { 738 /* Just do a dummy prop change to signal that there are *any* 739 propmods. */ 740 if (change->node_kind == svn_node_dir) 741 SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL, 742 pool)); 743 else if (change->node_kind == svn_node_file) 744 SVN_ERR(editor->change_file_prop(file_baton, "", NULL, 745 pool)); 746 } 747 } 748 749 /* Handle textual modifications. */ 750 if (change->node_kind == svn_node_file 751 && (change->text_mod || downgraded_copy)) 752 { 753 svn_txdelta_window_handler_t delta_handler; 754 void *delta_handler_baton; 755 const char *hex_digest = NULL; 756 757 if (cb->compare_root && source_root && source_fspath) 758 { 759 svn_checksum_t *checksum; 760 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 761 source_root, source_fspath, TRUE, 762 pool)); 763 hex_digest = svn_checksum_to_cstring(checksum, pool); 764 } 765 766 SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool, 767 &delta_handler, 768 &delta_handler_baton)); 769 if (cb->compare_root) 770 { 771 svn_txdelta_stream_t *delta_stream; 772 773 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root, 774 source_fspath, root, 775 edit_path, pool)); 776 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, 777 delta_handler_baton, pool)); 778 } 779 else 780 SVN_ERR(delta_handler(NULL, delta_handler_baton)); 781 } 782 } 783 784 /* Close the file baton if we opened it. */ 785 if (file_baton) 786 { 787 svn_checksum_t *checksum; 788 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path, 789 TRUE, pool)); 790 SVN_ERR(editor->close_file(file_baton, 791 svn_checksum_to_cstring(checksum, pool), 792 pool)); 793 } 794 795 return SVN_NO_ERROR; 796} 797 798#ifdef USE_EV2_IMPL 799static svn_error_t * 800fetch_kind_func(svn_node_kind_t *kind, 801 void *baton, 802 const char *path, 803 svn_revnum_t base_revision, 804 apr_pool_t *scratch_pool) 805{ 806 svn_fs_root_t *root = baton; 807 svn_fs_root_t *prev_root; 808 svn_fs_t *fs = svn_fs_root_fs(root); 809 810 if (!SVN_IS_VALID_REVNUM(base_revision)) 811 base_revision = svn_fs_revision_root_revision(root) - 1; 812 813 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 814 SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool)); 815 816 return SVN_NO_ERROR; 817} 818 819static svn_error_t * 820fetch_props_func(apr_hash_t **props, 821 void *baton, 822 const char *path, 823 svn_revnum_t base_revision, 824 apr_pool_t *result_pool, 825 apr_pool_t *scratch_pool) 826{ 827 svn_fs_root_t *root = baton; 828 svn_fs_root_t *prev_root; 829 svn_fs_t *fs = svn_fs_root_fs(root); 830 831 if (!SVN_IS_VALID_REVNUM(base_revision)) 832 base_revision = svn_fs_revision_root_revision(root) - 1; 833 834 SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 835 SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool)); 836 837 return SVN_NO_ERROR; 838} 839#endif 840 841 842 843 844svn_error_t * 845svn_repos_replay2(svn_fs_root_t *root, 846 const char *base_path, 847 svn_revnum_t low_water_mark, 848 svn_boolean_t send_deltas, 849 const svn_delta_editor_t *editor, 850 void *edit_baton, 851 svn_repos_authz_func_t authz_read_func, 852 void *authz_read_baton, 853 apr_pool_t *pool) 854{ 855#ifndef USE_EV2_IMPL 856 apr_hash_t *fs_changes; 857 apr_hash_t *changed_paths; 858 apr_hash_index_t *hi; 859 apr_array_header_t *paths; 860 struct path_driver_cb_baton cb_baton; 861 862 /* Special-case r0, which we know is an empty revision; if we don't 863 special-case it we might end up trying to compare it to "r-1". */ 864 if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0) 865 { 866 SVN_ERR(editor->set_target_revision(edit_baton, 0, pool)); 867 return SVN_NO_ERROR; 868 } 869 870 /* Fetch the paths changed under ROOT. */ 871 SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool)); 872 873 if (! base_path) 874 base_path = ""; 875 else if (base_path[0] == '/') 876 ++base_path; 877 878 /* Make an array from the keys of our CHANGED_PATHS hash, and copy 879 the values into a new hash whose keys have no leading slashes. */ 880 paths = apr_array_make(pool, apr_hash_count(fs_changes), 881 sizeof(const char *)); 882 changed_paths = apr_hash_make(pool); 883 for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi)) 884 { 885 const void *key; 886 void *val; 887 apr_ssize_t keylen; 888 const char *path; 889 svn_fs_path_change2_t *change; 890 svn_boolean_t allowed = TRUE; 891 892 apr_hash_this(hi, &key, &keylen, &val); 893 path = key; 894 change = val; 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 = svn__apr_hash_index_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, props, checksum, 1461 contents)); 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 void *key; 1518 void *val; 1519 apr_ssize_t keylen; 1520 const char *path; 1521 svn_fs_path_change2_t *change; 1522 svn_boolean_t allowed = TRUE; 1523 1524 apr_hash_this(hi, &key, &keylen, &val); 1525 path = key; 1526 change = val; 1527 1528 if (authz_read_func) 1529 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, 1530 scratch_pool)); 1531 1532 if (allowed) 1533 { 1534 if (path[0] == '/') 1535 { 1536 path++; 1537 keylen--; 1538 } 1539 1540 /* If the base_path doesn't match the top directory of this path 1541 we don't want anything to do with it... */ 1542 if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL) 1543 { 1544 APR_ARRAY_PUSH(paths, const char *) = path; 1545 apr_hash_set(changed_paths, path, keylen, change); 1546 } 1547 /* ...unless this was a change to one of the parent directories of 1548 base_path. */ 1549 else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL) 1550 { 1551 APR_ARRAY_PUSH(paths, const char *) = path; 1552 apr_hash_set(changed_paths, path, keylen, change); 1553 } 1554 } 1555 } 1556 1557 /* If we were not given a low water mark, assume that everything is there, 1558 all the way back to revision 0. */ 1559 if (! SVN_IS_VALID_REVNUM(low_water_mark)) 1560 low_water_mark = 0; 1561 1562 copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *)); 1563 1564 /* Sort the paths. Although not strictly required by the API, this has 1565 the pleasant side effect of maintaining a consistent ordering of 1566 dumpfile contents. */ 1567 qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths); 1568 1569 /* Now actually handle the various paths. */ 1570 iterpool = svn_pool_create(scratch_pool); 1571 for (i = 0; i < paths->nelts; i++) 1572 { 1573 const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *); 1574 1575 svn_pool_clear(iterpool); 1576 err = replay_node(root, repos_relpath, editor, low_water_mark, 1577 base_repos_relpath, copies, changed_paths, 1578 authz_read_func, authz_read_baton, 1579 scratch_pool, iterpool); 1580 if (err) 1581 break; 1582 } 1583 1584 if (err) 1585 return svn_error_compose_create(err, svn_editor_abort(editor)); 1586 else 1587 SVN_ERR(svn_editor_complete(editor)); 1588 1589 svn_pool_destroy(iterpool); 1590 return SVN_NO_ERROR; 1591} 1592