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