1/* log.c --- retrieving log messages 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23 24#include <stdlib.h> 25#define APR_WANT_STRFUNC 26#include <apr_want.h> 27 28#include "svn_compat.h" 29#include "svn_private_config.h" 30#include "svn_hash.h" 31#include "svn_pools.h" 32#include "svn_error.h" 33#include "svn_path.h" 34#include "svn_fs.h" 35#include "svn_repos.h" 36#include "svn_string.h" 37#include "svn_sorts.h" 38#include "svn_props.h" 39#include "svn_mergeinfo.h" 40#include "repos.h" 41#include "private/svn_fspath.h" 42#include "private/svn_mergeinfo_private.h" 43#include "private/svn_subr_private.h" 44 45 46 47svn_error_t * 48svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level, 49 svn_repos_t *repos, 50 svn_revnum_t revision, 51 svn_repos_authz_func_t authz_read_func, 52 void *authz_read_baton, 53 apr_pool_t *pool) 54{ 55 svn_fs_t *fs = svn_repos_fs(repos); 56 svn_fs_root_t *rev_root; 57 apr_hash_t *changes; 58 apr_hash_index_t *hi; 59 svn_boolean_t found_readable = FALSE; 60 svn_boolean_t found_unreadable = FALSE; 61 apr_pool_t *subpool; 62 63 /* By default, we'll grant full read access to REVISION. */ 64 *access_level = svn_repos_revision_access_full; 65 66 /* No auth-checking function? We're done. */ 67 if (! authz_read_func) 68 return SVN_NO_ERROR; 69 70 /* Fetch the changes associated with REVISION. */ 71 SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool)); 72 SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool)); 73 74 /* No changed paths? We're done. */ 75 if (apr_hash_count(changes) == 0) 76 return SVN_NO_ERROR; 77 78 /* Otherwise, we have to check the readability of each changed 79 path, or at least enough to answer the question asked. */ 80 subpool = svn_pool_create(pool); 81 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 82 { 83 const void *key; 84 void *val; 85 svn_fs_path_change2_t *change; 86 svn_boolean_t readable; 87 88 svn_pool_clear(subpool); 89 apr_hash_this(hi, &key, NULL, &val); 90 change = val; 91 92 SVN_ERR(authz_read_func(&readable, rev_root, key, 93 authz_read_baton, subpool)); 94 if (! readable) 95 found_unreadable = TRUE; 96 else 97 found_readable = TRUE; 98 99 /* If we have at least one of each (readable/unreadable), we 100 have our answer. */ 101 if (found_readable && found_unreadable) 102 goto decision; 103 104 switch (change->change_kind) 105 { 106 case svn_fs_path_change_add: 107 case svn_fs_path_change_replace: 108 { 109 const char *copyfrom_path; 110 svn_revnum_t copyfrom_rev; 111 112 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 113 rev_root, key, subpool)); 114 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 115 { 116 svn_fs_root_t *copyfrom_root; 117 SVN_ERR(svn_fs_revision_root(©from_root, fs, 118 copyfrom_rev, subpool)); 119 SVN_ERR(authz_read_func(&readable, 120 copyfrom_root, copyfrom_path, 121 authz_read_baton, subpool)); 122 if (! readable) 123 found_unreadable = TRUE; 124 125 /* If we have at least one of each (readable/unreadable), we 126 have our answer. */ 127 if (found_readable && found_unreadable) 128 goto decision; 129 } 130 } 131 break; 132 133 case svn_fs_path_change_delete: 134 case svn_fs_path_change_modify: 135 default: 136 break; 137 } 138 } 139 140 decision: 141 svn_pool_destroy(subpool); 142 143 /* Either every changed path was unreadable... */ 144 if (! found_readable) 145 *access_level = svn_repos_revision_access_none; 146 147 /* ... or some changed path was unreadable... */ 148 else if (found_unreadable) 149 *access_level = svn_repos_revision_access_partial; 150 151 /* ... or every changed path was readable (the default). */ 152 return SVN_NO_ERROR; 153} 154 155 156/* Store as keys in CHANGED the paths of all node in ROOT that show a 157 * significant change. "Significant" means that the text or 158 * properties of the node were changed, or that the node was added or 159 * deleted. 160 * 161 * The CHANGED hash set and its keys and values are allocated in POOL; 162 * keys are const char * paths and values are svn_log_changed_path_t. 163 * 164 * To prevent changes from being processed over and over again, the 165 * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the 166 * latter is NULL, we will request the list inside this function. 167 * 168 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 169 * AUTHZ_READ_BATON and FS) to check whether each changed-path (and 170 * copyfrom_path) is readable: 171 * 172 * - If some paths are readable and some are not, then silently 173 * omit the unreadable paths from the CHANGED hash, and return 174 * SVN_ERR_AUTHZ_PARTIALLY_READABLE. 175 * 176 * - If absolutely every changed-path (and copyfrom_path) is 177 * unreadable, then return an empty CHANGED hash and 178 * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision 179 * which truly has no changed paths from a revision in which all 180 * paths are unreadable.) 181 */ 182static svn_error_t * 183detect_changed(apr_hash_t **changed, 184 svn_fs_root_t *root, 185 svn_fs_t *fs, 186 apr_hash_t *prefetched_changes, 187 svn_repos_authz_func_t authz_read_func, 188 void *authz_read_baton, 189 apr_pool_t *pool) 190{ 191 apr_hash_t *changes = prefetched_changes; 192 apr_hash_index_t *hi; 193 apr_pool_t *subpool; 194 svn_boolean_t found_readable = FALSE; 195 svn_boolean_t found_unreadable = FALSE; 196 197 *changed = svn_hash__make(pool); 198 if (changes == NULL) 199 SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); 200 201 if (apr_hash_count(changes) == 0) 202 /* No paths changed in this revision? Uh, sure, I guess the 203 revision is readable, then. */ 204 return SVN_NO_ERROR; 205 206 subpool = svn_pool_create(pool); 207 208 for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 209 { 210 /* NOTE: Much of this loop is going to look quite similar to 211 svn_repos_check_revision_access(), but we have to do more things 212 here, so we'll live with the duplication. */ 213 const void *key; 214 void *val; 215 svn_fs_path_change2_t *change; 216 const char *path; 217 char action; 218 svn_log_changed_path2_t *item; 219 220 svn_pool_clear(subpool); 221 222 /* KEY will be the path, VAL the change. */ 223 apr_hash_this(hi, &key, NULL, &val); 224 path = (const char *) key; 225 change = val; 226 227 /* Skip path if unreadable. */ 228 if (authz_read_func) 229 { 230 svn_boolean_t readable; 231 SVN_ERR(authz_read_func(&readable, 232 root, path, 233 authz_read_baton, subpool)); 234 if (! readable) 235 { 236 found_unreadable = TRUE; 237 continue; 238 } 239 } 240 241 /* At least one changed-path was readable. */ 242 found_readable = TRUE; 243 244 switch (change->change_kind) 245 { 246 case svn_fs_path_change_reset: 247 continue; 248 249 case svn_fs_path_change_add: 250 action = 'A'; 251 break; 252 253 case svn_fs_path_change_replace: 254 action = 'R'; 255 break; 256 257 case svn_fs_path_change_delete: 258 action = 'D'; 259 break; 260 261 case svn_fs_path_change_modify: 262 default: 263 action = 'M'; 264 break; 265 } 266 267 item = svn_log_changed_path2_create(pool); 268 item->action = action; 269 item->node_kind = change->node_kind; 270 item->copyfrom_rev = SVN_INVALID_REVNUM; 271 item->text_modified = change->text_mod ? svn_tristate_true 272 : svn_tristate_false; 273 item->props_modified = change->prop_mod ? svn_tristate_true 274 : svn_tristate_false; 275 276 /* Pre-1.6 revision files don't store the change path kind, so fetch 277 it manually. */ 278 if (item->node_kind == svn_node_unknown) 279 { 280 svn_fs_root_t *check_root = root; 281 const char *check_path = path; 282 283 /* Deleted items don't exist so check earlier revision. We 284 know the parent must exist and could be a copy */ 285 if (change->change_kind == svn_fs_path_change_delete) 286 { 287 svn_fs_history_t *history; 288 svn_revnum_t prev_rev; 289 const char *parent_path, *name; 290 291 svn_fspath__split(&parent_path, &name, path, subpool); 292 293 SVN_ERR(svn_fs_node_history(&history, root, parent_path, 294 subpool)); 295 296 /* Two calls because the first call returns the original 297 revision as the deleted child means it is 'interesting' */ 298 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 299 SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 300 301 SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history, 302 subpool)); 303 SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool)); 304 check_path = svn_fspath__join(parent_path, name, subpool); 305 } 306 307 SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path, 308 subpool)); 309 } 310 311 312 if ((action == 'A') || (action == 'R')) 313 { 314 const char *copyfrom_path = change->copyfrom_path; 315 svn_revnum_t copyfrom_rev = change->copyfrom_rev; 316 317 /* the following is a potentially expensive operation since on FSFS 318 we will follow the DAG from ROOT to PATH and that requires 319 actually reading the directories along the way. */ 320 if (!change->copyfrom_known) 321 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 322 root, path, subpool)); 323 324 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 325 { 326 svn_boolean_t readable = TRUE; 327 328 if (authz_read_func) 329 { 330 svn_fs_root_t *copyfrom_root; 331 332 SVN_ERR(svn_fs_revision_root(©from_root, fs, 333 copyfrom_rev, subpool)); 334 SVN_ERR(authz_read_func(&readable, 335 copyfrom_root, copyfrom_path, 336 authz_read_baton, subpool)); 337 if (! readable) 338 found_unreadable = TRUE; 339 } 340 341 if (readable) 342 { 343 item->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 344 item->copyfrom_rev = copyfrom_rev; 345 } 346 } 347 } 348 svn_hash_sets(*changed, apr_pstrdup(pool, path), item); 349 } 350 351 svn_pool_destroy(subpool); 352 353 if (! found_readable) 354 /* Every changed-path was unreadable. */ 355 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, 356 NULL, NULL); 357 358 if (found_unreadable) 359 /* At least one changed-path was unreadable. */ 360 return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE, 361 NULL, NULL); 362 363 /* Every changed-path was readable. */ 364 return SVN_NO_ERROR; 365} 366 367/* This is used by svn_repos_get_logs to keep track of multiple 368 * path history information while working through history. 369 * 370 * The two pools are swapped after each iteration through history because 371 * to get the next history requires the previous one. 372 */ 373struct path_info 374{ 375 svn_stringbuf_t *path; 376 svn_revnum_t history_rev; 377 svn_boolean_t done; 378 svn_boolean_t first_time; 379 380 /* If possible, we like to keep open the history object for each path, 381 since it avoids needed to open and close it many times as we walk 382 backwards in time. To do so we need two pools, so that we can clear 383 one each time through. If we're not holding the history open for 384 this path then these three pointers will be NULL. */ 385 svn_fs_history_t *hist; 386 apr_pool_t *newpool; 387 apr_pool_t *oldpool; 388}; 389 390/* Advance to the next history for the path. 391 * 392 * If INFO->HIST is not NULL we do this using that existing history object, 393 * otherwise we open a new one. 394 * 395 * If no more history is available or the history revision is less 396 * (earlier) than START, or the history is not available due 397 * to authorization, then INFO->DONE is set to TRUE. 398 * 399 * A STRICT value of FALSE will indicate to follow history across copied 400 * paths. 401 * 402 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 403 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if 404 * we do indeed find more history for the path. 405 */ 406static svn_error_t * 407get_history(struct path_info *info, 408 svn_fs_t *fs, 409 svn_boolean_t strict, 410 svn_repos_authz_func_t authz_read_func, 411 void *authz_read_baton, 412 svn_revnum_t start, 413 apr_pool_t *pool) 414{ 415 svn_fs_root_t *history_root = NULL; 416 svn_fs_history_t *hist; 417 apr_pool_t *subpool; 418 const char *path; 419 420 if (info->hist) 421 { 422 subpool = info->newpool; 423 424 SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool)); 425 426 hist = info->hist; 427 } 428 else 429 { 430 subpool = svn_pool_create(pool); 431 432 /* Open the history located at the last rev we were at. */ 433 SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev, 434 subpool)); 435 436 SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data, 437 subpool)); 438 439 SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 440 441 if (info->first_time) 442 info->first_time = FALSE; 443 else 444 SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 445 } 446 447 if (! hist) 448 { 449 svn_pool_destroy(subpool); 450 if (info->oldpool) 451 svn_pool_destroy(info->oldpool); 452 info->done = TRUE; 453 return SVN_NO_ERROR; 454 } 455 456 /* Fetch the location information for this history step. */ 457 SVN_ERR(svn_fs_history_location(&path, &info->history_rev, 458 hist, subpool)); 459 460 svn_stringbuf_set(info->path, path); 461 462 /* If this history item predates our START revision then 463 don't fetch any more for this path. */ 464 if (info->history_rev < start) 465 { 466 svn_pool_destroy(subpool); 467 if (info->oldpool) 468 svn_pool_destroy(info->oldpool); 469 info->done = TRUE; 470 return SVN_NO_ERROR; 471 } 472 473 /* Is the history item readable? If not, done with path. */ 474 if (authz_read_func) 475 { 476 svn_boolean_t readable; 477 SVN_ERR(svn_fs_revision_root(&history_root, fs, 478 info->history_rev, 479 subpool)); 480 SVN_ERR(authz_read_func(&readable, history_root, 481 info->path->data, 482 authz_read_baton, 483 subpool)); 484 if (! readable) 485 info->done = TRUE; 486 } 487 488 if (! info->hist) 489 { 490 svn_pool_destroy(subpool); 491 } 492 else 493 { 494 apr_pool_t *temppool = info->oldpool; 495 info->oldpool = info->newpool; 496 svn_pool_clear(temppool); 497 info->newpool = temppool; 498 } 499 500 return SVN_NO_ERROR; 501} 502 503/* Set INFO->HIST to the next history for the path *if* there is history 504 * available and INFO->HISTORY_REV is equal to or greater than CURRENT. 505 * 506 * *CHANGED is set to TRUE if the path has history in the CURRENT revision, 507 * otherwise it is not touched. 508 * 509 * If we do need to get the next history revision for the path, call 510 * get_history to do it -- see it for details. 511 */ 512static svn_error_t * 513check_history(svn_boolean_t *changed, 514 struct path_info *info, 515 svn_fs_t *fs, 516 svn_revnum_t current, 517 svn_boolean_t strict, 518 svn_repos_authz_func_t authz_read_func, 519 void *authz_read_baton, 520 svn_revnum_t start, 521 apr_pool_t *pool) 522{ 523 /* If we're already done with histories for this path, 524 don't try to fetch any more. */ 525 if (info->done) 526 return SVN_NO_ERROR; 527 528 /* If the last rev we got for this path is less than CURRENT, 529 then just return and don't fetch history for this path. 530 The caller will get to this rev eventually or else reach 531 the limit. */ 532 if (info->history_rev < current) 533 return SVN_NO_ERROR; 534 535 /* If the last rev we got for this path is equal to CURRENT 536 then set *CHANGED to true and get the next history 537 rev where this path was changed. */ 538 *changed = TRUE; 539 return get_history(info, fs, strict, authz_read_func, 540 authz_read_baton, start, pool); 541} 542 543/* Return the next interesting revision in our list of HISTORIES. */ 544static svn_revnum_t 545next_history_rev(const apr_array_header_t *histories) 546{ 547 svn_revnum_t next_rev = SVN_INVALID_REVNUM; 548 int i; 549 550 for (i = 0; i < histories->nelts; ++i) 551 { 552 struct path_info *info = APR_ARRAY_IDX(histories, i, 553 struct path_info *); 554 if (info->done) 555 continue; 556 if (info->history_rev > next_rev) 557 next_rev = info->history_rev; 558 } 559 560 return next_rev; 561} 562 563/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to 564 catalogs describing how mergeinfo values on paths (which are the 565 keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES 566 already contains the changed paths for REV, use that. Otherwise, 567 request that data and return it in *PREFETCHED_CHANGES. */ 568/* ### TODO: This would make a *great*, useful public function, 569 ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */ 570static svn_error_t * 571fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog, 572 svn_mergeinfo_catalog_t *added_mergeinfo_catalog, 573 apr_hash_t **prefetched_changes, 574 svn_fs_t *fs, 575 svn_revnum_t rev, 576 apr_pool_t *result_pool, 577 apr_pool_t *scratch_pool) 578 579{ 580 svn_fs_root_t *root; 581 apr_pool_t *iterpool; 582 apr_hash_index_t *hi; 583 584 /* Initialize return variables. */ 585 *deleted_mergeinfo_catalog = svn_hash__make(result_pool); 586 *added_mergeinfo_catalog = svn_hash__make(result_pool); 587 588 /* Revision 0 has no mergeinfo and no mergeinfo changes. */ 589 if (rev == 0) 590 return SVN_NO_ERROR; 591 592 /* We're going to use the changed-paths information for REV to 593 narrow down our search. */ 594 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 595 if (*prefetched_changes == NULL) 596 SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool)); 597 598 /* No changed paths? We're done. */ 599 if (apr_hash_count(*prefetched_changes) == 0) 600 return SVN_NO_ERROR; 601 602 /* Loop over changes, looking for anything that might carry an 603 svn:mergeinfo change and is one of our paths of interest, or a 604 child or [grand]parent directory thereof. */ 605 iterpool = svn_pool_create(scratch_pool); 606 for (hi = apr_hash_first(scratch_pool, *prefetched_changes); 607 hi; 608 hi = apr_hash_next(hi)) 609 { 610 const void *key; 611 void *val; 612 svn_fs_path_change2_t *change; 613 const char *changed_path, *base_path = NULL; 614 svn_revnum_t base_rev = SVN_INVALID_REVNUM; 615 svn_fs_root_t *base_root = NULL; 616 svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value; 617 618 svn_pool_clear(iterpool); 619 620 /* KEY will be the path, VAL the change. */ 621 apr_hash_this(hi, &key, NULL, &val); 622 changed_path = key; 623 change = val; 624 625 /* If there was no property change on this item, ignore it. */ 626 if (! change->prop_mod) 627 continue; 628 629 switch (change->change_kind) 630 { 631 632 /* ### TODO: Can the add, replace, and modify cases be joined 633 ### together to all use svn_repos__prev_location()? The 634 ### difference would be the fallback case (path/rev-1 for 635 ### modifies, NULL otherwise). -- cmpilato */ 636 637 /* If the path was added or replaced, see if it was created via 638 copy. If so, that will tell us where its previous location 639 was. If not, there's no previous location to examine. */ 640 case svn_fs_path_change_add: 641 case svn_fs_path_change_replace: 642 { 643 const char *copyfrom_path; 644 svn_revnum_t copyfrom_rev; 645 646 SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 647 root, changed_path, iterpool)); 648 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 649 { 650 base_path = apr_pstrdup(scratch_pool, copyfrom_path); 651 base_rev = copyfrom_rev; 652 } 653 break; 654 } 655 656 /* If the path was merely modified, see if its previous 657 location was affected by a copy which happened in this 658 revision before assuming it holds the same path it did the 659 previous revision. */ 660 case svn_fs_path_change_modify: 661 { 662 svn_revnum_t appeared_rev; 663 664 SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path, 665 &base_rev, fs, rev, 666 changed_path, iterpool)); 667 668 /* If this path isn't the result of a copy that occurred 669 in this revision, we can find the previous version of 670 it in REV - 1 at the same path. */ 671 if (! (base_path && SVN_IS_VALID_REVNUM(base_rev) 672 && (appeared_rev == rev))) 673 { 674 base_path = changed_path; 675 base_rev = rev - 1; 676 } 677 break; 678 } 679 680 /* We don't care about any of the other cases. */ 681 case svn_fs_path_change_delete: 682 case svn_fs_path_change_reset: 683 default: 684 continue; 685 } 686 687 /* If there was a base location, fetch its mergeinfo property value. */ 688 if (base_path && SVN_IS_VALID_REVNUM(base_rev)) 689 { 690 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool)); 691 SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path, 692 SVN_PROP_MERGEINFO, iterpool)); 693 } 694 695 /* Now fetch the current (as of REV) mergeinfo property value. */ 696 SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path, 697 SVN_PROP_MERGEINFO, iterpool)); 698 699 /* No mergeinfo on either the new or previous location? Just 700 skip it. (If there *was* a change, it would have been in 701 inherited mergeinfo only, which should be picked up by the 702 iteration of this loop that finds the parent paths that 703 really got changed.) */ 704 if (! (mergeinfo_value || prev_mergeinfo_value)) 705 continue; 706 707 /* If mergeinfo was explicitly added or removed on this path, we 708 need to check to see if that was a real semantic change of 709 meaning. So, fill in the "missing" mergeinfo value with the 710 inherited mergeinfo for that path/revision. */ 711 if (prev_mergeinfo_value && (! mergeinfo_value)) 712 { 713 apr_array_header_t *query_paths = 714 apr_array_make(iterpool, 1, sizeof(const char *)); 715 svn_mergeinfo_t tmp_mergeinfo; 716 svn_mergeinfo_catalog_t tmp_catalog; 717 718 APR_ARRAY_PUSH(query_paths, const char *) = changed_path; 719 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, 720 query_paths, svn_mergeinfo_inherited, 721 FALSE, TRUE, iterpool, iterpool)); 722 tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path); 723 if (tmp_mergeinfo) 724 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value, 725 tmp_mergeinfo, 726 iterpool)); 727 } 728 else if (mergeinfo_value && (! prev_mergeinfo_value) 729 && base_path && SVN_IS_VALID_REVNUM(base_rev)) 730 { 731 apr_array_header_t *query_paths = 732 apr_array_make(iterpool, 1, sizeof(const char *)); 733 svn_mergeinfo_t tmp_mergeinfo; 734 svn_mergeinfo_catalog_t tmp_catalog; 735 736 APR_ARRAY_PUSH(query_paths, const char *) = base_path; 737 SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root, 738 query_paths, svn_mergeinfo_inherited, 739 FALSE, TRUE, iterpool, iterpool)); 740 tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path); 741 if (tmp_mergeinfo) 742 SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value, 743 tmp_mergeinfo, 744 iterpool)); 745 } 746 747 /* If the old and new mergeinfo differ in any way, store the 748 before and after mergeinfo values in our return hashes. */ 749 if ((prev_mergeinfo_value && (! mergeinfo_value)) 750 || ((! prev_mergeinfo_value) && mergeinfo_value) 751 || (prev_mergeinfo_value && mergeinfo_value 752 && (! svn_string_compare(mergeinfo_value, 753 prev_mergeinfo_value)))) 754 { 755 svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL; 756 svn_mergeinfo_t deleted, added; 757 const char *hash_path; 758 759 if (mergeinfo_value) 760 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, 761 mergeinfo_value->data, iterpool)); 762 if (prev_mergeinfo_value) 763 SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo, 764 prev_mergeinfo_value->data, iterpool)); 765 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 766 mergeinfo, FALSE, result_pool, 767 iterpool)); 768 769 /* Toss interesting stuff into our return catalogs. */ 770 hash_path = apr_pstrdup(result_pool, changed_path); 771 svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted); 772 svn_hash_sets(*added_mergeinfo_catalog, hash_path, added); 773 } 774 } 775 776 svn_pool_destroy(iterpool); 777 return SVN_NO_ERROR; 778} 779 780 781/* Determine what (if any) mergeinfo for PATHS was modified in 782 revision REV, returning the differences for added mergeinfo in 783 *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. 784 If *PREFETCHED_CAHNGES already contains the changed paths for 785 REV, use that. Otherwise, request that data and return it in 786 *PREFETCHED_CHANGES. 787 Use POOL for all allocations. */ 788static svn_error_t * 789get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo, 790 svn_mergeinfo_t *deleted_mergeinfo, 791 apr_hash_t **prefetched_changes, 792 svn_fs_t *fs, 793 const apr_array_header_t *paths, 794 svn_revnum_t rev, 795 apr_pool_t *result_pool, 796 apr_pool_t *scratch_pool) 797{ 798 svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog; 799 apr_hash_index_t *hi; 800 svn_fs_root_t *root; 801 apr_pool_t *iterpool; 802 int i; 803 svn_error_t *err; 804 805 /* Initialize return value. */ 806 *added_mergeinfo = svn_hash__make(result_pool); 807 *deleted_mergeinfo = svn_hash__make(result_pool); 808 809 /* If we're asking about revision 0, there's no mergeinfo to be found. */ 810 if (rev == 0) 811 return SVN_NO_ERROR; 812 813 /* No paths? No mergeinfo. */ 814 if (! paths->nelts) 815 return SVN_NO_ERROR; 816 817 /* Create a work subpool and get a root for REV. */ 818 SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 819 820 /* Fetch the mergeinfo changes for REV. */ 821 err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog, 822 &added_mergeinfo_catalog, 823 prefetched_changes, 824 fs, rev, scratch_pool, scratch_pool); 825 if (err) 826 { 827 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 828 { 829 /* Issue #3896: If invalid mergeinfo is encountered the 830 best we can do is ignore it and act as if there were 831 no mergeinfo modifications. */ 832 svn_error_clear(err); 833 return SVN_NO_ERROR; 834 } 835 else 836 { 837 return svn_error_trace(err); 838 } 839 } 840 841 /* In most revisions, there will be no mergeinfo change at all. */ 842 if ( apr_hash_count(deleted_mergeinfo_catalog) == 0 843 && apr_hash_count(added_mergeinfo_catalog) == 0) 844 return SVN_NO_ERROR; 845 846 /* Check our PATHS for any changes to their inherited mergeinfo. 847 (We deal with changes to mergeinfo directly *on* the paths in the 848 following loop.) */ 849 iterpool = svn_pool_create(scratch_pool); 850 for (i = 0; i < paths->nelts; i++) 851 { 852 const char *path = APR_ARRAY_IDX(paths, i, const char *); 853 const char *prev_path; 854 apr_ssize_t klen; 855 svn_revnum_t appeared_rev, prev_rev; 856 svn_fs_root_t *prev_root; 857 svn_mergeinfo_catalog_t catalog, inherited_catalog; 858 svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added, 859 prev_inherited_mergeinfo, inherited_mergeinfo; 860 apr_array_header_t *query_paths; 861 862 svn_pool_clear(iterpool); 863 864 /* If this path is represented in the changed-mergeinfo hashes, 865 we'll deal with it in the loop below. */ 866 if (svn_hash_gets(deleted_mergeinfo_catalog, path)) 867 continue; 868 869 /* Figure out what path/rev to compare against. Ignore 870 not-found errors returned by the filesystem. */ 871 err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 872 fs, rev, path, iterpool); 873 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 874 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)) 875 { 876 svn_error_clear(err); 877 err = SVN_NO_ERROR; 878 continue; 879 } 880 SVN_ERR(err); 881 882 /* If this path isn't the result of a copy that occurred in this 883 revision, we can find the previous version of it in REV - 1 884 at the same path. */ 885 if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev) 886 && (appeared_rev == rev))) 887 { 888 prev_path = path; 889 prev_rev = rev - 1; 890 } 891 892 /* Fetch the previous mergeinfo (including inherited stuff) for 893 this path. Ignore not-found errors returned by the 894 filesystem or invalid mergeinfo (Issue #3896).*/ 895 SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool)); 896 query_paths = apr_array_make(iterpool, 1, sizeof(const char *)); 897 APR_ARRAY_PUSH(query_paths, const char *) = prev_path; 898 err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths, 899 svn_mergeinfo_inherited, FALSE, TRUE, 900 iterpool, iterpool); 901 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 902 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 903 err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 904 { 905 svn_error_clear(err); 906 err = SVN_NO_ERROR; 907 continue; 908 } 909 SVN_ERR(err); 910 911 /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due 912 to move as a merge': A copy where the source and destination inherit 913 mergeinfo from the same parent means the inherited mergeinfo of the 914 source and destination will differ, but this diffrence is not 915 indicative of a merge unless the mergeinfo on the inherited parent 916 has actually changed. 917 918 To check for this we must fetch the "raw" previous inherited 919 mergeinfo and the "raw" mergeinfo @REV then compare these. */ 920 SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths, 921 svn_mergeinfo_nearest_ancestor, FALSE, 922 FALSE, /* adjust_inherited_mergeinfo */ 923 iterpool, iterpool)); 924 925 klen = strlen(prev_path); 926 prev_mergeinfo = apr_hash_get(catalog, prev_path, klen); 927 prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen); 928 929 /* Fetch the current mergeinfo (as of REV, and including 930 inherited stuff) for this path. */ 931 APR_ARRAY_IDX(query_paths, 0, const char *) = path; 932 SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths, 933 svn_mergeinfo_inherited, FALSE, TRUE, 934 iterpool, iterpool)); 935 936 /* Issue #4022 again, fetch the raw inherited mergeinfo. */ 937 SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths, 938 svn_mergeinfo_nearest_ancestor, FALSE, 939 FALSE, /* adjust_inherited_mergeinfo */ 940 iterpool, iterpool)); 941 942 klen = strlen(path); 943 mergeinfo = apr_hash_get(catalog, path, klen); 944 inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen); 945 946 if (!prev_mergeinfo && !mergeinfo) 947 continue; 948 949 /* Last bit of issue #4022 checking. */ 950 if (prev_inherited_mergeinfo && inherited_mergeinfo) 951 { 952 svn_boolean_t inherits_same_mergeinfo; 953 954 SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo, 955 prev_inherited_mergeinfo, 956 inherited_mergeinfo, 957 TRUE, iterpool)); 958 /* If a copy rather than an actual merge brought about an 959 inherited mergeinfo change then we are finished. */ 960 if (inherits_same_mergeinfo) 961 continue; 962 } 963 else 964 { 965 svn_boolean_t same_mergeinfo; 966 SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo, 967 prev_inherited_mergeinfo, 968 FALSE, 969 TRUE, iterpool)); 970 if (same_mergeinfo) 971 continue; 972 } 973 974 /* Compare, constrast, and combine the results. */ 975 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 976 mergeinfo, FALSE, result_pool, iterpool)); 977 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted, 978 result_pool, iterpool)); 979 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added, 980 result_pool, iterpool)); 981 } 982 983 /* Merge all the mergeinfos which are, or are children of, one of 984 our paths of interest into one giant delta mergeinfo. */ 985 for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog); 986 hi; hi = apr_hash_next(hi)) 987 { 988 const void *key; 989 apr_ssize_t klen; 990 void *val; 991 const char *changed_path; 992 svn_mergeinfo_t added, deleted; 993 994 /* The path is the key, the mergeinfo delta is the value. */ 995 apr_hash_this(hi, &key, &klen, &val); 996 changed_path = key; 997 added = val; 998 999 for (i = 0; i < paths->nelts; i++) 1000 { 1001 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1002 if (! svn_fspath__skip_ancestor(path, changed_path)) 1003 continue; 1004 svn_pool_clear(iterpool); 1005 deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen); 1006 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, 1007 svn_mergeinfo_dup(deleted, result_pool), 1008 result_pool, iterpool)); 1009 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, 1010 svn_mergeinfo_dup(added, result_pool), 1011 result_pool, iterpool)); 1012 1013 break; 1014 } 1015 } 1016 1017 svn_pool_destroy(iterpool); 1018 return SVN_NO_ERROR; 1019} 1020 1021 1022/* Fill LOG_ENTRY with history information in FS at REV. */ 1023static svn_error_t * 1024fill_log_entry(svn_log_entry_t *log_entry, 1025 svn_revnum_t rev, 1026 svn_fs_t *fs, 1027 apr_hash_t *prefetched_changes, 1028 svn_boolean_t discover_changed_paths, 1029 const apr_array_header_t *revprops, 1030 svn_repos_authz_func_t authz_read_func, 1031 void *authz_read_baton, 1032 apr_pool_t *pool) 1033{ 1034 apr_hash_t *r_props, *changed_paths = NULL; 1035 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE; 1036 1037 /* Discover changed paths if the user requested them 1038 or if we need to check that they are readable. */ 1039 if ((rev > 0) 1040 && (authz_read_func || discover_changed_paths)) 1041 { 1042 svn_fs_root_t *newroot; 1043 svn_error_t *patherr; 1044 1045 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool)); 1046 patherr = detect_changed(&changed_paths, 1047 newroot, fs, prefetched_changes, 1048 authz_read_func, authz_read_baton, 1049 pool); 1050 1051 if (patherr 1052 && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE) 1053 { 1054 /* All changed-paths are unreadable, so clear all fields. */ 1055 svn_error_clear(patherr); 1056 changed_paths = NULL; 1057 get_revprops = FALSE; 1058 } 1059 else if (patherr 1060 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE) 1061 { 1062 /* At least one changed-path was unreadable, so censor all 1063 but author and date. (The unreadable paths are already 1064 missing from the hash.) */ 1065 svn_error_clear(patherr); 1066 censor_revprops = TRUE; 1067 } 1068 else if (patherr) 1069 return patherr; 1070 1071 /* It may be the case that an authz func was passed in, but 1072 the user still doesn't want to see any changed-paths. */ 1073 if (! discover_changed_paths) 1074 changed_paths = NULL; 1075 } 1076 1077 if (get_revprops) 1078 { 1079 /* User is allowed to see at least some revprops. */ 1080 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool)); 1081 if (revprops == NULL) 1082 { 1083 /* Requested all revprops... */ 1084 if (censor_revprops) 1085 { 1086 /* ... but we can only return author/date. */ 1087 log_entry->revprops = svn_hash__make(pool); 1088 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, 1089 svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR)); 1090 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, 1091 svn_hash_gets(r_props, SVN_PROP_REVISION_DATE)); 1092 } 1093 else 1094 /* ... so return all we got. */ 1095 log_entry->revprops = r_props; 1096 } 1097 else 1098 { 1099 /* Requested only some revprops... */ 1100 int i; 1101 for (i = 0; i < revprops->nelts; i++) 1102 { 1103 char *name = APR_ARRAY_IDX(revprops, i, char *); 1104 svn_string_t *value = svn_hash_gets(r_props, name); 1105 if (censor_revprops 1106 && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0 1107 || strcmp(name, SVN_PROP_REVISION_DATE) == 0)) 1108 /* ... but we can only return author/date. */ 1109 continue; 1110 if (log_entry->revprops == NULL) 1111 log_entry->revprops = svn_hash__make(pool); 1112 svn_hash_sets(log_entry->revprops, name, value); 1113 } 1114 } 1115 } 1116 1117 log_entry->changed_paths = changed_paths; 1118 log_entry->changed_paths2 = changed_paths; 1119 log_entry->revision = rev; 1120 1121 return SVN_NO_ERROR; 1122} 1123 1124/* Send a log message for REV to RECEIVER with its RECEIVER_BATON. 1125 1126 FS is used with REV to fetch the interesting history information, 1127 such as changed paths, revprops, etc. 1128 1129 The detect_changed function is used if either AUTHZ_READ_FUNC is 1130 not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details. 1131 1132 If DESCENDING_ORDER is true, send child messages in descending order. 1133 1134 If REVPROPS is NULL, retrieve all revision properties; else, retrieve 1135 only the revision properties named by the (const char *) array elements 1136 (i.e. retrieve none if the array is empty). 1137 1138 LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and 1139 NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If 1140 HANDLING_MERGED_REVISION is true and *all* changed paths within REV are 1141 already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send 1142 the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was 1143 reverse merged. 1144 1145 If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise 1146 if NESTED_MERGES is not NULL and REV is contained in it, then don't send 1147 the log for REV, otherwise send it normally and add REV to 1148 NESTED_MERGES. */ 1149static svn_error_t * 1150send_log(svn_revnum_t rev, 1151 svn_fs_t *fs, 1152 apr_hash_t *prefetched_changes, 1153 svn_mergeinfo_t log_target_history_as_mergeinfo, 1154 apr_hash_t *nested_merges, 1155 svn_boolean_t discover_changed_paths, 1156 svn_boolean_t subtractive_merge, 1157 svn_boolean_t handling_merged_revision, 1158 const apr_array_header_t *revprops, 1159 svn_boolean_t has_children, 1160 svn_log_entry_receiver_t receiver, 1161 void *receiver_baton, 1162 svn_repos_authz_func_t authz_read_func, 1163 void *authz_read_baton, 1164 apr_pool_t *pool) 1165{ 1166 svn_log_entry_t *log_entry; 1167 /* Assume we want to send the log for REV. */ 1168 svn_boolean_t found_rev_of_interest = TRUE; 1169 1170 log_entry = svn_log_entry_create(pool); 1171 SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes, 1172 discover_changed_paths || handling_merged_revision, 1173 revprops, authz_read_func, authz_read_baton, 1174 pool)); 1175 log_entry->has_children = has_children; 1176 log_entry->subtractive_merge = subtractive_merge; 1177 1178 /* Is REV a merged revision that is already part of 1179 LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no 1180 need to send it, since it already was (or will be) sent. */ 1181 if (handling_merged_revision 1182 && log_entry->changed_paths2 1183 && log_target_history_as_mergeinfo 1184 && apr_hash_count(log_target_history_as_mergeinfo)) 1185 { 1186 apr_hash_index_t *hi; 1187 apr_pool_t *subpool = svn_pool_create(pool); 1188 1189 /* REV was merged in, but it might already be part of the log target's 1190 natural history, so change our starting assumption. */ 1191 found_rev_of_interest = FALSE; 1192 1193 /* Look at each changed path in REV. */ 1194 for (hi = apr_hash_first(subpool, log_entry->changed_paths2); 1195 hi; 1196 hi = apr_hash_next(hi)) 1197 { 1198 svn_boolean_t path_is_in_history = FALSE; 1199 const char *changed_path = svn__apr_hash_index_key(hi); 1200 apr_hash_index_t *hi2; 1201 apr_pool_t *inner_subpool = svn_pool_create(subpool); 1202 1203 /* Look at each path on the log target's mergeinfo. */ 1204 for (hi2 = apr_hash_first(inner_subpool, 1205 log_target_history_as_mergeinfo); 1206 hi2; 1207 hi2 = apr_hash_next(hi2)) 1208 { 1209 const char *mergeinfo_path = 1210 svn__apr_hash_index_key(hi2); 1211 svn_rangelist_t *rangelist = 1212 svn__apr_hash_index_val(hi2); 1213 1214 /* Check whether CHANGED_PATH at revision REV is a child of 1215 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */ 1216 if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path)) 1217 { 1218 int i; 1219 1220 for (i = 0; i < rangelist->nelts; i++) 1221 { 1222 svn_merge_range_t *range = 1223 APR_ARRAY_IDX(rangelist, i, 1224 svn_merge_range_t *); 1225 if (rev > range->start && rev <= range->end) 1226 { 1227 path_is_in_history = TRUE; 1228 break; 1229 } 1230 } 1231 } 1232 if (path_is_in_history) 1233 break; 1234 } 1235 svn_pool_destroy(inner_subpool); 1236 1237 if (!path_is_in_history) 1238 { 1239 /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of 1240 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the 1241 log for REV. */ 1242 found_rev_of_interest = TRUE; 1243 break; 1244 } 1245 } 1246 svn_pool_destroy(subpool); 1247 } 1248 1249 /* If we only got changed paths the sake of detecting redundant merged 1250 revisions, then be sure we don't send that info to the receiver. */ 1251 if (!discover_changed_paths && handling_merged_revision) 1252 log_entry->changed_paths = log_entry->changed_paths2 = NULL; 1253 1254 /* Send the entry to the receiver, unless it is a redundant merged 1255 revision. */ 1256 if (found_rev_of_interest) 1257 { 1258 /* Is REV a merged revision we've already sent? */ 1259 if (nested_merges && handling_merged_revision) 1260 { 1261 svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev, 1262 sizeof(svn_revnum_t *)); 1263 1264 if (merged_rev) 1265 { 1266 /* We already sent REV. */ 1267 return SVN_NO_ERROR; 1268 } 1269 else 1270 { 1271 /* NESTED_REVS needs to last across all the send_log, do_logs, 1272 handle_merged_revisions() recursions, so use the pool it 1273 was created in at the top of the recursion. */ 1274 apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges); 1275 svn_revnum_t *long_lived_rev = apr_palloc(hash_pool, 1276 sizeof(svn_revnum_t)); 1277 *long_lived_rev = rev; 1278 apr_hash_set(nested_merges, long_lived_rev, 1279 sizeof(svn_revnum_t *), long_lived_rev); 1280 } 1281 } 1282 1283 return (*receiver)(receiver_baton, log_entry, pool); 1284 } 1285 else 1286 { 1287 return SVN_NO_ERROR; 1288 } 1289} 1290 1291/* This controls how many history objects we keep open. For any targets 1292 over this number we have to open and close their histories as needed, 1293 which is CPU intensive, but keeps us from using an unbounded amount of 1294 memory. */ 1295#define MAX_OPEN_HISTORIES 32 1296 1297/* Get the histories for PATHS, and store them in *HISTORIES. 1298 1299 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1300 repository locations as fatal -- just ignore them. */ 1301static svn_error_t * 1302get_path_histories(apr_array_header_t **histories, 1303 svn_fs_t *fs, 1304 const apr_array_header_t *paths, 1305 svn_revnum_t hist_start, 1306 svn_revnum_t hist_end, 1307 svn_boolean_t strict_node_history, 1308 svn_boolean_t ignore_missing_locations, 1309 svn_repos_authz_func_t authz_read_func, 1310 void *authz_read_baton, 1311 apr_pool_t *pool) 1312{ 1313 svn_fs_root_t *root; 1314 apr_pool_t *iterpool; 1315 svn_error_t *err; 1316 int i; 1317 1318 /* Create a history object for each path so we can walk through 1319 them all at the same time until we have all changes or LIMIT 1320 is reached. 1321 1322 There is some pool fun going on due to the fact that we have 1323 to hold on to the old pool with the history before we can 1324 get the next history. 1325 */ 1326 *histories = apr_array_make(pool, paths->nelts, 1327 sizeof(struct path_info *)); 1328 1329 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool)); 1330 1331 iterpool = svn_pool_create(pool); 1332 for (i = 0; i < paths->nelts; i++) 1333 { 1334 const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 1335 struct path_info *info = apr_palloc(pool, 1336 sizeof(struct path_info)); 1337 1338 if (authz_read_func) 1339 { 1340 svn_boolean_t readable; 1341 1342 svn_pool_clear(iterpool); 1343 1344 SVN_ERR(authz_read_func(&readable, root, this_path, 1345 authz_read_baton, iterpool)); 1346 if (! readable) 1347 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 1348 } 1349 1350 info->path = svn_stringbuf_create(this_path, pool); 1351 info->done = FALSE; 1352 info->history_rev = hist_end; 1353 info->first_time = TRUE; 1354 1355 if (i < MAX_OPEN_HISTORIES) 1356 { 1357 err = svn_fs_node_history(&info->hist, root, this_path, pool); 1358 if (err 1359 && ignore_missing_locations 1360 && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1361 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1362 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1363 { 1364 svn_error_clear(err); 1365 continue; 1366 } 1367 SVN_ERR(err); 1368 info->newpool = svn_pool_create(pool); 1369 info->oldpool = svn_pool_create(pool); 1370 } 1371 else 1372 { 1373 info->hist = NULL; 1374 info->oldpool = NULL; 1375 info->newpool = NULL; 1376 } 1377 1378 err = get_history(info, fs, 1379 strict_node_history, 1380 authz_read_func, authz_read_baton, 1381 hist_start, pool); 1382 if (err 1383 && ignore_missing_locations 1384 && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1385 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1386 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1387 { 1388 svn_error_clear(err); 1389 continue; 1390 } 1391 SVN_ERR(err); 1392 APR_ARRAY_PUSH(*histories, struct path_info *) = info; 1393 } 1394 svn_pool_destroy(iterpool); 1395 1396 return SVN_NO_ERROR; 1397} 1398 1399/* Remove and return the first item from ARR. */ 1400static void * 1401array_pop_front(apr_array_header_t *arr) 1402{ 1403 void *item = arr->elts; 1404 1405 if (apr_is_empty_array(arr)) 1406 return NULL; 1407 1408 arr->elts += arr->elt_size; 1409 arr->nelts -= 1; 1410 arr->nalloc -= 1; 1411 return item; 1412} 1413 1414/* A struct which represents a single revision range, and the paths which 1415 have mergeinfo in that range. */ 1416struct path_list_range 1417{ 1418 apr_array_header_t *paths; 1419 svn_merge_range_t range; 1420 1421 /* Is RANGE the result of a reverse merge? */ 1422 svn_boolean_t reverse_merge; 1423}; 1424 1425/* A struct which represents "inverse mergeinfo", that is, instead of having 1426 a path->revision_range_list mapping, which is the way mergeinfo is commonly 1427 represented, this struct enables a revision_range_list,path tuple, where 1428 the paths can be accessed by revision. */ 1429struct rangelist_path 1430{ 1431 svn_rangelist_t *rangelist; 1432 const char *path; 1433}; 1434 1435/* Comparator function for combine_mergeinfo_path_lists(). Sorts 1436 rangelist_path structs in increasing order based upon starting revision, 1437 then ending revision of the first element in the rangelist. 1438 1439 This does not sort rangelists based upon subsequent elements, only the 1440 first range. We'll sort any subsequent ranges in the correct order 1441 when they get bumped up to the front by removal of earlier ones, so we 1442 don't really have to sort them here. See combine_mergeinfo_path_lists() 1443 for details. */ 1444static int 1445compare_rangelist_paths(const void *a, const void *b) 1446{ 1447 struct rangelist_path *rpa = *((struct rangelist_path *const *) a); 1448 struct rangelist_path *rpb = *((struct rangelist_path *const *) b); 1449 svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0, 1450 svn_merge_range_t *); 1451 svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0, 1452 svn_merge_range_t *); 1453 1454 if (mra->start < mrb->start) 1455 return -1; 1456 if (mra->start > mrb->start) 1457 return 1; 1458 if (mra->end < mrb->end) 1459 return -1; 1460 if (mra->end > mrb->end) 1461 return 1; 1462 1463 return 0; 1464} 1465 1466/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of 1467 'struct path_list_range's. This list represents the rangelists in 1468 MERGEINFO and each path which has mergeinfo in that range. 1469 If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed 1470 as the result of a reverse merge. */ 1471static svn_error_t * 1472combine_mergeinfo_path_lists(apr_array_header_t **combined_list, 1473 svn_mergeinfo_t mergeinfo, 1474 svn_boolean_t reverse_merge, 1475 apr_pool_t *pool) 1476{ 1477 apr_hash_index_t *hi; 1478 apr_array_header_t *rangelist_paths; 1479 apr_pool_t *subpool = svn_pool_create(pool); 1480 1481 /* Create a list of (revision range, path) tuples from MERGEINFO. */ 1482 rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo), 1483 sizeof(struct rangelist_path *)); 1484 for (hi = apr_hash_first(subpool, mergeinfo); hi; 1485 hi = apr_hash_next(hi)) 1486 { 1487 int i; 1488 struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp)); 1489 apr_hash_this(hi, (void *) &rp->path, NULL, 1490 (void *) &rp->rangelist); 1491 APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp; 1492 1493 /* We need to make local copies of the rangelist, since we will be 1494 modifying it, below. */ 1495 rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool); 1496 1497 /* Make all of the rangelists inclusive, both start and end. */ 1498 for (i = 0; i < rp->rangelist->nelts; i++) 1499 APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1; 1500 } 1501 1502 /* Loop over the (revision range, path) tuples, chopping them into 1503 (revision range, paths) tuples, and appending those to the output 1504 list. */ 1505 if (! *combined_list) 1506 *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *)); 1507 1508 while (rangelist_paths->nelts > 1) 1509 { 1510 svn_revnum_t youngest, next_youngest, tail, youngest_end; 1511 struct path_list_range *plr; 1512 struct rangelist_path *rp; 1513 int num_revs; 1514 int i; 1515 1516 /* First, sort the list such that the start revision of the first 1517 revision arrays are sorted. */ 1518 qsort(rangelist_paths->elts, rangelist_paths->nelts, 1519 rangelist_paths->elt_size, compare_rangelist_paths); 1520 1521 /* Next, find the number of revision ranges which start with the same 1522 revision. */ 1523 rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1524 youngest = 1525 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start; 1526 next_youngest = youngest; 1527 for (num_revs = 1; next_youngest == youngest; num_revs++) 1528 { 1529 if (num_revs == rangelist_paths->nelts) 1530 { 1531 num_revs += 1; 1532 break; 1533 } 1534 rp = APR_ARRAY_IDX(rangelist_paths, num_revs, 1535 struct rangelist_path *); 1536 next_youngest = APR_ARRAY_IDX(rp->rangelist, 0, 1537 struct svn_merge_range_t *)->start; 1538 } 1539 num_revs -= 1; 1540 1541 /* The start of the new range will be YOUNGEST, and we now find the end 1542 of the new range, which should be either one less than the next 1543 earliest start of a rangelist, or the end of the first rangelist. */ 1544 youngest_end = 1545 APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0, 1546 struct rangelist_path *)->rangelist, 1547 0, svn_merge_range_t *)->end; 1548 if ( (next_youngest == youngest) || (youngest_end < next_youngest) ) 1549 tail = youngest_end; 1550 else 1551 tail = next_youngest - 1; 1552 1553 /* Insert the (earliest, tail) tuple into the output list, along with 1554 a list of paths which match it. */ 1555 plr = apr_palloc(pool, sizeof(*plr)); 1556 plr->reverse_merge = reverse_merge; 1557 plr->range.start = youngest; 1558 plr->range.end = tail; 1559 plr->paths = apr_array_make(pool, num_revs, sizeof(const char *)); 1560 for (i = 0; i < num_revs; i++) 1561 APR_ARRAY_PUSH(plr->paths, const char *) = 1562 APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path; 1563 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1564 1565 /* Now, check to see which (rangelist path) combinations we can remove, 1566 and do so. */ 1567 for (i = 0; i < num_revs; i++) 1568 { 1569 svn_merge_range_t *range; 1570 rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *); 1571 range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *); 1572 1573 /* Set the start of the range to beyond the end of the range we 1574 just built. If the range is now "inverted", we can get pop it 1575 off the list. */ 1576 range->start = tail + 1; 1577 if (range->start > range->end) 1578 { 1579 if (rp->rangelist->nelts == 1) 1580 { 1581 /* The range is the only on its list, so we should remove 1582 the entire rangelist_path, adjusting our loop control 1583 variables appropriately. */ 1584 array_pop_front(rangelist_paths); 1585 i--; 1586 num_revs--; 1587 } 1588 else 1589 { 1590 /* We have more than one range on the list, so just remove 1591 the first one. */ 1592 array_pop_front(rp->rangelist); 1593 } 1594 } 1595 } 1596 } 1597 1598 /* Finally, add the last remaining (revision range, path) to the output 1599 list. */ 1600 if (rangelist_paths->nelts > 0) 1601 { 1602 struct rangelist_path *first_rp = 1603 APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1604 while (first_rp->rangelist->nelts > 0) 1605 { 1606 struct path_list_range *plr = apr_palloc(pool, sizeof(*plr)); 1607 1608 plr->reverse_merge = reverse_merge; 1609 plr->paths = apr_array_make(pool, 1, sizeof(const char *)); 1610 APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path; 1611 plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0, 1612 svn_merge_range_t *); 1613 array_pop_front(first_rp->rangelist); 1614 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1615 } 1616 } 1617 1618 svn_pool_destroy(subpool); 1619 1620 return SVN_NO_ERROR; 1621} 1622 1623 1624/* Pity that C is so ... linear. */ 1625static svn_error_t * 1626do_logs(svn_fs_t *fs, 1627 const apr_array_header_t *paths, 1628 svn_mergeinfo_t log_target_history_as_mergeinfo, 1629 svn_mergeinfo_t processed, 1630 apr_hash_t *nested_merges, 1631 svn_revnum_t hist_start, 1632 svn_revnum_t hist_end, 1633 int limit, 1634 svn_boolean_t discover_changed_paths, 1635 svn_boolean_t strict_node_history, 1636 svn_boolean_t include_merged_revisions, 1637 svn_boolean_t handling_merged_revisions, 1638 svn_boolean_t subtractive_merge, 1639 svn_boolean_t ignore_missing_locations, 1640 const apr_array_header_t *revprops, 1641 svn_boolean_t descending_order, 1642 svn_log_entry_receiver_t receiver, 1643 void *receiver_baton, 1644 svn_repos_authz_func_t authz_read_func, 1645 void *authz_read_baton, 1646 apr_pool_t *pool); 1647 1648/* Comparator function for handle_merged_revisions(). Sorts path_list_range 1649 structs in increasing order based on the struct's RANGE.START revision, 1650 then RANGE.END revision. */ 1651static int 1652compare_path_list_range(const void *a, const void *b) 1653{ 1654 struct path_list_range *plr_a = *((struct path_list_range *const *) a); 1655 struct path_list_range *plr_b = *((struct path_list_range *const *) b); 1656 1657 if (plr_a->range.start < plr_b->range.start) 1658 return -1; 1659 if (plr_a->range.start > plr_b->range.start) 1660 return 1; 1661 if (plr_a->range.end < plr_b->range.end) 1662 return -1; 1663 if (plr_a->range.end > plr_b->range.end) 1664 return 1; 1665 1666 return 0; 1667} 1668 1669/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS 1670 (as collected by examining paths of interest to a log operation), and 1671 determine which revisions to report as having been merged or reverse-merged 1672 via the commit resulting in REV. 1673 1674 Silently ignore some failures to find the revisions mentioned in the 1675 added/deleted mergeinfos, as might happen if there is invalid mergeinfo. 1676 1677 Other parameters are as described by do_logs(), around which this 1678 is a recursion wrapper. */ 1679static svn_error_t * 1680handle_merged_revisions(svn_revnum_t rev, 1681 svn_fs_t *fs, 1682 svn_mergeinfo_t log_target_history_as_mergeinfo, 1683 apr_hash_t *nested_merges, 1684 svn_mergeinfo_t processed, 1685 svn_mergeinfo_t added_mergeinfo, 1686 svn_mergeinfo_t deleted_mergeinfo, 1687 svn_boolean_t discover_changed_paths, 1688 svn_boolean_t strict_node_history, 1689 const apr_array_header_t *revprops, 1690 svn_log_entry_receiver_t receiver, 1691 void *receiver_baton, 1692 svn_repos_authz_func_t authz_read_func, 1693 void *authz_read_baton, 1694 apr_pool_t *pool) 1695{ 1696 apr_array_header_t *combined_list = NULL; 1697 svn_log_entry_t *empty_log_entry; 1698 apr_pool_t *iterpool; 1699 int i; 1700 1701 if (apr_hash_count(added_mergeinfo) == 0 1702 && apr_hash_count(deleted_mergeinfo) == 0) 1703 return SVN_NO_ERROR; 1704 1705 if (apr_hash_count(added_mergeinfo)) 1706 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo, 1707 FALSE, pool)); 1708 1709 if (apr_hash_count(deleted_mergeinfo)) 1710 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo, 1711 TRUE, pool)); 1712 1713 SVN_ERR_ASSERT(combined_list != NULL); 1714 qsort(combined_list->elts, combined_list->nelts, 1715 combined_list->elt_size, compare_path_list_range); 1716 1717 /* Because the combined_lists are ordered youngest to oldest, 1718 iterate over them in reverse. */ 1719 iterpool = svn_pool_create(pool); 1720 for (i = combined_list->nelts - 1; i >= 0; i--) 1721 { 1722 struct path_list_range *pl_range 1723 = APR_ARRAY_IDX(combined_list, i, struct path_list_range *); 1724 1725 svn_pool_clear(iterpool); 1726 SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo, 1727 processed, nested_merges, 1728 pl_range->range.start, pl_range->range.end, 0, 1729 discover_changed_paths, strict_node_history, 1730 TRUE, pl_range->reverse_merge, TRUE, TRUE, 1731 revprops, TRUE, receiver, receiver_baton, 1732 authz_read_func, authz_read_baton, iterpool)); 1733 } 1734 svn_pool_destroy(iterpool); 1735 1736 /* Send the empty revision. */ 1737 empty_log_entry = svn_log_entry_create(pool); 1738 empty_log_entry->revision = SVN_INVALID_REVNUM; 1739 return (*receiver)(receiver_baton, empty_log_entry, pool); 1740} 1741 1742/* This is used by do_logs to differentiate between forward and 1743 reverse merges. */ 1744struct added_deleted_mergeinfo 1745{ 1746 svn_mergeinfo_t added_mergeinfo; 1747 svn_mergeinfo_t deleted_mergeinfo; 1748}; 1749 1750/* Reduce the search range PATHS, HIST_START, HIST_END by removing 1751 parts already covered by PROCESSED. If reduction is possible 1752 elements may be removed from PATHS and *START_REDUCED and 1753 *END_REDUCED may be set to a narrower range. */ 1754static svn_error_t * 1755reduce_search(apr_array_header_t *paths, 1756 svn_revnum_t *hist_start, 1757 svn_revnum_t *hist_end, 1758 svn_mergeinfo_t processed, 1759 apr_pool_t *scratch_pool) 1760{ 1761 /* We add 1 to end to compensate for store_search */ 1762 svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end; 1763 svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1; 1764 int i; 1765 1766 for (i = 0; i < paths->nelts; ++i) 1767 { 1768 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1769 svn_rangelist_t *ranges = svn_hash_gets(processed, path); 1770 int j; 1771 1772 if (!ranges) 1773 continue; 1774 1775 /* ranges is ordered, could we use some sort of binary search 1776 rather than iterating? */ 1777 for (j = 0; j < ranges->nelts; ++j) 1778 { 1779 svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j, 1780 svn_merge_range_t *); 1781 if (range->start <= start && range->end >= end) 1782 { 1783 for (j = i; j < paths->nelts - 1; ++j) 1784 APR_ARRAY_IDX(paths, j, const char *) 1785 = APR_ARRAY_IDX(paths, j + 1, const char *); 1786 1787 --paths->nelts; 1788 --i; 1789 break; 1790 } 1791 1792 /* If there is only one path then we also check for a 1793 partial overlap rather than the full overlap above, and 1794 reduce the [hist_start, hist_end] range rather than 1795 dropping the path. */ 1796 if (paths->nelts == 1) 1797 { 1798 if (range->start <= start && range->end > start) 1799 { 1800 if (start == *hist_start) 1801 *hist_start = range->end - 1; 1802 else 1803 *hist_end = range->end - 1; 1804 break; 1805 } 1806 if (range->start < end && range->end >= end) 1807 { 1808 if (start == *hist_start) 1809 *hist_end = range->start; 1810 else 1811 *hist_start = range->start; 1812 break; 1813 } 1814 } 1815 } 1816 } 1817 1818 return SVN_NO_ERROR; 1819} 1820 1821/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */ 1822static svn_error_t * 1823store_search(svn_mergeinfo_t processed, 1824 const apr_array_header_t *paths, 1825 svn_revnum_t hist_start, 1826 svn_revnum_t hist_end, 1827 apr_pool_t *scratch_pool) 1828{ 1829 /* We add 1 to end so that we can use the mergeinfo API to handle 1830 singe revisions where HIST_START is equal to HIST_END. */ 1831 svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end; 1832 svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1; 1833 svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool); 1834 apr_pool_t *processed_pool = apr_hash_pool_get(processed); 1835 int i; 1836 1837 for (i = 0; i < paths->nelts; ++i) 1838 { 1839 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1840 svn_rangelist_t *ranges = apr_array_make(processed_pool, 1, 1841 sizeof(svn_merge_range_t*)); 1842 svn_merge_range_t *range = apr_palloc(processed_pool, 1843 sizeof(svn_merge_range_t)); 1844 1845 range->start = start; 1846 range->end = end; 1847 range->inheritable = TRUE; 1848 APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range; 1849 svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges); 1850 } 1851 SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo, 1852 apr_hash_pool_get(processed), scratch_pool)); 1853 1854 return SVN_NO_ERROR; 1855} 1856 1857/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke 1858 RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send 1859 the logs back as we find them, else buffer the logs and send them back 1860 in youngest->oldest order. 1861 1862 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1863 repository locations as fatal -- just ignore them. 1864 1865 If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo 1866 representing the history of PATHS between HIST_START and HIST_END. 1867 1868 If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for 1869 merged revisions, see INCLUDE_MERGED_REVISIONS argument to 1870 svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a 1871 recursive call for reverse merged revisions. 1872 1873 If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t * 1874 mapped to svn_revnum_t *) for logs that were previously sent. On the first 1875 call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is 1876 TRUE, then NESTED_MERGES will be created on the first call to do_logs, 1877 allocated in POOL. It is then shared across 1878 do_logs()/send_logs()/handle_merge_revisions() recursions, see also the 1879 argument of the same name in send_logs(). 1880 1881 PROCESSED is a mergeinfo hash that represents the paths and 1882 revisions that have already been searched. Allocated like 1883 NESTED_MERGES above. 1884 1885 All other parameters are the same as svn_repos_get_logs4(). 1886 */ 1887static svn_error_t * 1888do_logs(svn_fs_t *fs, 1889 const apr_array_header_t *paths, 1890 svn_mergeinfo_t log_target_history_as_mergeinfo, 1891 svn_mergeinfo_t processed, 1892 apr_hash_t *nested_merges, 1893 svn_revnum_t hist_start, 1894 svn_revnum_t hist_end, 1895 int limit, 1896 svn_boolean_t discover_changed_paths, 1897 svn_boolean_t strict_node_history, 1898 svn_boolean_t include_merged_revisions, 1899 svn_boolean_t subtractive_merge, 1900 svn_boolean_t handling_merged_revisions, 1901 svn_boolean_t ignore_missing_locations, 1902 const apr_array_header_t *revprops, 1903 svn_boolean_t descending_order, 1904 svn_log_entry_receiver_t receiver, 1905 void *receiver_baton, 1906 svn_repos_authz_func_t authz_read_func, 1907 void *authz_read_baton, 1908 apr_pool_t *pool) 1909{ 1910 apr_pool_t *iterpool; 1911 apr_pool_t *subpool = NULL; 1912 apr_array_header_t *revs = NULL; 1913 apr_hash_t *rev_mergeinfo = NULL; 1914 svn_revnum_t current; 1915 apr_array_header_t *histories; 1916 svn_boolean_t any_histories_left = TRUE; 1917 int send_count = 0; 1918 int i; 1919 1920 if (processed) 1921 { 1922 /* Casting away const. This only happens on recursive calls when 1923 it is known to be safe because we allocated paths. */ 1924 SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end, 1925 processed, pool)); 1926 } 1927 1928 if (!paths->nelts) 1929 return SVN_NO_ERROR; 1930 1931 if (processed) 1932 SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool)); 1933 1934 /* We have a list of paths and a revision range. But we don't care 1935 about all the revisions in the range -- only the ones in which 1936 one of our paths was changed. So let's go figure out which 1937 revisions contain real changes to at least one of our paths. */ 1938 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end, 1939 strict_node_history, ignore_missing_locations, 1940 authz_read_func, authz_read_baton, pool)); 1941 1942 /* Loop through all the revisions in the range and add any 1943 where a path was changed to the array, or if they wanted 1944 history in reverse order just send it to them right away. */ 1945 iterpool = svn_pool_create(pool); 1946 for (current = hist_end; 1947 any_histories_left; 1948 current = next_history_rev(histories)) 1949 { 1950 svn_boolean_t changed = FALSE; 1951 any_histories_left = FALSE; 1952 svn_pool_clear(iterpool); 1953 1954 for (i = 0; i < histories->nelts; i++) 1955 { 1956 struct path_info *info = APR_ARRAY_IDX(histories, i, 1957 struct path_info *); 1958 1959 /* Check history for this path in current rev. */ 1960 SVN_ERR(check_history(&changed, info, fs, current, 1961 strict_node_history, authz_read_func, 1962 authz_read_baton, hist_start, pool)); 1963 if (! info->done) 1964 any_histories_left = TRUE; 1965 } 1966 1967 /* If any of the paths changed in this rev then add or send it. */ 1968 if (changed) 1969 { 1970 svn_mergeinfo_t added_mergeinfo = NULL; 1971 svn_mergeinfo_t deleted_mergeinfo = NULL; 1972 svn_boolean_t has_children = FALSE; 1973 apr_hash_t *changes = NULL; 1974 1975 /* If we're including merged revisions, we need to calculate 1976 the mergeinfo deltas committed in this revision to our 1977 various paths. */ 1978 if (include_merged_revisions) 1979 { 1980 apr_array_header_t *cur_paths = 1981 apr_array_make(iterpool, paths->nelts, sizeof(const char *)); 1982 1983 /* Get the current paths of our history objects so we can 1984 query mergeinfo. */ 1985 /* ### TODO: Should this be ignoring depleted history items? */ 1986 for (i = 0; i < histories->nelts; i++) 1987 { 1988 struct path_info *info = APR_ARRAY_IDX(histories, i, 1989 struct path_info *); 1990 APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data; 1991 } 1992 SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo, 1993 &deleted_mergeinfo, 1994 &changes, 1995 fs, cur_paths, 1996 current, iterpool, 1997 iterpool)); 1998 has_children = (apr_hash_count(added_mergeinfo) > 0 1999 || apr_hash_count(deleted_mergeinfo) > 0); 2000 } 2001 2002 /* If our caller wants logs in descending order, we can send 2003 'em now (because that's the order we're crawling history 2004 in anyway). */ 2005 if (descending_order) 2006 { 2007 SVN_ERR(send_log(current, fs, changes, 2008 log_target_history_as_mergeinfo, nested_merges, 2009 discover_changed_paths, 2010 subtractive_merge, handling_merged_revisions, 2011 revprops, has_children, 2012 receiver, receiver_baton, 2013 authz_read_func, authz_read_baton, iterpool)); 2014 2015 if (has_children) /* Implies include_merged_revisions == TRUE */ 2016 { 2017 if (!nested_merges) 2018 { 2019 /* We're at the start of the recursion stack, create a 2020 single hash to be shared across all of the merged 2021 recursions so we can track and squelch duplicates. */ 2022 subpool = svn_pool_create(pool); 2023 nested_merges = svn_hash__make(subpool); 2024 processed = svn_hash__make(subpool); 2025 } 2026 2027 SVN_ERR(handle_merged_revisions( 2028 current, fs, 2029 log_target_history_as_mergeinfo, nested_merges, 2030 processed, 2031 added_mergeinfo, deleted_mergeinfo, 2032 discover_changed_paths, 2033 strict_node_history, 2034 revprops, 2035 receiver, receiver_baton, 2036 authz_read_func, 2037 authz_read_baton, 2038 iterpool)); 2039 } 2040 if (limit && ++send_count >= limit) 2041 break; 2042 } 2043 /* Otherwise, the caller wanted logs in ascending order, so 2044 we have to buffer up a list of revs and (if doing 2045 mergeinfo) a hash of related mergeinfo deltas, and 2046 process them later. */ 2047 else 2048 { 2049 if (! revs) 2050 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t)); 2051 APR_ARRAY_PUSH(revs, svn_revnum_t) = current; 2052 2053 if (added_mergeinfo || deleted_mergeinfo) 2054 { 2055 svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev)); 2056 struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2057 apr_palloc(pool, sizeof(*add_and_del_mergeinfo)); 2058 2059 if (added_mergeinfo) 2060 add_and_del_mergeinfo->added_mergeinfo = 2061 svn_mergeinfo_dup(added_mergeinfo, pool); 2062 2063 if (deleted_mergeinfo) 2064 add_and_del_mergeinfo->deleted_mergeinfo = 2065 svn_mergeinfo_dup(deleted_mergeinfo, pool); 2066 2067 *cur_rev = current; 2068 if (! rev_mergeinfo) 2069 rev_mergeinfo = svn_hash__make(pool); 2070 apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev), 2071 add_and_del_mergeinfo); 2072 } 2073 } 2074 } 2075 } 2076 svn_pool_destroy(iterpool); 2077 2078 if (subpool) 2079 { 2080 nested_merges = NULL; 2081 svn_pool_destroy(subpool); 2082 } 2083 2084 if (revs) 2085 { 2086 /* Work loop for processing the revisions we found since they wanted 2087 history in forward order. */ 2088 iterpool = svn_pool_create(pool); 2089 for (i = 0; i < revs->nelts; ++i) 2090 { 2091 svn_mergeinfo_t added_mergeinfo; 2092 svn_mergeinfo_t deleted_mergeinfo; 2093 svn_boolean_t has_children = FALSE; 2094 2095 svn_pool_clear(iterpool); 2096 current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t); 2097 2098 /* If we've got a hash of revision mergeinfo (which can only 2099 happen if INCLUDE_MERGED_REVISIONS was set), we check to 2100 see if this revision is one which merged in other 2101 revisions we need to handle recursively. */ 2102 if (rev_mergeinfo) 2103 { 2104 struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2105 apr_hash_get(rev_mergeinfo, ¤t, sizeof(svn_revnum_t)); 2106 added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo; 2107 deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo; 2108 has_children = (apr_hash_count(added_mergeinfo) > 0 2109 || apr_hash_count(deleted_mergeinfo) > 0); 2110 } 2111 2112 SVN_ERR(send_log(current, fs, NULL, 2113 log_target_history_as_mergeinfo, nested_merges, 2114 discover_changed_paths, subtractive_merge, 2115 handling_merged_revisions, revprops, has_children, 2116 receiver, receiver_baton, authz_read_func, 2117 authz_read_baton, iterpool)); 2118 if (has_children) 2119 { 2120 if (!nested_merges) 2121 { 2122 subpool = svn_pool_create(pool); 2123 nested_merges = svn_hash__make(subpool); 2124 } 2125 2126 SVN_ERR(handle_merged_revisions(current, fs, 2127 log_target_history_as_mergeinfo, 2128 nested_merges, 2129 processed, 2130 added_mergeinfo, 2131 deleted_mergeinfo, 2132 discover_changed_paths, 2133 strict_node_history, revprops, 2134 receiver, receiver_baton, 2135 authz_read_func, 2136 authz_read_baton, 2137 iterpool)); 2138 } 2139 if (limit && i + 1 >= limit) 2140 break; 2141 } 2142 svn_pool_destroy(iterpool); 2143 } 2144 2145 return SVN_NO_ERROR; 2146} 2147 2148struct location_segment_baton 2149{ 2150 apr_array_header_t *history_segments; 2151 apr_pool_t *pool; 2152}; 2153 2154/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */ 2155static svn_error_t * 2156location_segment_receiver(svn_location_segment_t *segment, 2157 void *baton, 2158 apr_pool_t *pool) 2159{ 2160 struct location_segment_baton *b = baton; 2161 2162 APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) = 2163 svn_location_segment_dup(segment, b->pool); 2164 2165 return SVN_NO_ERROR; 2166} 2167 2168 2169/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined 2170 history of each path in PATHS between START_REV and END_REV in REPOS's 2171 filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL 2172 is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all 2173 other (temporary) allocations. Other parameters are the same as 2174 svn_repos_get_logs4(). */ 2175static svn_error_t * 2176get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo, 2177 svn_repos_t *repos, 2178 const apr_array_header_t *paths, 2179 svn_revnum_t start_rev, 2180 svn_revnum_t end_rev, 2181 svn_repos_authz_func_t authz_read_func, 2182 void *authz_read_baton, 2183 apr_pool_t *result_pool, 2184 apr_pool_t *scratch_pool) 2185{ 2186 int i; 2187 svn_mergeinfo_t path_history_mergeinfo; 2188 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2189 2190 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev)); 2191 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev)); 2192 2193 /* Ensure START_REV is the youngest revision, as required by 2194 svn_repos_node_location_segments, for which this is an iterative 2195 wrapper. */ 2196 if (start_rev < end_rev) 2197 { 2198 svn_revnum_t tmp_rev = start_rev; 2199 start_rev = end_rev; 2200 end_rev = tmp_rev; 2201 } 2202 2203 *paths_history_mergeinfo = svn_hash__make(result_pool); 2204 2205 for (i = 0; i < paths->nelts; i++) 2206 { 2207 const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 2208 struct location_segment_baton loc_seg_baton; 2209 2210 svn_pool_clear(iterpool); 2211 loc_seg_baton.pool = scratch_pool; 2212 loc_seg_baton.history_segments = 2213 apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *)); 2214 2215 SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev, 2216 start_rev, end_rev, 2217 location_segment_receiver, 2218 &loc_seg_baton, 2219 authz_read_func, 2220 authz_read_baton, 2221 iterpool)); 2222 2223 SVN_ERR(svn_mergeinfo__mergeinfo_from_segments( 2224 &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool)); 2225 SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo, 2226 svn_mergeinfo_dup(path_history_mergeinfo, 2227 result_pool), 2228 result_pool, iterpool)); 2229 } 2230 svn_pool_destroy(iterpool); 2231 return SVN_NO_ERROR; 2232} 2233 2234svn_error_t * 2235svn_repos_get_logs4(svn_repos_t *repos, 2236 const apr_array_header_t *paths, 2237 svn_revnum_t start, 2238 svn_revnum_t end, 2239 int limit, 2240 svn_boolean_t discover_changed_paths, 2241 svn_boolean_t strict_node_history, 2242 svn_boolean_t include_merged_revisions, 2243 const apr_array_header_t *revprops, 2244 svn_repos_authz_func_t authz_read_func, 2245 void *authz_read_baton, 2246 svn_log_entry_receiver_t receiver, 2247 void *receiver_baton, 2248 apr_pool_t *pool) 2249{ 2250 svn_revnum_t head = SVN_INVALID_REVNUM; 2251 svn_fs_t *fs = repos->fs; 2252 svn_boolean_t descending_order; 2253 svn_mergeinfo_t paths_history_mergeinfo = NULL; 2254 2255 /* Setup log range. */ 2256 SVN_ERR(svn_fs_youngest_rev(&head, fs, pool)); 2257 2258 if (! SVN_IS_VALID_REVNUM(start)) 2259 start = head; 2260 2261 if (! SVN_IS_VALID_REVNUM(end)) 2262 end = head; 2263 2264 /* Check that revisions are sane before ever invoking receiver. */ 2265 if (start > head) 2266 return svn_error_createf 2267 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2268 _("No such revision %ld"), start); 2269 if (end > head) 2270 return svn_error_createf 2271 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2272 _("No such revision %ld"), end); 2273 2274 /* Ensure a youngest-to-oldest revision crawl ordering using our 2275 (possibly sanitized) range values. */ 2276 descending_order = start >= end; 2277 if (descending_order) 2278 { 2279 svn_revnum_t tmp_rev = start; 2280 start = end; 2281 end = tmp_rev; 2282 } 2283 2284 if (! paths) 2285 paths = apr_array_make(pool, 0, sizeof(const char *)); 2286 2287 /* If we're not including merged revisions, and we were given no 2288 paths or a single empty (or "/") path, then we can bypass a bunch 2289 of complexity because we already know in which revisions the root 2290 directory was changed -- all of them. */ 2291 if ((! include_merged_revisions) 2292 && ((! paths->nelts) 2293 || ((paths->nelts == 1) 2294 && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *)) 2295 || (strcmp(APR_ARRAY_IDX(paths, 0, const char *), 2296 "/") == 0))))) 2297 { 2298 apr_uint64_t send_count = 0; 2299 int i; 2300 apr_pool_t *iterpool = svn_pool_create(pool); 2301 2302 /* If we are provided an authz callback function, use it to 2303 verify that the user has read access to the root path in the 2304 first of our revisions. 2305 2306 ### FIXME: Strictly speaking, we should be checking this 2307 ### access in every revision along the line. But currently, 2308 ### there are no known authz implementations which concern 2309 ### themselves with per-revision access. */ 2310 if (authz_read_func) 2311 { 2312 svn_boolean_t readable; 2313 svn_fs_root_t *rev_root; 2314 2315 SVN_ERR(svn_fs_revision_root(&rev_root, fs, 2316 descending_order ? end : start, pool)); 2317 SVN_ERR(authz_read_func(&readable, rev_root, "", 2318 authz_read_baton, pool)); 2319 if (! readable) 2320 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 2321 } 2322 2323 send_count = end - start + 1; 2324 if (limit && send_count > limit) 2325 send_count = limit; 2326 for (i = 0; i < send_count; ++i) 2327 { 2328 svn_revnum_t rev; 2329 2330 svn_pool_clear(iterpool); 2331 2332 if (descending_order) 2333 rev = end - i; 2334 else 2335 rev = start + i; 2336 SVN_ERR(send_log(rev, fs, NULL, NULL, NULL, 2337 discover_changed_paths, FALSE, 2338 FALSE, revprops, FALSE, receiver, 2339 receiver_baton, authz_read_func, 2340 authz_read_baton, iterpool)); 2341 } 2342 svn_pool_destroy(iterpool); 2343 2344 return SVN_NO_ERROR; 2345 } 2346 2347 /* If we are including merged revisions, then create mergeinfo that 2348 represents all of PATHS' history between START and END. We will use 2349 this later to squelch duplicate log revisions that might exist in 2350 both natural history and merged-in history. See 2351 http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */ 2352 if (include_merged_revisions) 2353 { 2354 apr_pool_t *subpool = svn_pool_create(pool); 2355 2356 SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo, 2357 repos, paths, start, end, 2358 authz_read_func, 2359 authz_read_baton, 2360 pool, subpool)); 2361 svn_pool_destroy(subpool); 2362 } 2363 2364 return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end, 2365 limit, discover_changed_paths, strict_node_history, 2366 include_merged_revisions, FALSE, FALSE, FALSE, revprops, 2367 descending_order, receiver, receiver_baton, 2368 authz_read_func, authz_read_baton, pool); 2369} 2370