rev_hunt.c revision 289166
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 /* Assign the current path to all younger revisions until we reach 730 the copy target rev. */ 731 while ((revision_ptr < revision_ptr_end) 732 && (*revision_ptr >= appeared_rev)) 733 { 734 /* *revision_ptr is allocated out of pool, so we can point 735 to in the hash table. */ 736 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 737 apr_pstrdup(pool, path)); 738 revision_ptr++; 739 } 740 741 /* Ignore all revs between the copy target rev and the copy 742 source rev (non-inclusive). */ 743 while ((revision_ptr < revision_ptr_end) 744 && (*revision_ptr > prev_rev)) 745 revision_ptr++; 746 747 /* State update. */ 748 path = prev_path; 749 revision = prev_rev; 750 751 if (authz_read_func) 752 { 753 svn_boolean_t readable; 754 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); 755 SVN_ERR(authz_read_func(&readable, root, path, 756 authz_read_baton, currpool)); 757 if (!readable) 758 { 759 svn_pool_destroy(lastpool); 760 svn_pool_destroy(currpool); 761 return SVN_NO_ERROR; 762 } 763 } 764 765 /* Clear last pool and switch. */ 766 svn_pool_clear(lastpool); 767 tmppool = lastpool; 768 lastpool = currpool; 769 currpool = tmppool; 770 } 771 772 /* There are no copies relevant to path@revision. So any remaining 773 revisions either predate the creation of path@revision or have 774 the node existing at the same path. We will look up path@lrev 775 for each remaining location-revision and make sure it is related 776 to path@revision. */ 777 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); 778 SVN_ERR(svn_fs_node_id(&id, root, path, pool)); 779 while (revision_ptr < revision_ptr_end) 780 { 781 svn_node_kind_t kind; 782 const svn_fs_id_t *lrev_id; 783 784 svn_pool_clear(currpool); 785 SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool)); 786 SVN_ERR(svn_fs_check_path(&kind, root, path, currpool)); 787 if (kind == svn_node_none) 788 break; 789 SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool)); 790 if (! svn_fs_check_related(id, lrev_id)) 791 break; 792 793 /* The node exists at the same path; record that and advance. */ 794 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 795 apr_pstrdup(pool, path)); 796 revision_ptr++; 797 } 798 799 /* Ignore any remaining location-revisions; they predate the 800 creation of path@revision. */ 801 802 svn_pool_destroy(lastpool); 803 svn_pool_destroy(currpool); 804 805 return SVN_NO_ERROR; 806} 807 808 809/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of 810 its revision range fits between END_REV and START_REV, possibly 811 cropping the range so that it fits *entirely* in that range. */ 812static svn_error_t * 813maybe_crop_and_send_segment(svn_location_segment_t *segment, 814 svn_revnum_t start_rev, 815 svn_revnum_t end_rev, 816 svn_location_segment_receiver_t receiver, 817 void *receiver_baton, 818 apr_pool_t *pool) 819{ 820 /* We only want to transmit this segment if some portion of it 821 is between our END_REV and START_REV. */ 822 if (! ((segment->range_start > start_rev) 823 || (segment->range_end < end_rev))) 824 { 825 /* Correct our segment range when the range straddles one of 826 our requested revision boundaries. */ 827 if (segment->range_start < end_rev) 828 segment->range_start = end_rev; 829 if (segment->range_end > start_rev) 830 segment->range_end = start_rev; 831 SVN_ERR(receiver(segment, receiver_baton, pool)); 832 } 833 return SVN_NO_ERROR; 834} 835 836 837svn_error_t * 838svn_repos_node_location_segments(svn_repos_t *repos, 839 const char *path, 840 svn_revnum_t peg_revision, 841 svn_revnum_t start_rev, 842 svn_revnum_t end_rev, 843 svn_location_segment_receiver_t receiver, 844 void *receiver_baton, 845 svn_repos_authz_func_t authz_read_func, 846 void *authz_read_baton, 847 apr_pool_t *pool) 848{ 849 svn_fs_t *fs = svn_repos_fs(repos); 850 svn_stringbuf_t *current_path; 851 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev; 852 apr_pool_t *subpool; 853 854 /* No PEG_REVISION? We'll use HEAD. */ 855 if (! SVN_IS_VALID_REVNUM(peg_revision)) 856 { 857 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); 858 peg_revision = youngest_rev; 859 } 860 861 /* No START_REV? We'll use HEAD (which we may have already fetched). */ 862 if (! SVN_IS_VALID_REVNUM(start_rev)) 863 { 864 if (SVN_IS_VALID_REVNUM(youngest_rev)) 865 start_rev = youngest_rev; 866 else 867 SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool)); 868 } 869 870 /* No END_REV? We'll use 0. */ 871 end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0; 872 873 /* Are the revision properly ordered? They better be -- the API 874 demands it. */ 875 SVN_ERR_ASSERT(end_rev <= start_rev); 876 SVN_ERR_ASSERT(start_rev <= peg_revision); 877 878 /* Ensure that PATH is absolute, because our path-math will depend 879 on that being the case. */ 880 if (*path != '/') 881 path = apr_pstrcat(pool, "/", path, (char *)NULL); 882 883 /* Auth check. */ 884 if (authz_read_func) 885 { 886 svn_fs_root_t *peg_root; 887 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); 888 SVN_ERR(check_readability(peg_root, path, 889 authz_read_func, authz_read_baton, pool)); 890 } 891 892 /* Okay, let's get searching! */ 893 subpool = svn_pool_create(pool); 894 current_rev = peg_revision; 895 current_path = svn_stringbuf_create(path, pool); 896 while (current_rev >= end_rev) 897 { 898 svn_revnum_t appeared_rev, prev_rev; 899 const char *cur_path, *prev_path; 900 svn_location_segment_t *segment; 901 902 svn_pool_clear(subpool); 903 904 cur_path = apr_pstrmemdup(subpool, current_path->data, 905 current_path->len); 906 segment = apr_pcalloc(subpool, sizeof(*segment)); 907 segment->range_end = current_rev; 908 segment->range_start = end_rev; 909 /* segment path should be absolute without leading '/'. */ 910 segment->path = cur_path + 1; 911 912 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 913 fs, current_rev, cur_path, subpool)); 914 915 /* If there are no previous locations for this thing (meaning, 916 it originated at the current path), then we simply need to 917 find its revision of origin to populate our final segment. 918 Otherwise, the APPEARED_REV is the start of current segment's 919 range. */ 920 if (! prev_path) 921 { 922 svn_fs_root_t *revroot; 923 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool)); 924 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot, 925 cur_path, subpool)); 926 if (segment->range_start < end_rev) 927 segment->range_start = end_rev; 928 current_rev = SVN_INVALID_REVNUM; 929 } 930 else 931 { 932 segment->range_start = appeared_rev; 933 svn_stringbuf_set(current_path, prev_path); 934 current_rev = prev_rev; 935 } 936 937 /* Report our segment, providing it passes authz muster. */ 938 if (authz_read_func) 939 { 940 svn_boolean_t readable; 941 svn_fs_root_t *cur_rev_root; 942 943 /* authz_read_func requires path to have a leading slash. */ 944 const char *abs_path = apr_pstrcat(subpool, "/", segment->path, 945 (char *)NULL); 946 947 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, 948 segment->range_end, subpool)); 949 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path, 950 authz_read_baton, subpool)); 951 if (! readable) 952 return SVN_NO_ERROR; 953 } 954 955 /* Transmit the segment (if it's within the scope of our concern). */ 956 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev, 957 receiver, receiver_baton, subpool)); 958 959 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done 960 (and didn't ever reach END_REV). */ 961 if (! SVN_IS_VALID_REVNUM(current_rev)) 962 break; 963 964 /* If there's a gap in the history, we need to report as much 965 (if the gap is within the scope of our concern). */ 966 if (segment->range_start - current_rev > 1) 967 { 968 svn_location_segment_t *gap_segment; 969 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment)); 970 gap_segment->range_end = segment->range_start - 1; 971 gap_segment->range_start = current_rev + 1; 972 gap_segment->path = NULL; 973 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev, 974 receiver, receiver_baton, 975 subpool)); 976 } 977 } 978 svn_pool_destroy(subpool); 979 return SVN_NO_ERROR; 980} 981 982/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */ 983static svn_error_t * 984get_path_mergeinfo(apr_hash_t **mergeinfo, 985 svn_fs_t *fs, 986 const char *path, 987 svn_revnum_t revnum, 988 apr_pool_t *result_pool, 989 apr_pool_t *scratch_pool) 990{ 991 svn_mergeinfo_catalog_t tmp_catalog; 992 svn_fs_root_t *root; 993 apr_array_header_t *paths = apr_array_make(scratch_pool, 1, 994 sizeof(const char *)); 995 996 APR_ARRAY_PUSH(paths, const char *) = path; 997 998 SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool)); 999 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz) 1000 because we will filter out unreadable revisions in 1001 find_interesting_revision(), above */ 1002 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths, 1003 svn_mergeinfo_inherited, FALSE, TRUE, 1004 result_pool, scratch_pool)); 1005 1006 *mergeinfo = svn_hash_gets(tmp_catalog, path); 1007 if (!*mergeinfo) 1008 *mergeinfo = apr_hash_make(result_pool); 1009 1010 return SVN_NO_ERROR; 1011} 1012 1013static APR_INLINE svn_boolean_t 1014is_path_in_hash(apr_hash_t *duplicate_path_revs, 1015 const char *path, 1016 svn_revnum_t revision, 1017 apr_pool_t *pool) 1018{ 1019 const char *key = apr_psprintf(pool, "%s:%ld", path, revision); 1020 void *ptr; 1021 1022 ptr = svn_hash_gets(duplicate_path_revs, key); 1023 return ptr != NULL; 1024} 1025 1026struct path_revision 1027{ 1028 svn_revnum_t revnum; 1029 const char *path; 1030 1031 /* Does this path_rev have merges to also be included? */ 1032 apr_hash_t *merged_mergeinfo; 1033 1034 /* Is this a merged revision? */ 1035 svn_boolean_t merged; 1036}; 1037 1038/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store 1039 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The 1040 returned *MERGED_MERGEINFO will be NULL if there are no changes. */ 1041static svn_error_t * 1042get_merged_mergeinfo(apr_hash_t **merged_mergeinfo, 1043 svn_repos_t *repos, 1044 struct path_revision *old_path_rev, 1045 apr_pool_t *result_pool, 1046 apr_pool_t *scratch_pool) 1047{ 1048 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed; 1049 svn_error_t *err; 1050 svn_fs_root_t *root; 1051 apr_hash_t *changed_paths; 1052 const char *path = old_path_rev->path; 1053 1054 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it 1055 if there is a property change. */ 1056 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum, 1057 scratch_pool)); 1058 SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool)); 1059 while (1) 1060 { 1061 svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path); 1062 if (changed_path && changed_path->prop_mod) 1063 break; 1064 if (svn_fspath__is_root(path, strlen(path))) 1065 { 1066 *merged_mergeinfo = NULL; 1067 return SVN_NO_ERROR; 1068 } 1069 path = svn_fspath__dirname(path, scratch_pool); 1070 } 1071 1072 /* First, find the mergeinfo difference for old_path_rev->revnum, and 1073 old_path_rev->revnum - 1. */ 1074 err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path, 1075 old_path_rev->revnum, scratch_pool, 1076 scratch_pool); 1077 if (err) 1078 { 1079 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 1080 { 1081 /* Issue #3896: If invalid mergeinfo is encountered the 1082 best we can do is ignore it and act is if there are 1083 no mergeinfo differences. */ 1084 svn_error_clear(err); 1085 *merged_mergeinfo = NULL; 1086 return SVN_NO_ERROR; 1087 } 1088 else 1089 { 1090 return svn_error_trace(err); 1091 } 1092 } 1093 1094 err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path, 1095 old_path_rev->revnum - 1, scratch_pool, 1096 scratch_pool); 1097 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND 1098 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 1099 { 1100 /* If the path doesn't exist in the previous revision or it does exist 1101 but has invalid mergeinfo (Issue #3896), assume no merges. */ 1102 svn_error_clear(err); 1103 *merged_mergeinfo = NULL; 1104 return SVN_NO_ERROR; 1105 } 1106 else 1107 SVN_ERR(err); 1108 1109 /* Then calculate and merge the differences. */ 1110 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo, 1111 curr_mergeinfo, FALSE, result_pool, 1112 scratch_pool)); 1113 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool)); 1114 1115 /* Store the result. */ 1116 if (apr_hash_count(changed)) 1117 *merged_mergeinfo = changed; 1118 else 1119 *merged_mergeinfo = NULL; 1120 1121 return SVN_NO_ERROR; 1122} 1123 1124static svn_error_t * 1125find_interesting_revisions(apr_array_header_t *path_revisions, 1126 svn_repos_t *repos, 1127 const char *path, 1128 svn_revnum_t start, 1129 svn_revnum_t end, 1130 svn_boolean_t include_merged_revisions, 1131 svn_boolean_t mark_as_merged, 1132 apr_hash_t *duplicate_path_revs, 1133 svn_repos_authz_func_t authz_read_func, 1134 void *authz_read_baton, 1135 apr_pool_t *result_pool, 1136 apr_pool_t *scratch_pool) 1137{ 1138 apr_pool_t *iterpool, *last_pool; 1139 svn_fs_history_t *history; 1140 svn_fs_root_t *root; 1141 svn_node_kind_t kind; 1142 1143 /* We switch between two pools while looping, since we need information from 1144 the last iteration to be available. */ 1145 iterpool = svn_pool_create(scratch_pool); 1146 last_pool = svn_pool_create(scratch_pool); 1147 1148 /* The path had better be a file in this revision. */ 1149 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1150 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1151 if (kind != svn_node_file) 1152 return svn_error_createf 1153 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"), 1154 path, end); 1155 1156 /* Open a history object. */ 1157 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); 1158 while (1) 1159 { 1160 struct path_revision *path_rev; 1161 svn_revnum_t tmp_revnum; 1162 const char *tmp_path; 1163 apr_pool_t *tmp_pool; 1164 1165 svn_pool_clear(iterpool); 1166 1167 /* Fetch the history object to walk through. */ 1168 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); 1169 if (!history) 1170 break; 1171 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1172 history, iterpool)); 1173 1174 /* Check to see if we already saw this path (and it's ancestors) */ 1175 if (include_merged_revisions 1176 && is_path_in_hash(duplicate_path_revs, tmp_path, 1177 tmp_revnum, iterpool)) 1178 break; 1179 1180 /* Check authorization. */ 1181 if (authz_read_func) 1182 { 1183 svn_boolean_t readable; 1184 svn_fs_root_t *tmp_root; 1185 1186 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1187 iterpool)); 1188 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1189 authz_read_baton, iterpool)); 1190 if (! readable) 1191 break; 1192 } 1193 1194 /* We didn't break, so we must really want this path-rev. */ 1195 path_rev = apr_palloc(result_pool, sizeof(*path_rev)); 1196 path_rev->path = apr_pstrdup(result_pool, tmp_path); 1197 path_rev->revnum = tmp_revnum; 1198 path_rev->merged = mark_as_merged; 1199 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev; 1200 1201 if (include_merged_revisions) 1202 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos, 1203 path_rev, result_pool, iterpool)); 1204 else 1205 path_rev->merged_mergeinfo = NULL; 1206 1207 /* Add the path/rev pair to the hash, so we can filter out future 1208 occurrences of it. We only care about this if including merged 1209 revisions, 'cause that's the only time we can have duplicates. */ 1210 svn_hash_sets(duplicate_path_revs, 1211 apr_psprintf(result_pool, "%s:%ld", path_rev->path, 1212 path_rev->revnum), 1213 (void *)0xdeadbeef); 1214 1215 if (path_rev->revnum <= start) 1216 break; 1217 1218 /* Swap pools. */ 1219 tmp_pool = iterpool; 1220 iterpool = last_pool; 1221 last_pool = tmp_pool; 1222 } 1223 1224 svn_pool_destroy(iterpool); 1225 svn_pool_destroy(last_pool); 1226 1227 return SVN_NO_ERROR; 1228} 1229 1230/* Comparison function to sort path/revisions in increasing order */ 1231static int 1232compare_path_revisions(const void *a, const void *b) 1233{ 1234 struct path_revision *a_pr = *(struct path_revision *const *)a; 1235 struct path_revision *b_pr = *(struct path_revision *const *)b; 1236 1237 if (a_pr->revnum == b_pr->revnum) 1238 return 0; 1239 1240 return a_pr->revnum < b_pr->revnum ? 1 : -1; 1241} 1242 1243static svn_error_t * 1244find_merged_revisions(apr_array_header_t **merged_path_revisions_out, 1245 svn_revnum_t start, 1246 const apr_array_header_t *mainline_path_revisions, 1247 svn_repos_t *repos, 1248 apr_hash_t *duplicate_path_revs, 1249 svn_repos_authz_func_t authz_read_func, 1250 void *authz_read_baton, 1251 apr_pool_t *result_pool, 1252 apr_pool_t *scratch_pool) 1253{ 1254 const apr_array_header_t *old; 1255 apr_array_header_t *new_merged_path_revs; 1256 apr_pool_t *iterpool, *last_pool; 1257 apr_array_header_t *merged_path_revisions = 1258 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *)); 1259 1260 old = mainline_path_revisions; 1261 iterpool = svn_pool_create(scratch_pool); 1262 last_pool = svn_pool_create(scratch_pool); 1263 1264 do 1265 { 1266 int i; 1267 apr_pool_t *temp_pool; 1268 1269 svn_pool_clear(iterpool); 1270 new_merged_path_revs = apr_array_make(iterpool, 0, 1271 sizeof(struct path_revision *)); 1272 1273 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather 1274 path_revisions for any merged revisions, and store those in NEW. */ 1275 for (i = 0; i < old->nelts; i++) 1276 { 1277 apr_pool_t *iterpool2; 1278 apr_hash_index_t *hi; 1279 struct path_revision *old_pr = APR_ARRAY_IDX(old, i, 1280 struct path_revision *); 1281 if (!old_pr->merged_mergeinfo) 1282 continue; 1283 1284 iterpool2 = svn_pool_create(iterpool); 1285 1286 /* Determine and trace the merge sources. */ 1287 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi; 1288 hi = apr_hash_next(hi)) 1289 { 1290 apr_pool_t *iterpool3; 1291 svn_rangelist_t *rangelist; 1292 const char *path; 1293 int j; 1294 1295 svn_pool_clear(iterpool2); 1296 iterpool3 = svn_pool_create(iterpool2); 1297 1298 apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist); 1299 1300 for (j = 0; j < rangelist->nelts; j++) 1301 { 1302 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j, 1303 svn_merge_range_t *); 1304 svn_node_kind_t kind; 1305 svn_fs_root_t *root; 1306 1307 if (range->end < start) 1308 continue; 1309 1310 svn_pool_clear(iterpool3); 1311 1312 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end, 1313 iterpool3)); 1314 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3)); 1315 if (kind != svn_node_file) 1316 continue; 1317 1318 /* Search and find revisions to add to the NEW list. */ 1319 SVN_ERR(find_interesting_revisions(new_merged_path_revs, 1320 repos, path, 1321 range->start, range->end, 1322 TRUE, TRUE, 1323 duplicate_path_revs, 1324 authz_read_func, 1325 authz_read_baton, 1326 result_pool, iterpool3)); 1327 } 1328 svn_pool_destroy(iterpool3); 1329 } 1330 svn_pool_destroy(iterpool2); 1331 } 1332 1333 /* Append the newly found path revisions with the old ones. */ 1334 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions, 1335 new_merged_path_revs); 1336 1337 /* Swap data structures */ 1338 old = new_merged_path_revs; 1339 temp_pool = last_pool; 1340 last_pool = iterpool; 1341 iterpool = temp_pool; 1342 } 1343 while (new_merged_path_revs->nelts > 0); 1344 1345 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */ 1346 qsort(merged_path_revisions->elts, merged_path_revisions->nelts, 1347 sizeof(struct path_revision *), compare_path_revisions); 1348 1349 /* Copy to the output array. */ 1350 *merged_path_revisions_out = apr_array_copy(result_pool, 1351 merged_path_revisions); 1352 1353 svn_pool_destroy(iterpool); 1354 svn_pool_destroy(last_pool); 1355 1356 return SVN_NO_ERROR; 1357} 1358 1359struct send_baton 1360{ 1361 apr_pool_t *iterpool; 1362 apr_pool_t *last_pool; 1363 apr_hash_t *last_props; 1364 const char *last_path; 1365 svn_fs_root_t *last_root; 1366}; 1367 1368/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by 1369 SB. */ 1370static svn_error_t * 1371send_path_revision(struct path_revision *path_rev, 1372 svn_repos_t *repos, 1373 struct send_baton *sb, 1374 svn_file_rev_handler_t handler, 1375 void *handler_baton) 1376{ 1377 apr_hash_t *rev_props; 1378 apr_hash_t *props; 1379 apr_array_header_t *prop_diffs; 1380 svn_fs_root_t *root; 1381 svn_txdelta_stream_t *delta_stream; 1382 svn_txdelta_window_handler_t delta_handler = NULL; 1383 void *delta_baton = NULL; 1384 apr_pool_t *tmp_pool; /* For swapping */ 1385 svn_boolean_t contents_changed; 1386 1387 svn_pool_clear(sb->iterpool); 1388 1389 /* Get the revision properties. */ 1390 SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs, 1391 path_rev->revnum, sb->iterpool)); 1392 1393 /* Open the revision root. */ 1394 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum, 1395 sb->iterpool)); 1396 1397 /* Get the file's properties for this revision and compute the diffs. */ 1398 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path, 1399 sb->iterpool)); 1400 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props, 1401 sb->iterpool)); 1402 1403 /* Check if the contents changed. */ 1404 /* Special case: In the first revision, we always provide a delta. */ 1405 if (sb->last_root) 1406 SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root, 1407 sb->last_path, root, path_rev->path, 1408 sb->iterpool)); 1409 else 1410 contents_changed = TRUE; 1411 1412 /* We have all we need, give to the handler. */ 1413 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum, 1414 rev_props, path_rev->merged, 1415 contents_changed ? &delta_handler : NULL, 1416 contents_changed ? &delta_baton : NULL, 1417 prop_diffs, sb->iterpool)); 1418 1419 /* Compute and send delta if client asked for it. 1420 Note that this was initialized to NULL, so if !contents_changed, 1421 no deltas will be computed. */ 1422 if (delta_handler) 1423 { 1424 /* Get the content delta. */ 1425 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, 1426 sb->last_root, sb->last_path, 1427 root, path_rev->path, 1428 sb->iterpool)); 1429 /* And send. */ 1430 SVN_ERR(svn_txdelta_send_txstream(delta_stream, 1431 delta_handler, delta_baton, 1432 sb->iterpool)); 1433 } 1434 1435 /* Remember root, path and props for next iteration. */ 1436 sb->last_root = root; 1437 sb->last_path = path_rev->path; 1438 sb->last_props = props; 1439 1440 /* Swap the pools. */ 1441 tmp_pool = sb->iterpool; 1442 sb->iterpool = sb->last_pool; 1443 sb->last_pool = tmp_pool; 1444 1445 return SVN_NO_ERROR; 1446} 1447 1448/* Similar to svn_repos_get_file_revs2() but returns paths while walking 1449 history instead of after collecting all history. 1450 1451 This allows implementing clients to immediately start processing and 1452 stop when they got the information they need. (E.g. all or a specific set 1453 of lines were modified) */ 1454static svn_error_t * 1455get_file_revs_backwards(svn_repos_t *repos, 1456 const char *path, 1457 svn_revnum_t start, 1458 svn_revnum_t end, 1459 svn_repos_authz_func_t authz_read_func, 1460 void *authz_read_baton, 1461 svn_file_rev_handler_t handler, 1462 void *handler_baton, 1463 apr_pool_t *scratch_pool) 1464{ 1465 apr_pool_t *iterpool, *last_pool; 1466 svn_fs_history_t *history; 1467 svn_fs_root_t *root; 1468 svn_node_kind_t kind; 1469 struct send_baton sb; 1470 1471 /* We switch between two pools while looping and so does the path-rev 1472 handler for actually reported revisions. We do this as we 1473 need just information from last iteration to be available. */ 1474 1475 iterpool = svn_pool_create(scratch_pool); 1476 last_pool = svn_pool_create(scratch_pool); 1477 sb.iterpool = svn_pool_create(scratch_pool); 1478 sb.last_pool = svn_pool_create(scratch_pool); 1479 1480 /* We want the first txdelta to be against the empty file. */ 1481 sb.last_root = NULL; 1482 sb.last_path = NULL; 1483 1484 /* Create an empty hash table for the first property diff. */ 1485 sb.last_props = apr_hash_make(sb.last_pool); 1486 1487 /* The path had better be a file in this revision. */ 1488 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1489 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1490 if (kind != svn_node_file) 1491 return svn_error_createf(SVN_ERR_FS_NOT_FILE, 1492 NULL, _("'%s' is not a file in revision %ld"), 1493 path, end); 1494 1495 /* Open a history object. */ 1496 SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); 1497 while (1) 1498 { 1499 struct path_revision *path_rev; 1500 svn_revnum_t tmp_revnum; 1501 const char *tmp_path; 1502 1503 svn_pool_clear(iterpool); 1504 1505 /* Fetch the history object to walk through. */ 1506 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); 1507 if (!history) 1508 break; 1509 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1510 history, iterpool)); 1511 1512 /* Check authorization. */ 1513 if (authz_read_func) 1514 { 1515 svn_boolean_t readable; 1516 svn_fs_root_t *tmp_root; 1517 1518 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1519 iterpool)); 1520 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1521 authz_read_baton, iterpool)); 1522 if (! readable) 1523 break; 1524 } 1525 1526 /* We didn't break, so we must really want this path-rev. */ 1527 path_rev = apr_palloc(iterpool, sizeof(*path_rev)); 1528 path_rev->path = tmp_path; 1529 path_rev->revnum = tmp_revnum; 1530 path_rev->merged = FALSE; 1531 1532 SVN_ERR(send_path_revision(path_rev, repos, &sb, 1533 handler, handler_baton)); 1534 1535 if (path_rev->revnum <= start) 1536 break; 1537 1538 /* Swap pools. */ 1539 { 1540 apr_pool_t *tmp_pool = iterpool; 1541 iterpool = last_pool; 1542 last_pool = tmp_pool; 1543 } 1544 } 1545 1546 svn_pool_destroy(iterpool); 1547 svn_pool_destroy(last_pool); 1548 svn_pool_destroy(sb.last_pool); 1549 svn_pool_destroy(sb.iterpool); 1550 1551 return SVN_NO_ERROR; 1552 1553} 1554 1555 1556/* We don't yet support sending revisions in reverse order; the caller wait 1557 * until we've traced back through the entire history, and then accept 1558 * them from oldest to youngest. Someday this may change, but in the meantime, 1559 * the general algorithm is thus: 1560 * 1561 * 1) Trace back through the history of an object, adding each revision 1562 * found to the MAINLINE_PATH_REVISIONS array, marking any which were 1563 * merges. 1564 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the 1565 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using 1566 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple 1567 * times. 1568 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from 1569 * oldest to youngest, interleaving as appropriate. This is implemented 1570 * similar to an insertion sort, but instead of inserting into another 1571 * array, we just call the appropriate handler. 1572 * 1573 * 2013-02: Added a very simple reverse for mainline only changes. Before this, 1574 * this would return an error (path not found) or just the first 1575 * revision before end. 1576 */ 1577svn_error_t * 1578svn_repos_get_file_revs2(svn_repos_t *repos, 1579 const char *path, 1580 svn_revnum_t start, 1581 svn_revnum_t end, 1582 svn_boolean_t include_merged_revisions, 1583 svn_repos_authz_func_t authz_read_func, 1584 void *authz_read_baton, 1585 svn_file_rev_handler_t handler, 1586 void *handler_baton, 1587 apr_pool_t *scratch_pool) 1588{ 1589 apr_array_header_t *mainline_path_revisions, *merged_path_revisions; 1590 apr_hash_t *duplicate_path_revs; 1591 struct send_baton sb; 1592 int mainline_pos, merged_pos; 1593 1594 if (end < start) 1595 { 1596 if (include_merged_revisions) 1597 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); 1598 1599 return svn_error_trace( 1600 get_file_revs_backwards(repos, path, 1601 end, start, 1602 authz_read_func, 1603 authz_read_baton, 1604 handler, 1605 handler_baton, 1606 scratch_pool)); 1607 } 1608 1609 /* We switch between two pools while looping, since we need information from 1610 the last iteration to be available. */ 1611 sb.iterpool = svn_pool_create(scratch_pool); 1612 sb.last_pool = svn_pool_create(scratch_pool); 1613 1614 /* We want the first txdelta to be against the empty file. */ 1615 sb.last_root = NULL; 1616 sb.last_path = NULL; 1617 1618 /* Create an empty hash table for the first property diff. */ 1619 sb.last_props = apr_hash_make(sb.last_pool); 1620 1621 1622 /* Get the revisions we are interested in. */ 1623 duplicate_path_revs = apr_hash_make(scratch_pool); 1624 mainline_path_revisions = apr_array_make(scratch_pool, 100, 1625 sizeof(struct path_revision *)); 1626 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path, 1627 start, end, include_merged_revisions, 1628 FALSE, duplicate_path_revs, 1629 authz_read_func, authz_read_baton, 1630 scratch_pool, sb.iterpool)); 1631 1632 /* If we are including merged revisions, go get those, too. */ 1633 if (include_merged_revisions) 1634 SVN_ERR(find_merged_revisions(&merged_path_revisions, start, 1635 mainline_path_revisions, repos, 1636 duplicate_path_revs, authz_read_func, 1637 authz_read_baton, 1638 scratch_pool, sb.iterpool)); 1639 else 1640 merged_path_revisions = apr_array_make(scratch_pool, 0, 1641 sizeof(struct path_revision *)); 1642 1643 /* We must have at least one revision to get. */ 1644 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0); 1645 1646 /* Walk through both mainline and merged revisions, and send them in 1647 reverse chronological order, interleaving as appropriate. */ 1648 mainline_pos = mainline_path_revisions->nelts - 1; 1649 merged_pos = merged_path_revisions->nelts - 1; 1650 while (mainline_pos >= 0 && merged_pos >= 0) 1651 { 1652 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1653 mainline_pos, 1654 struct path_revision *); 1655 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1656 merged_pos, 1657 struct path_revision *); 1658 1659 if (main_pr->revnum <= merged_pr->revnum) 1660 { 1661 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, 1662 handler_baton)); 1663 mainline_pos -= 1; 1664 } 1665 else 1666 { 1667 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1668 handler_baton)); 1669 merged_pos -= 1; 1670 } 1671 } 1672 1673 /* Send any remaining revisions from the mainline list. */ 1674 for (; mainline_pos >= 0; mainline_pos -= 1) 1675 { 1676 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1677 mainline_pos, 1678 struct path_revision *); 1679 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton)); 1680 } 1681 1682 /* Ditto for the merged list. */ 1683 for (; merged_pos >= 0; merged_pos -= 1) 1684 { 1685 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1686 merged_pos, 1687 struct path_revision *); 1688 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1689 handler_baton)); 1690 } 1691 1692 svn_pool_destroy(sb.last_pool); 1693 svn_pool_destroy(sb.iterpool); 1694 1695 return SVN_NO_ERROR; 1696} 1697