1/* rev_hunt.c --- routines to hunt down particular fs revisions and 2 * their properties. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25#include <string.h> 26#include "svn_compat.h" 27#include "svn_private_config.h" 28#include "svn_hash.h" 29#include "svn_pools.h" 30#include "svn_error.h" 31#include "svn_error_codes.h" 32#include "svn_fs.h" 33#include "svn_repos.h" 34#include "svn_string.h" 35#include "svn_time.h" 36#include "svn_sorts.h" 37#include "svn_props.h" 38#include "svn_mergeinfo.h" 39#include "repos.h" 40#include "private/svn_fspath.h" 41#include "private/svn_fs_private.h" 42#include "private/svn_sorts_private.h" 43 44 45/* Note: this binary search assumes that the datestamp properties on 46 each revision are in chronological order. That is if revision A > 47 revision B, then A's datestamp is younger than B's datestamp. 48 49 If someone comes along and sets a bogus datestamp, this routine 50 might not work right. 51 52 ### todo: you know, we *could* have svn_fs_change_rev_prop() do 53 some semantic checking when it's asked to change special reserved 54 svn: properties. It could prevent such a problem. */ 55 56 57/* helper for svn_repos_dated_revision(). 58 59 Set *TM to the apr_time_t datestamp on revision REV in FS. */ 60static svn_error_t * 61get_time(apr_time_t *tm, 62 svn_fs_t *fs, 63 svn_revnum_t rev, 64 apr_pool_t *pool) 65{ 66 svn_string_t *date_str; 67 68 SVN_ERR(svn_fs_revision_prop2(&date_str, fs, rev, SVN_PROP_REVISION_DATE, 69 FALSE, pool, pool)); 70 if (! date_str) 71 return svn_error_createf 72 (SVN_ERR_FS_GENERAL, NULL, 73 _("Failed to find time on revision %ld"), rev); 74 75 return svn_time_from_cstring(tm, date_str->data, pool); 76} 77 78 79svn_error_t * 80svn_repos_dated_revision(svn_revnum_t *revision, 81 svn_repos_t *repos, 82 apr_time_t tm, 83 apr_pool_t *pool) 84{ 85 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest; 86 apr_time_t this_time; 87 svn_fs_t *fs = repos->fs; 88 89 /* Initialize top and bottom values of binary search. */ 90 SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool)); 91 SVN_ERR(svn_fs_refresh_revision_props(fs, pool)); 92 rev_bot = 0; 93 rev_top = rev_latest; 94 95 while (rev_bot <= rev_top) 96 { 97 rev_mid = (rev_top + rev_bot) / 2; 98 SVN_ERR(get_time(&this_time, fs, rev_mid, pool)); 99 100 if (this_time > tm)/* we've overshot */ 101 { 102 apr_time_t previous_time; 103 104 if ((rev_mid - 1) < 0) 105 { 106 *revision = 0; 107 break; 108 } 109 110 /* see if time falls between rev_mid and rev_mid-1: */ 111 SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool)); 112 if (previous_time <= tm) 113 { 114 *revision = rev_mid - 1; 115 break; 116 } 117 118 rev_top = rev_mid - 1; 119 } 120 121 else if (this_time < tm) /* we've undershot */ 122 { 123 apr_time_t next_time; 124 125 if ((rev_mid + 1) > rev_latest) 126 { 127 *revision = rev_latest; 128 break; 129 } 130 131 /* see if time falls between rev_mid and rev_mid+1: */ 132 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool)); 133 if (next_time > tm) 134 { 135 *revision = rev_mid; 136 break; 137 } 138 139 rev_bot = rev_mid + 1; 140 } 141 142 else 143 { 144 *revision = rev_mid; /* exact match! */ 145 break; 146 } 147 } 148 149 return SVN_NO_ERROR; 150} 151 152 153svn_error_t * 154svn_repos_get_committed_info(svn_revnum_t *committed_rev, 155 const char **committed_date, 156 const char **last_author, 157 svn_fs_root_t *root, 158 const char *path, 159 apr_pool_t *pool) 160{ 161 apr_hash_t *revprops; 162 163 svn_fs_t *fs = svn_fs_root_fs(root); 164 165 /* ### It might be simpler just to declare that revision 166 properties have char * (i.e., UTF-8) values, not arbitrary 167 binary values, hmmm. */ 168 svn_string_t *committed_date_s, *last_author_s; 169 170 /* Get the CR field out of the node's skel. */ 171 SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool)); 172 173 /* Get the revision properties of this revision. */ 174 SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, *committed_rev, TRUE, 175 pool, pool)); 176 177 /* Extract date and author from these revprops. */ 178 committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE); 179 last_author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); 180 181 *committed_date = committed_date_s ? committed_date_s->data : NULL; 182 *last_author = last_author_s ? last_author_s->data : NULL; 183 184 return SVN_NO_ERROR; 185} 186 187svn_error_t * 188svn_repos_history2(svn_fs_t *fs, 189 const char *path, 190 svn_repos_history_func_t history_func, 191 void *history_baton, 192 svn_repos_authz_func_t authz_read_func, 193 void *authz_read_baton, 194 svn_revnum_t start, 195 svn_revnum_t end, 196 svn_boolean_t cross_copies, 197 apr_pool_t *pool) 198{ 199 svn_fs_history_t *history; 200 apr_pool_t *oldpool = svn_pool_create(pool); 201 apr_pool_t *newpool = svn_pool_create(pool); 202 const char *history_path; 203 svn_revnum_t history_rev; 204 svn_fs_root_t *root; 205 206 /* Validate the revisions. */ 207 if (! SVN_IS_VALID_REVNUM(start)) 208 return svn_error_createf 209 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 210 _("Invalid start revision %ld"), start); 211 if (! SVN_IS_VALID_REVNUM(end)) 212 return svn_error_createf 213 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 214 _("Invalid end revision %ld"), end); 215 216 /* Ensure that the input is ordered. */ 217 if (start > end) 218 { 219 svn_revnum_t tmprev = start; 220 start = end; 221 end = tmprev; 222 } 223 224 /* Get a revision root for END, and an initial HISTORY baton. */ 225 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); 226 227 if (authz_read_func) 228 { 229 svn_boolean_t readable; 230 SVN_ERR(authz_read_func(&readable, root, path, 231 authz_read_baton, pool)); 232 if (! readable) 233 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 234 } 235 236 SVN_ERR(svn_fs_node_history2(&history, root, path, oldpool, oldpool)); 237 238 /* Now, we loop over the history items, calling svn_fs_history_prev(). */ 239 do 240 { 241 /* Note that we have to do some crazy pool work here. We can't 242 get rid of the old history until we use it to get the new, so 243 we alternate back and forth between our subpools. */ 244 apr_pool_t *tmppool; 245 svn_error_t *err; 246 247 SVN_ERR(svn_fs_history_prev2(&history, history, cross_copies, newpool, 248 oldpool)); 249 250 /* Only continue if there is further history to deal with. */ 251 if (! history) 252 break; 253 254 /* Fetch the location information for this history step. */ 255 SVN_ERR(svn_fs_history_location(&history_path, &history_rev, 256 history, newpool)); 257 258 /* If this history item predates our START revision, quit 259 here. */ 260 if (history_rev < start) 261 break; 262 263 /* Is the history item readable? If not, quit. */ 264 if (authz_read_func) 265 { 266 svn_boolean_t readable; 267 svn_fs_root_t *history_root; 268 SVN_ERR(svn_fs_revision_root(&history_root, fs, 269 history_rev, newpool)); 270 SVN_ERR(authz_read_func(&readable, history_root, history_path, 271 authz_read_baton, newpool)); 272 if (! readable) 273 break; 274 } 275 276 /* Call the user-provided callback function. */ 277 err = history_func(history_baton, history_path, history_rev, newpool); 278 if (err) 279 { 280 if (err->apr_err == SVN_ERR_CEASE_INVOCATION) 281 { 282 svn_error_clear(err); 283 goto cleanup; 284 } 285 else 286 { 287 return svn_error_trace(err); 288 } 289 } 290 291 /* We're done with the old history item, so we can clear its 292 pool, and then toggle our notion of "the old pool". */ 293 svn_pool_clear(oldpool); 294 tmppool = oldpool; 295 oldpool = newpool; 296 newpool = tmppool; 297 } 298 while (history); /* shouldn't hit this */ 299 300 cleanup: 301 svn_pool_destroy(oldpool); 302 svn_pool_destroy(newpool); 303 return SVN_NO_ERROR; 304} 305 306 307svn_error_t * 308svn_repos_deleted_rev(svn_fs_t *fs, 309 const char *path, 310 svn_revnum_t start, 311 svn_revnum_t end, 312 svn_revnum_t *deleted, 313 apr_pool_t *pool) 314{ 315 apr_pool_t *iterpool; 316 svn_fs_root_t *start_root, *root; 317 svn_revnum_t mid_rev; 318 svn_node_kind_t kind; 319 svn_fs_node_relation_t node_relation; 320 321 /* Validate the revision range. */ 322 if (! SVN_IS_VALID_REVNUM(start)) 323 return svn_error_createf 324 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 325 _("Invalid start revision %ld"), start); 326 if (! SVN_IS_VALID_REVNUM(end)) 327 return svn_error_createf 328 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 329 _("Invalid end revision %ld"), end); 330 331 /* Ensure that the input is ordered. */ 332 if (start > end) 333 { 334 svn_revnum_t tmprev = start; 335 start = end; 336 end = tmprev; 337 } 338 339 /* Ensure path exists in fs at start revision. */ 340 SVN_ERR(svn_fs_revision_root(&start_root, fs, start, pool)); 341 SVN_ERR(svn_fs_check_path(&kind, start_root, path, pool)); 342 if (kind == svn_node_none) 343 { 344 /* Path must exist in fs at start rev. */ 345 *deleted = SVN_INVALID_REVNUM; 346 return SVN_NO_ERROR; 347 } 348 349 /* Ensure path was deleted at or before end revision. */ 350 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); 351 SVN_ERR(svn_fs_check_path(&kind, root, path, pool)); 352 if (kind != svn_node_none) 353 { 354 /* path exists in the end node and the end node is equivalent 355 or otherwise equivalent to the start node. This can mean 356 a few things: 357 358 1) The end node *is* simply the start node, uncopied 359 and unmodified in the start to end range. 360 361 2) The start node was modified, but never copied. 362 363 3) The start node was copied, but this copy occurred at 364 start or some rev *previous* to start, this is 365 effectively the same situation as 1 if the node was 366 never modified or 2 if it was. 367 368 In the first three cases the path was not deleted in 369 the specified range and we are done, but in the following 370 cases the start node must have been deleted at least once: 371 372 4) The start node was deleted and replaced by a copy of 373 itself at some rev between start and end. This copy 374 may itself have been replaced with copies of itself. 375 376 5) The start node was deleted and replaced by a node which 377 it does not share any history with. 378 */ 379 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path, 380 root, path, pool)); 381 if (node_relation != svn_fs_node_unrelated) 382 { 383 svn_fs_root_t *copy_root; 384 const char *copy_path; 385 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, 386 path, pool)); 387 if (!copy_root || 388 (svn_fs_revision_root_revision(copy_root) <= start)) 389 { 390 /* Case 1,2 or 3, nothing more to do. */ 391 *deleted = SVN_INVALID_REVNUM; 392 return SVN_NO_ERROR; 393 } 394 } 395 } 396 397 /* If we get here we know that path exists in rev start and was deleted 398 at least once before rev end. To find the revision path was first 399 deleted we use a binary search. The rules for the determining if 400 the deletion comes before or after a given median revision are 401 described by this matrix: 402 403 | Most recent copy event that 404 | caused mid node to exist. 405 |----------------------------------------------------- 406 Compare path | | | | 407 at start and | Copied at | Copied at | Never copied | 408 mid nodes. | rev > start | rev <= start | | 409 | | | | 410 -------------------------------------------------------------------| 411 Mid node is | A) Start node | | 412 equivalent to | replaced with | E) Mid node == start node, | 413 start node | an unmodified | look HIGHER. | 414 | copy of | | 415 | itself, | | 416 | look LOWER. | | 417 -------------------------------------------------------------------| 418 Mid node is | B) Start node | | 419 otherwise | replaced with | F) Mid node is a modified | 420 related to | a modified | version of start node, | 421 start node | copy of | look HIGHER. | 422 | itself, | | 423 | look LOWER. | | 424 -------------------------------------------------------------------| 425 Mid node is | | 426 unrelated to | C) Start node replaced with unrelated mid node, | 427 start node | look LOWER. | 428 | | 429 -------------------------------------------------------------------| 430 Path doesn't | | 431 exist at mid | D) Start node deleted before mid node, | 432 node | look LOWER | 433 | | 434 -------------------------------------------------------------------- 435 */ 436 437 mid_rev = (start + end) / 2; 438 iterpool = svn_pool_create(pool); 439 440 while (1) 441 { 442 svn_pool_clear(iterpool); 443 444 /* Get revision root and node id for mid_rev at that revision. */ 445 SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, iterpool)); 446 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool)); 447 if (kind == svn_node_none) 448 { 449 /* Case D: Look lower in the range. */ 450 end = mid_rev; 451 mid_rev = (start + mid_rev) / 2; 452 } 453 else 454 { 455 svn_fs_root_t *copy_root; 456 const char *copy_path; 457 /* Determine the relationship between the start node 458 and the current node. */ 459 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path, 460 root, path, iterpool)); 461 if (node_relation != svn_fs_node_unrelated) 462 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, 463 path, iterpool)); 464 if (node_relation == svn_fs_node_unrelated || 465 (copy_root && 466 (svn_fs_revision_root_revision(copy_root) > start))) 467 { 468 /* Cases A, B, C: Look at lower revs. */ 469 end = mid_rev; 470 mid_rev = (start + mid_rev) / 2; 471 } 472 else if (end - mid_rev == 1) 473 { 474 /* Found the node path was deleted. */ 475 *deleted = end; 476 break; 477 } 478 else 479 { 480 /* Cases E, F: Look at higher revs. */ 481 start = mid_rev; 482 mid_rev = (start + end) / 2; 483 } 484 } 485 } 486 487 svn_pool_destroy(iterpool); 488 return SVN_NO_ERROR; 489} 490 491 492/* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is 493 unreadable. */ 494static svn_error_t * 495check_readability(svn_fs_root_t *root, 496 const char *path, 497 svn_repos_authz_func_t authz_read_func, 498 void *authz_read_baton, 499 apr_pool_t *pool) 500{ 501 svn_boolean_t readable; 502 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool)); 503 if (! readable) 504 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, 505 _("Unreadable path encountered; access denied")); 506 return SVN_NO_ERROR; 507} 508 509 510/* The purpose of this function is to discover if fs_path@future_rev 511 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */ 512 513static svn_error_t * 514check_ancestry_of_peg_path(svn_boolean_t *is_ancestor, 515 svn_fs_t *fs, 516 const char *fs_path, 517 svn_revnum_t peg_revision, 518 svn_revnum_t future_revision, 519 apr_pool_t *pool) 520{ 521 svn_fs_root_t *root; 522 svn_fs_history_t *history; 523 const char *path = NULL; 524 svn_revnum_t revision; 525 apr_pool_t *lastpool, *currpool; 526 527 lastpool = svn_pool_create(pool); 528 currpool = svn_pool_create(pool); 529 530 SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool)); 531 532 SVN_ERR(svn_fs_node_history2(&history, root, fs_path, lastpool, lastpool)); 533 534 /* Since paths that are different according to strcmp may still be 535 equivalent (due to number of consecutive slashes and the fact that 536 "" is the same as "/"), we get the "canonical" path in the first 537 iteration below so that the comparison after the loop will work 538 correctly. */ 539 fs_path = NULL; 540 541 while (1) 542 { 543 apr_pool_t *tmppool; 544 545 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, currpool, 546 lastpool)); 547 548 if (!history) 549 break; 550 551 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool)); 552 553 if (!fs_path) 554 fs_path = apr_pstrdup(pool, path); 555 556 if (revision <= peg_revision) 557 break; 558 559 /* Clear old pool and flip. */ 560 svn_pool_clear(lastpool); 561 tmppool = lastpool; 562 lastpool = currpool; 563 currpool = tmppool; 564 } 565 566 /* We must have had at least one iteration above where we 567 reassigned fs_path. Else, the path wouldn't have existed at 568 future_revision and svn_fs_history would have thrown. */ 569 SVN_ERR_ASSERT(fs_path != NULL); 570 571 *is_ancestor = (history && strcmp(path, fs_path) == 0); 572 573 return SVN_NO_ERROR; 574} 575 576 577svn_error_t * 578svn_repos__prev_location(svn_revnum_t *appeared_rev, 579 const char **prev_path, 580 svn_revnum_t *prev_rev, 581 svn_fs_t *fs, 582 svn_revnum_t revision, 583 const char *path, 584 apr_pool_t *pool) 585{ 586 svn_fs_root_t *root, *copy_root; 587 const char *copy_path, *copy_src_path, *remainder; 588 svn_revnum_t copy_src_rev; 589 590 /* Initialize return variables. */ 591 if (appeared_rev) 592 *appeared_rev = SVN_INVALID_REVNUM; 593 if (prev_rev) 594 *prev_rev = SVN_INVALID_REVNUM; 595 if (prev_path) 596 *prev_path = NULL; 597 598 /* Ask about the most recent copy which affected PATH@REVISION. If 599 there was no such copy, we're done. */ 600 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); 601 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool)); 602 if (! copy_root) 603 return SVN_NO_ERROR; 604 605 /* Ultimately, it's not the path of the closest copy's source that 606 we care about -- it's our own path's location in the copy source 607 revision. So we'll tack the relative path that expresses the 608 difference between the copy destination and our path in the copy 609 revision onto the copy source path to determine this information. 610 611 In other words, if our path is "/branches/my-branch/foo/bar", and 612 we know that the closest relevant copy was a copy of "/trunk" to 613 "/branches/my-branch", then that relative path under the copy 614 destination is "/foo/bar". Tacking that onto the copy source 615 path tells us that our path was located at "/trunk/foo/bar" 616 before the copy. 617 */ 618 SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path, 619 copy_root, copy_path, pool)); 620 remainder = svn_fspath__skip_ancestor(copy_path, path); 621 if (prev_path) 622 *prev_path = svn_fspath__join(copy_src_path, remainder, pool); 623 if (appeared_rev) 624 *appeared_rev = svn_fs_revision_root_revision(copy_root); 625 if (prev_rev) 626 *prev_rev = copy_src_rev; 627 return SVN_NO_ERROR; 628} 629 630 631svn_error_t * 632svn_repos_trace_node_locations(svn_fs_t *fs, 633 apr_hash_t **locations, 634 const char *fs_path, 635 svn_revnum_t peg_revision, 636 const apr_array_header_t *location_revisions_orig, 637 svn_repos_authz_func_t authz_read_func, 638 void *authz_read_baton, 639 apr_pool_t *pool) 640{ 641 apr_array_header_t *location_revisions; 642 svn_revnum_t *revision_ptr, *revision_ptr_end; 643 svn_fs_root_t *root; 644 const char *path; 645 svn_revnum_t revision; 646 svn_boolean_t is_ancestor; 647 apr_pool_t *lastpool, *currpool; 648 649 SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t)); 650 651 /* Ensure that FS_PATH is absolute, because our path-math below will 652 depend on that being the case. */ 653 if (*fs_path != '/') 654 fs_path = apr_pstrcat(pool, "/", fs_path, SVN_VA_NULL); 655 656 /* Another sanity check. */ 657 if (authz_read_func) 658 { 659 svn_fs_root_t *peg_root; 660 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); 661 SVN_ERR(check_readability(peg_root, fs_path, 662 authz_read_func, authz_read_baton, pool)); 663 } 664 665 *locations = apr_hash_make(pool); 666 667 /* We flip between two pools in the second loop below. */ 668 lastpool = svn_pool_create(pool); 669 currpool = svn_pool_create(pool); 670 671 /* First - let's sort the array of the revisions from the greatest revision 672 * downward, so it will be easier to search on. */ 673 location_revisions = apr_array_copy(pool, location_revisions_orig); 674 svn_sort__array(location_revisions, svn_sort_compare_revisions); 675 676 revision_ptr = (svn_revnum_t *)location_revisions->elts; 677 revision_ptr_end = revision_ptr + location_revisions->nelts; 678 679 /* Ignore revisions R that are younger than the peg_revisions where 680 path@peg_revision is not an ancestor of path@R. */ 681 is_ancestor = FALSE; 682 while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision) 683 { 684 svn_pool_clear(currpool); 685 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path, 686 peg_revision, *revision_ptr, 687 currpool)); 688 if (is_ancestor) 689 break; 690 ++revision_ptr; 691 } 692 693 revision = is_ancestor ? *revision_ptr : peg_revision; 694 path = fs_path; 695 if (authz_read_func) 696 { 697 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); 698 SVN_ERR(check_readability(root, fs_path, authz_read_func, 699 authz_read_baton, pool)); 700 } 701 702 while (revision_ptr < revision_ptr_end) 703 { 704 apr_pool_t *tmppool; 705 svn_revnum_t appeared_rev, prev_rev; 706 const char *prev_path; 707 708 /* Find the target of the innermost copy relevant to path@revision. 709 The copy may be of path itself, or of a parent directory. */ 710 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 711 fs, revision, path, currpool)); 712 if (! prev_path) 713 break; 714 715 /* Assign the current path to all younger revisions until we reach 716 the copy target rev. */ 717 while ((revision_ptr < revision_ptr_end) 718 && (*revision_ptr >= appeared_rev)) 719 { 720 /* *revision_ptr is allocated out of pool, so we can point 721 to in the hash table. */ 722 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 723 apr_pstrdup(pool, path)); 724 revision_ptr++; 725 } 726 727 /* Ignore all revs between the copy target rev and the copy 728 source rev (non-inclusive). */ 729 while ((revision_ptr < revision_ptr_end) 730 && (*revision_ptr > prev_rev)) 731 revision_ptr++; 732 733 /* State update. */ 734 path = prev_path; 735 revision = prev_rev; 736 737 if (authz_read_func) 738 { 739 svn_boolean_t readable; 740 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); 741 SVN_ERR(authz_read_func(&readable, root, path, 742 authz_read_baton, currpool)); 743 if (!readable) 744 { 745 svn_pool_destroy(lastpool); 746 svn_pool_destroy(currpool); 747 return SVN_NO_ERROR; 748 } 749 } 750 751 /* Clear last pool and switch. */ 752 svn_pool_clear(lastpool); 753 tmppool = lastpool; 754 lastpool = currpool; 755 currpool = tmppool; 756 } 757 758 /* There are no copies relevant to path@revision. So any remaining 759 revisions either predate the creation of path@revision or have 760 the node existing at the same path. We will look up path@lrev 761 for each remaining location-revision and make sure it is related 762 to path@revision. */ 763 SVN_ERR(svn_fs_revision_root(&root, fs, revision, lastpool)); 764 while (revision_ptr < revision_ptr_end) 765 { 766 svn_node_kind_t kind; 767 svn_fs_node_relation_t node_relation; 768 svn_fs_root_t *cur_rev_root; 769 770 svn_pool_clear(currpool); 771 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, *revision_ptr, 772 currpool)); 773 SVN_ERR(svn_fs_check_path(&kind, cur_rev_root, path, currpool)); 774 if (kind == svn_node_none) 775 break; 776 SVN_ERR(svn_fs_node_relation(&node_relation, root, path, 777 cur_rev_root, path, currpool)); 778 if (node_relation == svn_fs_node_unrelated) 779 break; 780 781 /* The node exists at the same path; record that and advance. */ 782 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 783 apr_pstrdup(pool, path)); 784 revision_ptr++; 785 } 786 787 /* Ignore any remaining location-revisions; they predate the 788 creation of path@revision. */ 789 790 svn_pool_destroy(lastpool); 791 svn_pool_destroy(currpool); 792 793 return SVN_NO_ERROR; 794} 795 796 797/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of 798 its revision range fits between END_REV and START_REV, possibly 799 cropping the range so that it fits *entirely* in that range. */ 800static svn_error_t * 801maybe_crop_and_send_segment(svn_location_segment_t *segment, 802 svn_revnum_t start_rev, 803 svn_revnum_t end_rev, 804 svn_location_segment_receiver_t receiver, 805 void *receiver_baton, 806 apr_pool_t *pool) 807{ 808 /* We only want to transmit this segment if some portion of it 809 is between our END_REV and START_REV. */ 810 if (! ((segment->range_start > start_rev) 811 || (segment->range_end < end_rev))) 812 { 813 /* Correct our segment range when the range straddles one of 814 our requested revision boundaries. */ 815 if (segment->range_start < end_rev) 816 segment->range_start = end_rev; 817 if (segment->range_end > start_rev) 818 segment->range_end = start_rev; 819 SVN_ERR(receiver(segment, receiver_baton, pool)); 820 } 821 return SVN_NO_ERROR; 822} 823 824 825svn_error_t * 826svn_repos_node_location_segments(svn_repos_t *repos, 827 const char *path, 828 svn_revnum_t peg_revision, 829 svn_revnum_t start_rev, 830 svn_revnum_t end_rev, 831 svn_location_segment_receiver_t receiver, 832 void *receiver_baton, 833 svn_repos_authz_func_t authz_read_func, 834 void *authz_read_baton, 835 apr_pool_t *pool) 836{ 837 svn_fs_t *fs = svn_repos_fs(repos); 838 svn_stringbuf_t *current_path; 839 svn_revnum_t youngest_rev, current_rev; 840 apr_pool_t *subpool; 841 842 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); 843 844 /* No PEG_REVISION? We'll use HEAD. */ 845 if (! SVN_IS_VALID_REVNUM(peg_revision)) 846 peg_revision = youngest_rev; 847 848 if (peg_revision > youngest_rev) 849 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 850 _("No such revision %ld"), peg_revision); 851 852 /* No START_REV? We'll use peg rev. */ 853 if (! SVN_IS_VALID_REVNUM(start_rev)) 854 start_rev = peg_revision; 855 else if (start_rev > peg_revision) 856 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 857 _("No such revision %ld"), start_rev); 858 859 /* No END_REV? We'll use 0. */ 860 if (! SVN_IS_VALID_REVNUM(end_rev)) 861 end_rev = 0; 862 else if (end_rev > start_rev) 863 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 864 _("No such revision %ld"), end_rev); 865 866 /* Are the revision properly ordered? They better be -- the API 867 demands it. */ 868 SVN_ERR_ASSERT(end_rev <= start_rev); 869 SVN_ERR_ASSERT(start_rev <= peg_revision); 870 871 /* Ensure that PATH is absolute, because our path-math will depend 872 on that being the case. */ 873 if (*path != '/') 874 path = apr_pstrcat(pool, "/", path, SVN_VA_NULL); 875 876 /* Auth check. */ 877 if (authz_read_func) 878 { 879 svn_fs_root_t *peg_root; 880 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); 881 SVN_ERR(check_readability(peg_root, path, 882 authz_read_func, authz_read_baton, pool)); 883 } 884 885 /* Okay, let's get searching! */ 886 subpool = svn_pool_create(pool); 887 current_rev = peg_revision; 888 current_path = svn_stringbuf_create(path, pool); 889 while (current_rev >= end_rev) 890 { 891 svn_revnum_t appeared_rev, prev_rev; 892 const char *cur_path, *prev_path; 893 svn_location_segment_t *segment; 894 895 svn_pool_clear(subpool); 896 897 cur_path = apr_pstrmemdup(subpool, current_path->data, 898 current_path->len); 899 segment = apr_pcalloc(subpool, sizeof(*segment)); 900 segment->range_end = current_rev; 901 segment->range_start = end_rev; 902 /* segment path should be absolute without leading '/'. */ 903 segment->path = cur_path + 1; 904 905 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 906 fs, current_rev, cur_path, subpool)); 907 908 /* If there are no previous locations for this thing (meaning, 909 it originated at the current path), then we simply need to 910 find its revision of origin to populate our final segment. 911 Otherwise, the APPEARED_REV is the start of current segment's 912 range. */ 913 if (! prev_path) 914 { 915 svn_fs_root_t *revroot; 916 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool)); 917 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot, 918 cur_path, subpool)); 919 if (segment->range_start < end_rev) 920 segment->range_start = end_rev; 921 current_rev = SVN_INVALID_REVNUM; 922 } 923 else 924 { 925 segment->range_start = appeared_rev; 926 svn_stringbuf_set(current_path, prev_path); 927 current_rev = prev_rev; 928 } 929 930 /* Report our segment, providing it passes authz muster. */ 931 if (authz_read_func) 932 { 933 svn_boolean_t readable; 934 svn_fs_root_t *cur_rev_root; 935 936 /* authz_read_func requires path to have a leading slash. */ 937 const char *abs_path = apr_pstrcat(subpool, "/", segment->path, 938 SVN_VA_NULL); 939 940 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, 941 segment->range_end, subpool)); 942 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path, 943 authz_read_baton, subpool)); 944 if (! readable) 945 return SVN_NO_ERROR; 946 } 947 948 /* Transmit the segment (if it's within the scope of our concern). */ 949 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev, 950 receiver, receiver_baton, subpool)); 951 952 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done 953 (and didn't ever reach END_REV). */ 954 if (! SVN_IS_VALID_REVNUM(current_rev)) 955 break; 956 957 /* If there's a gap in the history, we need to report as much 958 (if the gap is within the scope of our concern). */ 959 if (segment->range_start - current_rev > 1) 960 { 961 svn_location_segment_t *gap_segment; 962 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment)); 963 gap_segment->range_end = segment->range_start - 1; 964 gap_segment->range_start = current_rev + 1; 965 gap_segment->path = NULL; 966 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev, 967 receiver, receiver_baton, 968 subpool)); 969 } 970 } 971 svn_pool_destroy(subpool); 972 return SVN_NO_ERROR; 973} 974 975static APR_INLINE svn_boolean_t 976is_path_in_hash(apr_hash_t *duplicate_path_revs, 977 const char *path, 978 svn_revnum_t revision, 979 apr_pool_t *pool) 980{ 981 const char *key = apr_psprintf(pool, "%s:%ld", path, revision); 982 void *ptr; 983 984 ptr = svn_hash_gets(duplicate_path_revs, key); 985 return ptr != NULL; 986} 987 988struct path_revision 989{ 990 svn_revnum_t revnum; 991 const char *path; 992 993 /* Does this path_rev have merges to also be included? If so, this is 994 the union of both additions and (negated) deletions of mergeinfo. */ 995 apr_hash_t *merged_mergeinfo; 996 997 /* Is this a merged revision? */ 998 svn_boolean_t merged; 999}; 1000 1001/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store 1002 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The 1003 difference is the union of both additions and (negated) deletions. The 1004 returned *MERGED_MERGEINFO will be NULL if there are no changes. */ 1005static svn_error_t * 1006get_merged_mergeinfo(apr_hash_t **merged_mergeinfo, 1007 svn_repos_t *repos, 1008 struct path_revision *old_path_rev, 1009 apr_pool_t *result_pool, 1010 apr_pool_t *scratch_pool) 1011{ 1012 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed; 1013 svn_error_t *err; 1014 svn_fs_root_t *root, *prev_root; 1015 const char *start_path = old_path_rev->path; 1016 const char *path = NULL; 1017 1018 svn_fs_path_change_iterator_t *iterator; 1019 svn_fs_path_change3_t *change; 1020 1021 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it 1022 if there is a property change. */ 1023 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum, 1024 scratch_pool)); 1025 SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool)); 1026 SVN_ERR(svn_fs_path_change_get(&change, iterator)); 1027 1028 /* Find the changed PATH closest to START_PATH which may have a mergeinfo 1029 * change. */ 1030 while (change) 1031 { 1032 if ( change->prop_mod 1033 && change->mergeinfo_mod != svn_tristate_false 1034 && svn_fspath__skip_ancestor(change->path.data, start_path)) 1035 { 1036 if (!path || svn_fspath__skip_ancestor(path, change->path.data)) 1037 path = apr_pstrmemdup(scratch_pool, change->path.data, 1038 change->path.len); 1039 } 1040 1041 SVN_ERR(svn_fs_path_change_get(&change, iterator)); 1042 } 1043 1044 if (path == NULL) 1045 { 1046 *merged_mergeinfo = NULL; 1047 return SVN_NO_ERROR; 1048 } 1049 1050 /* First, find the mergeinfo difference for old_path_rev->revnum, and 1051 old_path_rev->revnum - 1. */ 1052 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz) 1053 because we will filter out unreadable revisions in 1054 find_interesting_revision() */ 1055 err = svn_fs__get_mergeinfo_for_path(&curr_mergeinfo, 1056 root, old_path_rev->path, 1057 svn_mergeinfo_inherited, TRUE, 1058 scratch_pool, scratch_pool); 1059 if (err) 1060 { 1061 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 1062 { 1063 /* Issue #3896: If invalid mergeinfo is encountered the 1064 best we can do is ignore it and act is if there are 1065 no mergeinfo differences. */ 1066 svn_error_clear(err); 1067 *merged_mergeinfo = NULL; 1068 return SVN_NO_ERROR; 1069 } 1070 else 1071 { 1072 return svn_error_trace(err); 1073 } 1074 } 1075 1076 SVN_ERR(svn_fs_revision_root(&prev_root, repos->fs, old_path_rev->revnum - 1, 1077 scratch_pool)); 1078 err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo, 1079 prev_root, old_path_rev->path, 1080 svn_mergeinfo_inherited, TRUE, 1081 scratch_pool, scratch_pool); 1082 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND 1083 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 1084 { 1085 /* If the path doesn't exist in the previous revision or it does exist 1086 but has invalid mergeinfo (Issue #3896), assume no merges. */ 1087 svn_error_clear(err); 1088 *merged_mergeinfo = NULL; 1089 return SVN_NO_ERROR; 1090 } 1091 else 1092 SVN_ERR(err); 1093 1094 /* Then calculate and merge the differences, combining additions and 1095 (negated) deletions as all positive changes in CHANGES. */ 1096 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo, 1097 curr_mergeinfo, FALSE, result_pool, 1098 scratch_pool)); 1099 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool)); 1100 1101 /* Store the result. */ 1102 if (apr_hash_count(changed)) 1103 *merged_mergeinfo = changed; 1104 else 1105 *merged_mergeinfo = NULL; 1106 1107 return SVN_NO_ERROR; 1108} 1109 1110static svn_error_t * 1111find_interesting_revisions(apr_array_header_t *path_revisions, 1112 svn_repos_t *repos, 1113 const char *path, 1114 svn_revnum_t start, 1115 svn_revnum_t end, 1116 svn_boolean_t include_merged_revisions, 1117 svn_boolean_t mark_as_merged, 1118 apr_hash_t *duplicate_path_revs, 1119 svn_repos_authz_func_t authz_read_func, 1120 void *authz_read_baton, 1121 apr_pool_t *result_pool, 1122 apr_pool_t *scratch_pool) 1123{ 1124 apr_pool_t *iterpool, *last_pool; 1125 svn_fs_history_t *history; 1126 svn_fs_root_t *root; 1127 svn_node_kind_t kind; 1128 1129 /* We switch between two pools while looping, since we need information from 1130 the last iteration to be available. */ 1131 iterpool = svn_pool_create(scratch_pool); 1132 last_pool = svn_pool_create(scratch_pool); 1133 1134 /* The path had better be a file in this revision. */ 1135 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1136 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1137 if (kind != svn_node_file) 1138 return svn_error_createf 1139 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"), 1140 path, end); 1141 1142 /* Open a history object. */ 1143 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, 1144 scratch_pool)); 1145 while (1) 1146 { 1147 struct path_revision *path_rev; 1148 svn_revnum_t tmp_revnum; 1149 const char *tmp_path; 1150 apr_pool_t *tmp_pool; 1151 1152 svn_pool_clear(iterpool); 1153 1154 /* Fetch the history object to walk through. */ 1155 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool, 1156 iterpool)); 1157 if (!history) 1158 break; 1159 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1160 history, iterpool)); 1161 1162 /* Check to see if we already saw this path (and it's ancestors) */ 1163 if (include_merged_revisions 1164 && is_path_in_hash(duplicate_path_revs, tmp_path, 1165 tmp_revnum, iterpool)) 1166 break; 1167 1168 /* Check authorization. */ 1169 if (authz_read_func) 1170 { 1171 svn_boolean_t readable; 1172 svn_fs_root_t *tmp_root; 1173 1174 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1175 iterpool)); 1176 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1177 authz_read_baton, iterpool)); 1178 if (! readable) 1179 break; 1180 } 1181 1182 /* We didn't break, so we must really want this path-rev. */ 1183 path_rev = apr_palloc(result_pool, sizeof(*path_rev)); 1184 path_rev->path = apr_pstrdup(result_pool, tmp_path); 1185 path_rev->revnum = tmp_revnum; 1186 path_rev->merged = mark_as_merged; 1187 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev; 1188 1189 if (include_merged_revisions) 1190 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos, 1191 path_rev, result_pool, iterpool)); 1192 else 1193 path_rev->merged_mergeinfo = NULL; 1194 1195 /* Add the path/rev pair to the hash, so we can filter out future 1196 occurrences of it. We only care about this if including merged 1197 revisions, 'cause that's the only time we can have duplicates. */ 1198 svn_hash_sets(duplicate_path_revs, 1199 apr_psprintf(result_pool, "%s:%ld", path_rev->path, 1200 path_rev->revnum), 1201 (void *)0xdeadbeef); 1202 1203 if (path_rev->revnum <= start) 1204 break; 1205 1206 /* Swap pools. */ 1207 tmp_pool = iterpool; 1208 iterpool = last_pool; 1209 last_pool = tmp_pool; 1210 } 1211 1212 svn_pool_destroy(iterpool); 1213 svn_pool_destroy(last_pool); 1214 1215 return SVN_NO_ERROR; 1216} 1217 1218/* Comparison function to sort path/revisions in increasing order */ 1219static int 1220compare_path_revisions(const void *a, const void *b) 1221{ 1222 struct path_revision *a_pr = *(struct path_revision *const *)a; 1223 struct path_revision *b_pr = *(struct path_revision *const *)b; 1224 1225 if (a_pr->revnum == b_pr->revnum) 1226 return 0; 1227 1228 return a_pr->revnum < b_pr->revnum ? 1 : -1; 1229} 1230 1231static svn_error_t * 1232find_merged_revisions(apr_array_header_t **merged_path_revisions_out, 1233 svn_revnum_t start, 1234 const apr_array_header_t *mainline_path_revisions, 1235 svn_repos_t *repos, 1236 apr_hash_t *duplicate_path_revs, 1237 svn_repos_authz_func_t authz_read_func, 1238 void *authz_read_baton, 1239 apr_pool_t *result_pool, 1240 apr_pool_t *scratch_pool) 1241{ 1242 const apr_array_header_t *old; 1243 apr_array_header_t *new_merged_path_revs; 1244 apr_pool_t *iterpool, *last_pool; 1245 apr_array_header_t *merged_path_revisions = 1246 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *)); 1247 1248 old = mainline_path_revisions; 1249 iterpool = svn_pool_create(scratch_pool); 1250 last_pool = svn_pool_create(scratch_pool); 1251 1252 do 1253 { 1254 int i; 1255 apr_pool_t *temp_pool; 1256 1257 svn_pool_clear(iterpool); 1258 new_merged_path_revs = apr_array_make(iterpool, 0, 1259 sizeof(struct path_revision *)); 1260 1261 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather 1262 path_revisions for any merged revisions, and store those in NEW. */ 1263 for (i = 0; i < old->nelts; i++) 1264 { 1265 apr_pool_t *iterpool2; 1266 apr_hash_index_t *hi; 1267 struct path_revision *old_pr = APR_ARRAY_IDX(old, i, 1268 struct path_revision *); 1269 if (!old_pr->merged_mergeinfo) 1270 continue; 1271 1272 iterpool2 = svn_pool_create(iterpool); 1273 1274 /* Determine and trace the merge sources. */ 1275 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi; 1276 hi = apr_hash_next(hi)) 1277 { 1278 const char *path = apr_hash_this_key(hi); 1279 svn_rangelist_t *rangelist = apr_hash_this_val(hi); 1280 apr_pool_t *iterpool3; 1281 int j; 1282 1283 svn_pool_clear(iterpool2); 1284 iterpool3 = svn_pool_create(iterpool2); 1285 1286 for (j = 0; j < rangelist->nelts; j++) 1287 { 1288 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j, 1289 svn_merge_range_t *); 1290 svn_node_kind_t kind; 1291 svn_fs_root_t *root; 1292 1293 if (range->end < start) 1294 continue; 1295 1296 svn_pool_clear(iterpool3); 1297 1298 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end, 1299 iterpool3)); 1300 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3)); 1301 if (kind != svn_node_file) 1302 continue; 1303 1304 /* Search and find revisions to add to the NEW list. */ 1305 SVN_ERR(find_interesting_revisions(new_merged_path_revs, 1306 repos, path, 1307 range->start, range->end, 1308 TRUE, TRUE, 1309 duplicate_path_revs, 1310 authz_read_func, 1311 authz_read_baton, 1312 result_pool, iterpool3)); 1313 } 1314 svn_pool_destroy(iterpool3); 1315 } 1316 svn_pool_destroy(iterpool2); 1317 } 1318 1319 /* Append the newly found path revisions with the old ones. */ 1320 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions, 1321 new_merged_path_revs); 1322 1323 /* Swap data structures */ 1324 old = new_merged_path_revs; 1325 temp_pool = last_pool; 1326 last_pool = iterpool; 1327 iterpool = temp_pool; 1328 } 1329 while (new_merged_path_revs->nelts > 0); 1330 1331 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */ 1332 svn_sort__array(merged_path_revisions, compare_path_revisions); 1333 1334 /* Copy to the output array. */ 1335 *merged_path_revisions_out = apr_array_copy(result_pool, 1336 merged_path_revisions); 1337 1338 svn_pool_destroy(iterpool); 1339 svn_pool_destroy(last_pool); 1340 1341 return SVN_NO_ERROR; 1342} 1343 1344struct send_baton 1345{ 1346 apr_pool_t *iterpool; 1347 apr_pool_t *last_pool; 1348 apr_hash_t *last_props; 1349 const char *last_path; 1350 svn_fs_root_t *last_root; 1351 svn_boolean_t include_merged_revisions; 1352}; 1353 1354/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by 1355 SB. */ 1356static svn_error_t * 1357send_path_revision(struct path_revision *path_rev, 1358 svn_repos_t *repos, 1359 struct send_baton *sb, 1360 svn_file_rev_handler_t handler, 1361 void *handler_baton) 1362{ 1363 apr_hash_t *rev_props; 1364 apr_hash_t *props; 1365 apr_array_header_t *prop_diffs; 1366 svn_fs_root_t *root; 1367 svn_txdelta_stream_t *delta_stream; 1368 svn_txdelta_window_handler_t delta_handler = NULL; 1369 void *delta_baton = NULL; 1370 apr_pool_t *tmp_pool; /* For swapping */ 1371 svn_boolean_t contents_changed; 1372 svn_boolean_t props_changed; 1373 1374 svn_pool_clear(sb->iterpool); 1375 1376 /* Get the revision properties. */ 1377 SVN_ERR(svn_fs_revision_proplist2(&rev_props, repos->fs, 1378 path_rev->revnum, FALSE, 1379 sb->iterpool, sb->iterpool)); 1380 1381 /* Open the revision root. */ 1382 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum, 1383 sb->iterpool)); 1384 1385 /* Check if the props *may* have changed. */ 1386 if (sb->last_root) 1387 { 1388 /* We don't use svn_fs_props_different() because it's more 1389 * expensive. */ 1390 SVN_ERR(svn_fs_props_changed(&props_changed, 1391 sb->last_root, sb->last_path, 1392 root, path_rev->path, sb->iterpool)); 1393 } 1394 else 1395 { 1396 props_changed = TRUE; 1397 } 1398 1399 /* Calculate actual difference between last and current properties. */ 1400 if (props_changed) 1401 { 1402 /* Get the file's properties for this revision and compute the diffs. */ 1403 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path, 1404 sb->iterpool)); 1405 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props, 1406 sb->iterpool)); 1407 } 1408 else 1409 { 1410 /* Properties didn't change: copy LAST_PROPS to current POOL. */ 1411 props = svn_prop_hash_dup(sb->last_props, sb->iterpool); 1412 prop_diffs = apr_array_make(sb->iterpool, 0, sizeof(svn_prop_t)); 1413 } 1414 1415 /* Check if the contents *may* have changed. */ 1416 if (! sb->last_root) 1417 { 1418 /* Special case: In the first revision, we always provide a delta. */ 1419 contents_changed = TRUE; 1420 } 1421 else if (sb->include_merged_revisions 1422 && strcmp(sb->last_path, path_rev->path)) 1423 { 1424 /* ### This is a HACK!!! 1425 * Blame -g, in older clients anyways, relies on getting a notification 1426 * whenever the path changes - even if there was no content change. 1427 * 1428 * TODO: A future release should take an extra parameter and depending 1429 * on that either always send a text delta or only send it if there 1430 * is a difference. */ 1431 contents_changed = TRUE; 1432 } 1433 else 1434 { 1435 /* Did the file contents actually change? 1436 * It could e.g. be a property-only change. */ 1437 SVN_ERR(svn_fs_contents_different(&contents_changed, sb->last_root, 1438 sb->last_path, root, path_rev->path, 1439 sb->iterpool)); 1440 } 1441 1442 /* We have all we need, give to the handler. */ 1443 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum, 1444 rev_props, path_rev->merged, 1445 contents_changed ? &delta_handler : NULL, 1446 contents_changed ? &delta_baton : NULL, 1447 prop_diffs, sb->iterpool)); 1448 1449 /* Compute and send delta if client asked for it. 1450 Note that this was initialized to NULL, so if !contents_changed, 1451 no deltas will be computed. */ 1452 if (delta_handler && delta_handler != svn_delta_noop_window_handler) 1453 { 1454 /* Get the content delta. */ 1455 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, 1456 sb->last_root, sb->last_path, 1457 root, path_rev->path, 1458 sb->iterpool)); 1459 /* And send. */ 1460 SVN_ERR(svn_txdelta_send_txstream(delta_stream, 1461 delta_handler, delta_baton, 1462 sb->iterpool)); 1463 } 1464 1465 /* Remember root, path and props for next iteration. */ 1466 sb->last_root = root; 1467 sb->last_path = path_rev->path; 1468 sb->last_props = props; 1469 1470 /* Swap the pools. */ 1471 tmp_pool = sb->iterpool; 1472 sb->iterpool = sb->last_pool; 1473 sb->last_pool = tmp_pool; 1474 1475 return SVN_NO_ERROR; 1476} 1477 1478/* Similar to svn_repos_get_file_revs2() but returns paths while walking 1479 history instead of after collecting all history. 1480 1481 This allows implementing clients to immediately start processing and 1482 stop when they got the information they need. (E.g. all or a specific set 1483 of lines were modified) */ 1484static svn_error_t * 1485get_file_revs_backwards(svn_repos_t *repos, 1486 const char *path, 1487 svn_revnum_t start, 1488 svn_revnum_t end, 1489 svn_repos_authz_func_t authz_read_func, 1490 void *authz_read_baton, 1491 svn_file_rev_handler_t handler, 1492 void *handler_baton, 1493 apr_pool_t *scratch_pool) 1494{ 1495 apr_pool_t *iterpool, *last_pool; 1496 svn_fs_history_t *history; 1497 svn_fs_root_t *root; 1498 svn_node_kind_t kind; 1499 struct send_baton sb; 1500 1501 /* We switch between two pools while looping and so does the path-rev 1502 handler for actually reported revisions. We do this as we 1503 need just information from last iteration to be available. */ 1504 1505 iterpool = svn_pool_create(scratch_pool); 1506 last_pool = svn_pool_create(scratch_pool); 1507 sb.iterpool = svn_pool_create(scratch_pool); 1508 sb.last_pool = svn_pool_create(scratch_pool); 1509 sb.include_merged_revisions = FALSE; 1510 1511 /* We want the first txdelta to be against the empty file. */ 1512 sb.last_root = NULL; 1513 sb.last_path = NULL; 1514 1515 /* Create an empty hash table for the first property diff. */ 1516 sb.last_props = apr_hash_make(sb.last_pool); 1517 1518 /* The path had better be a file in this revision. */ 1519 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1520 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1521 if (kind != svn_node_file) 1522 return svn_error_createf(SVN_ERR_FS_NOT_FILE, 1523 NULL, _("'%s' is not a file in revision %ld"), 1524 path, end); 1525 1526 /* Open a history object. */ 1527 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, iterpool)); 1528 while (1) 1529 { 1530 struct path_revision *path_rev; 1531 svn_revnum_t tmp_revnum; 1532 const char *tmp_path; 1533 1534 svn_pool_clear(iterpool); 1535 1536 /* Fetch the history object to walk through. */ 1537 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool, 1538 iterpool)); 1539 if (!history) 1540 break; 1541 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1542 history, iterpool)); 1543 1544 /* Check authorization. */ 1545 if (authz_read_func) 1546 { 1547 svn_boolean_t readable; 1548 svn_fs_root_t *tmp_root; 1549 1550 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1551 iterpool)); 1552 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1553 authz_read_baton, iterpool)); 1554 if (! readable) 1555 break; 1556 } 1557 1558 /* We didn't break, so we must really want this path-rev. */ 1559 path_rev = apr_palloc(iterpool, sizeof(*path_rev)); 1560 path_rev->path = tmp_path; 1561 path_rev->revnum = tmp_revnum; 1562 path_rev->merged = FALSE; 1563 1564 SVN_ERR(send_path_revision(path_rev, repos, &sb, 1565 handler, handler_baton)); 1566 1567 if (path_rev->revnum <= start) 1568 break; 1569 1570 /* Swap pools. */ 1571 { 1572 apr_pool_t *tmp_pool = iterpool; 1573 iterpool = last_pool; 1574 last_pool = tmp_pool; 1575 } 1576 } 1577 1578 svn_pool_destroy(iterpool); 1579 svn_pool_destroy(last_pool); 1580 svn_pool_destroy(sb.last_pool); 1581 svn_pool_destroy(sb.iterpool); 1582 1583 return SVN_NO_ERROR; 1584 1585} 1586 1587 1588/* We don't yet support sending revisions in reverse order; the caller wait 1589 * until we've traced back through the entire history, and then accept 1590 * them from oldest to youngest. Someday this may change, but in the meantime, 1591 * the general algorithm is thus: 1592 * 1593 * 1) Trace back through the history of an object, adding each revision 1594 * found to the MAINLINE_PATH_REVISIONS array, marking any which were 1595 * merges. 1596 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the 1597 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using 1598 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple 1599 * times. 1600 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from 1601 * oldest to youngest, interleaving as appropriate. This is implemented 1602 * similar to an insertion sort, but instead of inserting into another 1603 * array, we just call the appropriate handler. 1604 * 1605 * 2013-02: Added a very simple reverse for mainline only changes. Before this, 1606 * this would return an error (path not found) or just the first 1607 * revision before end. 1608 */ 1609svn_error_t * 1610svn_repos_get_file_revs2(svn_repos_t *repos, 1611 const char *path, 1612 svn_revnum_t start, 1613 svn_revnum_t end, 1614 svn_boolean_t include_merged_revisions, 1615 svn_repos_authz_func_t authz_read_func, 1616 void *authz_read_baton, 1617 svn_file_rev_handler_t handler, 1618 void *handler_baton, 1619 apr_pool_t *scratch_pool) 1620{ 1621 apr_array_header_t *mainline_path_revisions, *merged_path_revisions; 1622 apr_hash_t *duplicate_path_revs; 1623 struct send_baton sb; 1624 int mainline_pos, merged_pos; 1625 1626 if (!SVN_IS_VALID_REVNUM(start) 1627 || !SVN_IS_VALID_REVNUM(end)) 1628 { 1629 svn_revnum_t youngest_rev; 1630 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, repos->fs, scratch_pool)); 1631 1632 if (!SVN_IS_VALID_REVNUM(start)) 1633 start = youngest_rev; 1634 if (!SVN_IS_VALID_REVNUM(end)) 1635 end = youngest_rev; 1636 } 1637 1638 /* Make sure we catch up on the latest revprop changes. This is the only 1639 * time we will refresh the revprop data in this query. */ 1640 SVN_ERR(svn_fs_refresh_revision_props(repos->fs, scratch_pool)); 1641 1642 if (end < start) 1643 { 1644 if (include_merged_revisions) 1645 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); 1646 1647 return svn_error_trace( 1648 get_file_revs_backwards(repos, path, 1649 end, start, 1650 authz_read_func, 1651 authz_read_baton, 1652 handler, 1653 handler_baton, 1654 scratch_pool)); 1655 } 1656 1657 /* We switch between two pools while looping, since we need information from 1658 the last iteration to be available. */ 1659 sb.iterpool = svn_pool_create(scratch_pool); 1660 sb.last_pool = svn_pool_create(scratch_pool); 1661 1662 /* We want the first txdelta to be against the empty file. */ 1663 sb.last_root = NULL; 1664 sb.last_path = NULL; 1665 1666 /* Create an empty hash table for the first property diff. */ 1667 sb.last_props = apr_hash_make(sb.last_pool); 1668 1669 /* Inform send_path_revision() whether workarounds / special behavior 1670 * may be needed. */ 1671 sb.include_merged_revisions = include_merged_revisions; 1672 1673 /* Get the revisions we are interested in. */ 1674 duplicate_path_revs = apr_hash_make(scratch_pool); 1675 mainline_path_revisions = apr_array_make(scratch_pool, 100, 1676 sizeof(struct path_revision *)); 1677 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path, 1678 start, end, include_merged_revisions, 1679 FALSE, duplicate_path_revs, 1680 authz_read_func, authz_read_baton, 1681 scratch_pool, sb.iterpool)); 1682 1683 /* If we are including merged revisions, go get those, too. */ 1684 if (include_merged_revisions) 1685 SVN_ERR(find_merged_revisions(&merged_path_revisions, start, 1686 mainline_path_revisions, repos, 1687 duplicate_path_revs, authz_read_func, 1688 authz_read_baton, 1689 scratch_pool, sb.iterpool)); 1690 else 1691 merged_path_revisions = apr_array_make(scratch_pool, 0, 1692 sizeof(struct path_revision *)); 1693 1694 /* We must have at least one revision to get. */ 1695 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0); 1696 1697 /* Walk through both mainline and merged revisions, and send them in 1698 reverse chronological order, interleaving as appropriate. */ 1699 mainline_pos = mainline_path_revisions->nelts - 1; 1700 merged_pos = merged_path_revisions->nelts - 1; 1701 while (mainline_pos >= 0 && merged_pos >= 0) 1702 { 1703 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1704 mainline_pos, 1705 struct path_revision *); 1706 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1707 merged_pos, 1708 struct path_revision *); 1709 1710 if (main_pr->revnum <= merged_pr->revnum) 1711 { 1712 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, 1713 handler_baton)); 1714 mainline_pos -= 1; 1715 } 1716 else 1717 { 1718 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1719 handler_baton)); 1720 merged_pos -= 1; 1721 } 1722 } 1723 1724 /* Send any remaining revisions from the mainline list. */ 1725 for (; mainline_pos >= 0; mainline_pos -= 1) 1726 { 1727 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1728 mainline_pos, 1729 struct path_revision *); 1730 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton)); 1731 } 1732 1733 /* Ditto for the merged list. */ 1734 for (; merged_pos >= 0; merged_pos -= 1) 1735 { 1736 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1737 merged_pos, 1738 struct path_revision *); 1739 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1740 handler_baton)); 1741 } 1742 1743 svn_pool_destroy(sb.last_pool); 1744 svn_pool_destroy(sb.iterpool); 1745 1746 return SVN_NO_ERROR; 1747} 1748