log.c revision 251886
1130803Smarcel/* log.c --- retrieving log messages 2130803Smarcel * 3130803Smarcel * ==================================================================== 4130803Smarcel * Licensed to the Apache Software Foundation (ASF) under one 5130803Smarcel * or more contributor license agreements. See the NOTICE file 6130803Smarcel * distributed with this work for additional information 7130803Smarcel * regarding copyright ownership. The ASF licenses this file 8130803Smarcel * to you under the Apache License, Version 2.0 (the 9130803Smarcel * "License"); you may not use this file except in compliance 10130803Smarcel * with the License. You may obtain a copy of the License at 11130803Smarcel * 12130803Smarcel * http://www.apache.org/licenses/LICENSE-2.0 13130803Smarcel * 14130803Smarcel * Unless required by applicable law or agreed to in writing, 15130803Smarcel * software distributed under the License is distributed on an 16130803Smarcel * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17130803Smarcel * KIND, either express or implied. See the License for the 18130803Smarcel * specific language governing permissions and limitations 19130803Smarcel * under the License. 20130803Smarcel * ==================================================================== 21130803Smarcel */ 22130803Smarcel 23130803Smarcel 24130803Smarcel#include <stdlib.h> 25130803Smarcel#define APR_WANT_STRFUNC 26130803Smarcel#include <apr_want.h> 27130803Smarcel 28130803Smarcel#include "svn_compat.h" 29130803Smarcel#include "svn_private_config.h" 30130803Smarcel#include "svn_hash.h" 31130803Smarcel#include "svn_pools.h" 32130803Smarcel#include "svn_error.h" 33130803Smarcel#include "svn_path.h" 34130803Smarcel#include "svn_fs.h" 35130803Smarcel#include "svn_repos.h" 36130803Smarcel#include "svn_string.h" 37130803Smarcel#include "svn_sorts.h" 38130803Smarcel#include "svn_props.h" 39130803Smarcel#include "svn_mergeinfo.h" 40130803Smarcel#include "repos.h" 41130803Smarcel#include "private/svn_fspath.h" 42130803Smarcel#include "private/svn_mergeinfo_private.h" 43130803Smarcel#include "private/svn_subr_private.h" 44130803Smarcel 45130803Smarcel 46130803Smarcel 47130803Smarcelsvn_error_t * 48130803Smarcelsvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level, 49130803Smarcel svn_repos_t *repos, 50130803Smarcel svn_revnum_t revision, 51130803Smarcel svn_repos_authz_func_t authz_read_func, 52130803Smarcel void *authz_read_baton, 53130803Smarcel apr_pool_t *pool) 54130803Smarcel{ 55130803Smarcel svn_fs_t *fs = svn_repos_fs(repos); 56130803Smarcel svn_fs_root_t *rev_root; 57130803Smarcel apr_hash_t *changes; 58130803Smarcel apr_hash_index_t *hi; 59130803Smarcel svn_boolean_t found_readable = FALSE; 60130803Smarcel svn_boolean_t found_unreadable = FALSE; 61130803Smarcel apr_pool_t *subpool; 62130803Smarcel 63130803Smarcel /* By default, we'll grant full read access to REVISION. */ 64130803Smarcel *access_level = svn_repos_revision_access_full; 65130803Smarcel 66130803Smarcel /* No auth-checking function? We're done. */ 67130803Smarcel if (! authz_read_func) 68130803Smarcel return SVN_NO_ERROR; 69130803Smarcel 70130803Smarcel /* Fetch the changes associated with REVISION. */ 71130803Smarcel SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool)); 72130803Smarcel SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool)); 73130803Smarcel 74130803Smarcel /* No changed paths? We're done. */ 75130803Smarcel if (apr_hash_count(changes) == 0) 76130803Smarcel return SVN_NO_ERROR; 77130803Smarcel 78130803Smarcel /* Otherwise, we have to check the readability of each changed 79130803Smarcel path, or at least enough to answer the question asked. */ 80130803Smarcel subpool = svn_pool_create(pool); 81130803Smarcel for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 82130803Smarcel { 83130803Smarcel const void *key; 84130803Smarcel void *val; 85130803Smarcel svn_fs_path_change2_t *change; 86130803Smarcel svn_boolean_t readable; 87130803Smarcel 88130803Smarcel svn_pool_clear(subpool); 89130803Smarcel apr_hash_this(hi, &key, NULL, &val); 90130803Smarcel change = val; 91130803Smarcel 92130803Smarcel SVN_ERR(authz_read_func(&readable, rev_root, key, 93130803Smarcel authz_read_baton, subpool)); 94130803Smarcel if (! readable) 95130803Smarcel found_unreadable = TRUE; 96130803Smarcel else 97130803Smarcel found_readable = TRUE; 98130803Smarcel 99130803Smarcel /* If we have at least one of each (readable/unreadable), we 100130803Smarcel have our answer. */ 101130803Smarcel if (found_readable && found_unreadable) 102130803Smarcel goto decision; 103130803Smarcel 104130803Smarcel switch (change->change_kind) 105130803Smarcel { 106130803Smarcel case svn_fs_path_change_add: 107130803Smarcel case svn_fs_path_change_replace: 108130803Smarcel { 109130803Smarcel const char *copyfrom_path; 110130803Smarcel svn_revnum_t copyfrom_rev; 111130803Smarcel 112130803Smarcel SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 113130803Smarcel rev_root, key, subpool)); 114130803Smarcel if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 115130803Smarcel { 116130803Smarcel svn_fs_root_t *copyfrom_root; 117130803Smarcel SVN_ERR(svn_fs_revision_root(©from_root, fs, 118130803Smarcel copyfrom_rev, subpool)); 119130803Smarcel SVN_ERR(authz_read_func(&readable, 120130803Smarcel copyfrom_root, copyfrom_path, 121130803Smarcel authz_read_baton, subpool)); 122130803Smarcel if (! readable) 123130803Smarcel found_unreadable = TRUE; 124130803Smarcel 125130803Smarcel /* If we have at least one of each (readable/unreadable), we 126130803Smarcel have our answer. */ 127130803Smarcel if (found_readable && found_unreadable) 128130803Smarcel goto decision; 129130803Smarcel } 130130803Smarcel } 131130803Smarcel break; 132130803Smarcel 133130803Smarcel case svn_fs_path_change_delete: 134130803Smarcel case svn_fs_path_change_modify: 135130803Smarcel default: 136130803Smarcel break; 137130803Smarcel } 138130803Smarcel } 139130803Smarcel 140130803Smarcel decision: 141130803Smarcel svn_pool_destroy(subpool); 142130803Smarcel 143130803Smarcel /* Either every changed path was unreadable... */ 144130803Smarcel if (! found_readable) 145130803Smarcel *access_level = svn_repos_revision_access_none; 146130803Smarcel 147130803Smarcel /* ... or some changed path was unreadable... */ 148130803Smarcel else if (found_unreadable) 149130803Smarcel *access_level = svn_repos_revision_access_partial; 150130803Smarcel 151130803Smarcel /* ... or every changed path was readable (the default). */ 152130803Smarcel return SVN_NO_ERROR; 153130803Smarcel} 154130803Smarcel 155130803Smarcel 156130803Smarcel/* Store as keys in CHANGED the paths of all node in ROOT that show a 157130803Smarcel * significant change. "Significant" means that the text or 158130803Smarcel * properties of the node were changed, or that the node was added or 159130803Smarcel * deleted. 160130803Smarcel * 161130803Smarcel * The CHANGED hash set and its keys and values are allocated in POOL; 162130803Smarcel * keys are const char * paths and values are svn_log_changed_path_t. 163130803Smarcel * 164130803Smarcel * To prevent changes from being processed over and over again, the 165130803Smarcel * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the 166130803Smarcel * latter is NULL, we will request the list inside this function. 167130803Smarcel * 168130803Smarcel * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 169130803Smarcel * AUTHZ_READ_BATON and FS) to check whether each changed-path (and 170130803Smarcel * copyfrom_path) is readable: 171130803Smarcel * 172130803Smarcel * - If some paths are readable and some are not, then silently 173130803Smarcel * omit the unreadable paths from the CHANGED hash, and return 174130803Smarcel * SVN_ERR_AUTHZ_PARTIALLY_READABLE. 175130803Smarcel * 176130803Smarcel * - If absolutely every changed-path (and copyfrom_path) is 177130803Smarcel * unreadable, then return an empty CHANGED hash and 178130803Smarcel * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision 179130803Smarcel * which truly has no changed paths from a revision in which all 180130803Smarcel * paths are unreadable.) 181130803Smarcel */ 182130803Smarcelstatic svn_error_t * 183130803Smarceldetect_changed(apr_hash_t **changed, 184130803Smarcel svn_fs_root_t *root, 185130803Smarcel svn_fs_t *fs, 186130803Smarcel apr_hash_t *prefetched_changes, 187130803Smarcel svn_repos_authz_func_t authz_read_func, 188130803Smarcel void *authz_read_baton, 189130803Smarcel apr_pool_t *pool) 190130803Smarcel{ 191130803Smarcel apr_hash_t *changes = prefetched_changes; 192130803Smarcel apr_hash_index_t *hi; 193130803Smarcel apr_pool_t *subpool; 194130803Smarcel svn_boolean_t found_readable = FALSE; 195130803Smarcel svn_boolean_t found_unreadable = FALSE; 196130803Smarcel 197130803Smarcel *changed = svn_hash__make(pool); 198130803Smarcel if (changes == NULL) 199130803Smarcel SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); 200130803Smarcel 201130803Smarcel if (apr_hash_count(changes) == 0) 202130803Smarcel /* No paths changed in this revision? Uh, sure, I guess the 203130803Smarcel revision is readable, then. */ 204130803Smarcel return SVN_NO_ERROR; 205130803Smarcel 206130803Smarcel subpool = svn_pool_create(pool); 207130803Smarcel 208130803Smarcel for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 209130803Smarcel { 210130803Smarcel /* NOTE: Much of this loop is going to look quite similar to 211130803Smarcel svn_repos_check_revision_access(), but we have to do more things 212130803Smarcel here, so we'll live with the duplication. */ 213130803Smarcel const void *key; 214130803Smarcel void *val; 215130803Smarcel svn_fs_path_change2_t *change; 216130803Smarcel const char *path; 217130803Smarcel char action; 218130803Smarcel svn_log_changed_path2_t *item; 219130803Smarcel 220130803Smarcel svn_pool_clear(subpool); 221130803Smarcel 222130803Smarcel /* KEY will be the path, VAL the change. */ 223130803Smarcel apr_hash_this(hi, &key, NULL, &val); 224130803Smarcel path = (const char *) key; 225130803Smarcel change = val; 226130803Smarcel 227130803Smarcel /* Skip path if unreadable. */ 228130803Smarcel if (authz_read_func) 229130803Smarcel { 230130803Smarcel svn_boolean_t readable; 231130803Smarcel SVN_ERR(authz_read_func(&readable, 232130803Smarcel root, path, 233130803Smarcel authz_read_baton, subpool)); 234130803Smarcel if (! readable) 235130803Smarcel { 236130803Smarcel found_unreadable = TRUE; 237130803Smarcel continue; 238130803Smarcel } 239130803Smarcel } 240130803Smarcel 241130803Smarcel /* At least one changed-path was readable. */ 242130803Smarcel found_readable = TRUE; 243130803Smarcel 244130803Smarcel switch (change->change_kind) 245130803Smarcel { 246130803Smarcel case svn_fs_path_change_reset: 247130803Smarcel continue; 248130803Smarcel 249130803Smarcel case svn_fs_path_change_add: 250130803Smarcel action = 'A'; 251130803Smarcel break; 252130803Smarcel 253130803Smarcel case svn_fs_path_change_replace: 254130803Smarcel action = 'R'; 255130803Smarcel break; 256130803Smarcel 257130803Smarcel case svn_fs_path_change_delete: 258130803Smarcel action = 'D'; 259130803Smarcel break; 260130803Smarcel 261130803Smarcel case svn_fs_path_change_modify: 262130803Smarcel default: 263130803Smarcel action = 'M'; 264130803Smarcel break; 265130803Smarcel } 266130803Smarcel 267130803Smarcel item = svn_log_changed_path2_create(pool); 268130803Smarcel item->action = action; 269130803Smarcel item->node_kind = change->node_kind; 270130803Smarcel item->copyfrom_rev = SVN_INVALID_REVNUM; 271130803Smarcel item->text_modified = change->text_mod ? svn_tristate_true 272130803Smarcel : svn_tristate_false; 273130803Smarcel item->props_modified = change->prop_mod ? svn_tristate_true 274130803Smarcel : svn_tristate_false; 275130803Smarcel 276130803Smarcel /* Pre-1.6 revision files don't store the change path kind, so fetch 277130803Smarcel it manually. */ 278130803Smarcel if (item->node_kind == svn_node_unknown) 279130803Smarcel { 280130803Smarcel svn_fs_root_t *check_root = root; 281130803Smarcel const char *check_path = path; 282130803Smarcel 283130803Smarcel /* Deleted items don't exist so check earlier revision. We 284130803Smarcel know the parent must exist and could be a copy */ 285130803Smarcel if (change->change_kind == svn_fs_path_change_delete) 286130803Smarcel { 287130803Smarcel svn_fs_history_t *history; 288130803Smarcel svn_revnum_t prev_rev; 289130803Smarcel const char *parent_path, *name; 290130803Smarcel 291130803Smarcel svn_fspath__split(&parent_path, &name, path, subpool); 292130803Smarcel 293130803Smarcel SVN_ERR(svn_fs_node_history(&history, root, parent_path, 294130803Smarcel subpool)); 295130803Smarcel 296130803Smarcel /* Two calls because the first call returns the original 297130803Smarcel revision as the deleted child means it is 'interesting' */ 298130803Smarcel SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 299130803Smarcel SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 300130803Smarcel 301130803Smarcel SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history, 302130803Smarcel subpool)); 303130803Smarcel SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool)); 304130803Smarcel check_path = svn_fspath__join(parent_path, name, subpool); 305130803Smarcel } 306130803Smarcel 307130803Smarcel SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path, 308130803Smarcel subpool)); 309130803Smarcel } 310130803Smarcel 311130803Smarcel 312130803Smarcel if ((action == 'A') || (action == 'R')) 313130803Smarcel { 314130803Smarcel const char *copyfrom_path = change->copyfrom_path; 315130803Smarcel svn_revnum_t copyfrom_rev = change->copyfrom_rev; 316130803Smarcel 317130803Smarcel /* the following is a potentially expensive operation since on FSFS 318130803Smarcel we will follow the DAG from ROOT to PATH and that requires 319130803Smarcel actually reading the directories along the way. */ 320130803Smarcel if (!change->copyfrom_known) 321130803Smarcel SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 322130803Smarcel root, path, subpool)); 323130803Smarcel 324130803Smarcel if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 325130803Smarcel { 326130803Smarcel svn_boolean_t readable = TRUE; 327130803Smarcel 328130803Smarcel if (authz_read_func) 329130803Smarcel { 330130803Smarcel svn_fs_root_t *copyfrom_root; 331130803Smarcel 332130803Smarcel SVN_ERR(svn_fs_revision_root(©from_root, fs, 333130803Smarcel copyfrom_rev, subpool)); 334130803Smarcel SVN_ERR(authz_read_func(&readable, 335130803Smarcel copyfrom_root, copyfrom_path, 336130803Smarcel authz_read_baton, subpool)); 337130803Smarcel if (! readable) 338130803Smarcel found_unreadable = TRUE; 339130803Smarcel } 340130803Smarcel 341130803Smarcel if (readable) 342130803Smarcel { 343130803Smarcel item->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 344130803Smarcel item->copyfrom_rev = copyfrom_rev; 345130803Smarcel } 346130803Smarcel } 347130803Smarcel } 348130803Smarcel svn_hash_sets(*changed, apr_pstrdup(pool, path), item); 349130803Smarcel } 350130803Smarcel 351130803Smarcel svn_pool_destroy(subpool); 352130803Smarcel 353130803Smarcel if (! found_readable) 354130803Smarcel /* Every changed-path was unreadable. */ 355130803Smarcel return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, 356130803Smarcel NULL, NULL); 357130803Smarcel 358130803Smarcel if (found_unreadable) 359130803Smarcel /* At least one changed-path was unreadable. */ 360130803Smarcel return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE, 361130803Smarcel NULL, NULL); 362130803Smarcel 363130803Smarcel /* Every changed-path was readable. */ 364130803Smarcel return SVN_NO_ERROR; 365130803Smarcel} 366130803Smarcel 367130803Smarcel/* This is used by svn_repos_get_logs to keep track of multiple 368130803Smarcel * path history information while working through history. 369130803Smarcel * 370130803Smarcel * The two pools are swapped after each iteration through history because 371130803Smarcel * to get the next history requires the previous one. 372130803Smarcel */ 373130803Smarcelstruct path_info 374130803Smarcel{ 375130803Smarcel svn_stringbuf_t *path; 376130803Smarcel svn_revnum_t history_rev; 377130803Smarcel svn_boolean_t done; 378130803Smarcel svn_boolean_t first_time; 379130803Smarcel 380130803Smarcel /* If possible, we like to keep open the history object for each path, 381130803Smarcel since it avoids needed to open and close it many times as we walk 382130803Smarcel backwards in time. To do so we need two pools, so that we can clear 383130803Smarcel one each time through. If we're not holding the history open for 384130803Smarcel this path then these three pointers will be NULL. */ 385130803Smarcel svn_fs_history_t *hist; 386130803Smarcel apr_pool_t *newpool; 387130803Smarcel apr_pool_t *oldpool; 388130803Smarcel}; 389130803Smarcel 390130803Smarcel/* Advance to the next history for the path. 391130803Smarcel * 392130803Smarcel * If INFO->HIST is not NULL we do this using that existing history object, 393130803Smarcel * otherwise we open a new one. 394130803Smarcel * 395130803Smarcel * If no more history is available or the history revision is less 396130803Smarcel * (earlier) than START, or the history is not available due 397130803Smarcel * to authorization, then INFO->DONE is set to TRUE. 398130803Smarcel * 399130803Smarcel * A STRICT value of FALSE will indicate to follow history across copied 400130803Smarcel * paths. 401130803Smarcel * 402130803Smarcel * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 403130803Smarcel * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if 404130803Smarcel * we do indeed find more history for the path. 405130803Smarcel */ 406130803Smarcelstatic svn_error_t * 407130803Smarcelget_history(struct path_info *info, 408130803Smarcel svn_fs_t *fs, 409130803Smarcel svn_boolean_t strict, 410130803Smarcel svn_repos_authz_func_t authz_read_func, 411130803Smarcel void *authz_read_baton, 412130803Smarcel svn_revnum_t start, 413130803Smarcel apr_pool_t *pool) 414130803Smarcel{ 415130803Smarcel svn_fs_root_t *history_root = NULL; 416130803Smarcel svn_fs_history_t *hist; 417130803Smarcel apr_pool_t *subpool; 418130803Smarcel const char *path; 419130803Smarcel 420130803Smarcel if (info->hist) 421130803Smarcel { 422130803Smarcel subpool = info->newpool; 423130803Smarcel 424130803Smarcel SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool)); 425130803Smarcel 426130803Smarcel hist = info->hist; 427130803Smarcel } 428130803Smarcel else 429130803Smarcel { 430130803Smarcel subpool = svn_pool_create(pool); 431130803Smarcel 432130803Smarcel /* Open the history located at the last rev we were at. */ 433130803Smarcel SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev, 434130803Smarcel subpool)); 435130803Smarcel 436130803Smarcel SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data, 437130803Smarcel subpool)); 438130803Smarcel 439130803Smarcel SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 440130803Smarcel 441130803Smarcel if (info->first_time) 442130803Smarcel info->first_time = FALSE; 443130803Smarcel else 444130803Smarcel SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 445130803Smarcel } 446130803Smarcel 447130803Smarcel if (! hist) 448130803Smarcel { 449130803Smarcel svn_pool_destroy(subpool); 450130803Smarcel if (info->oldpool) 451130803Smarcel svn_pool_destroy(info->oldpool); 452130803Smarcel info->done = TRUE; 453130803Smarcel return SVN_NO_ERROR; 454130803Smarcel } 455130803Smarcel 456130803Smarcel /* Fetch the location information for this history step. */ 457130803Smarcel SVN_ERR(svn_fs_history_location(&path, &info->history_rev, 458130803Smarcel hist, subpool)); 459130803Smarcel 460130803Smarcel svn_stringbuf_set(info->path, path); 461130803Smarcel 462130803Smarcel /* If this history item predates our START revision then 463130803Smarcel don't fetch any more for this path. */ 464130803Smarcel if (info->history_rev < start) 465130803Smarcel { 466130803Smarcel svn_pool_destroy(subpool); 467130803Smarcel if (info->oldpool) 468130803Smarcel svn_pool_destroy(info->oldpool); 469130803Smarcel info->done = TRUE; 470130803Smarcel return SVN_NO_ERROR; 471130803Smarcel } 472130803Smarcel 473130803Smarcel /* Is the history item readable? If not, done with path. */ 474130803Smarcel if (authz_read_func) 475130803Smarcel { 476130803Smarcel svn_boolean_t readable; 477130803Smarcel SVN_ERR(svn_fs_revision_root(&history_root, fs, 478130803Smarcel info->history_rev, 479130803Smarcel subpool)); 480130803Smarcel SVN_ERR(authz_read_func(&readable, history_root, 481130803Smarcel info->path->data, 482130803Smarcel authz_read_baton, 483130803Smarcel subpool)); 484130803Smarcel if (! readable) 485130803Smarcel info->done = TRUE; 486130803Smarcel } 487130803Smarcel 488130803Smarcel if (! info->hist) 489130803Smarcel { 490130803Smarcel svn_pool_destroy(subpool); 491130803Smarcel } 492130803Smarcel else 493130803Smarcel { 494130803Smarcel apr_pool_t *temppool = info->oldpool; 495130803Smarcel info->oldpool = info->newpool; 496130803Smarcel svn_pool_clear(temppool); 497130803Smarcel info->newpool = temppool; 498130803Smarcel } 499130803Smarcel 500130803Smarcel return SVN_NO_ERROR; 501130803Smarcel} 502130803Smarcel 503130803Smarcel/* Set INFO->HIST to the next history for the path *if* there is history 504130803Smarcel * available and INFO->HISTORY_REV is equal to or greater than CURRENT. 505130803Smarcel * 506130803Smarcel * *CHANGED is set to TRUE if the path has history in the CURRENT revision, 507130803Smarcel * otherwise it is not touched. 508130803Smarcel * 509130803Smarcel * If we do need to get the next history revision for the path, call 510130803Smarcel * get_history to do it -- see it for details. 511130803Smarcel */ 512130803Smarcelstatic svn_error_t * 513130803Smarcelcheck_history(svn_boolean_t *changed, 514130803Smarcel struct path_info *info, 515130803Smarcel svn_fs_t *fs, 516130803Smarcel svn_revnum_t current, 517130803Smarcel svn_boolean_t strict, 518130803Smarcel svn_repos_authz_func_t authz_read_func, 519130803Smarcel void *authz_read_baton, 520130803Smarcel svn_revnum_t start, 521130803Smarcel apr_pool_t *pool) 522130803Smarcel{ 523130803Smarcel /* If we're already done with histories for this path, 524130803Smarcel don't try to fetch any more. */ 525130803Smarcel if (info->done) 526130803Smarcel return SVN_NO_ERROR; 527130803Smarcel 528130803Smarcel /* If the last rev we got for this path is less than CURRENT, 529130803Smarcel then just return and don't fetch history for this path. 530130803Smarcel The caller will get to this rev eventually or else reach 531130803Smarcel the limit. */ 532130803Smarcel if (info->history_rev < current) 533130803Smarcel return SVN_NO_ERROR; 534130803Smarcel 535130803Smarcel /* If the last rev we got for this path is equal to CURRENT 536130803Smarcel then set *CHANGED to true and get the next history 537130803Smarcel rev where this path was changed. */ 538130803Smarcel *changed = TRUE; 539130803Smarcel return get_history(info, fs, strict, authz_read_func, 540130803Smarcel authz_read_baton, start, pool); 541130803Smarcel} 542130803Smarcel 543130803Smarcel/* Return the next interesting revision in our list of HISTORIES. */ 544130803Smarcelstatic svn_revnum_t 545130803Smarcelnext_history_rev(const apr_array_header_t *histories) 546130803Smarcel{ 547130803Smarcel svn_revnum_t next_rev = SVN_INVALID_REVNUM; 548130803Smarcel int i; 549130803Smarcel 550130803Smarcel for (i = 0; i < histories->nelts; ++i) 551130803Smarcel { 552130803Smarcel struct path_info *info = APR_ARRAY_IDX(histories, i, 553130803Smarcel struct path_info *); 554130803Smarcel if (info->done) 555130803Smarcel continue; 556130803Smarcel if (info->history_rev > next_rev) 557130803Smarcel next_rev = info->history_rev; 558130803Smarcel } 559130803Smarcel 560130803Smarcel return next_rev; 561130803Smarcel} 562130803Smarcel 563130803Smarcel/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to 564130803Smarcel catalogs describing how mergeinfo values on paths (which are the 565130803Smarcel keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES 566130803Smarcel already contains the changed paths for REV, use that. Otherwise, 567130803Smarcel request that data and return it in *PREFETCHED_CHANGES. */ 568130803Smarcel/* ### TODO: This would make a *great*, useful public function, 569130803Smarcel ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */ 570130803Smarcelstatic svn_error_t * 571130803Smarcelfs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog, 572130803Smarcel svn_mergeinfo_catalog_t *added_mergeinfo_catalog, 573130803Smarcel apr_hash_t **prefetched_changes, 574130803Smarcel svn_fs_t *fs, 575130803Smarcel svn_revnum_t rev, 576130803Smarcel apr_pool_t *result_pool, 577130803Smarcel apr_pool_t *scratch_pool) 578130803Smarcel 579130803Smarcel{ 580130803Smarcel svn_fs_root_t *root; 581130803Smarcel apr_pool_t *iterpool; 582130803Smarcel apr_hash_index_t *hi; 583130803Smarcel 584130803Smarcel /* Initialize return variables. */ 585130803Smarcel *deleted_mergeinfo_catalog = svn_hash__make(result_pool); 586130803Smarcel *added_mergeinfo_catalog = svn_hash__make(result_pool); 587130803Smarcel 588130803Smarcel /* Revision 0 has no mergeinfo and no mergeinfo changes. */ 589130803Smarcel if (rev == 0) 590130803Smarcel return SVN_NO_ERROR; 591130803Smarcel 592130803Smarcel /* We're going to use the changed-paths information for REV to 593130803Smarcel narrow down our search. */ 594130803Smarcel SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 595130803Smarcel if (*prefetched_changes == NULL) 596130803Smarcel SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool)); 597130803Smarcel 598130803Smarcel /* No changed paths? We're done. */ 599130803Smarcel if (apr_hash_count(*prefetched_changes) == 0) 600130803Smarcel return SVN_NO_ERROR; 601130803Smarcel 602130803Smarcel /* Loop over changes, looking for anything that might carry an 603130803Smarcel svn:mergeinfo change and is one of our paths of interest, or a 604130803Smarcel child or [grand]parent directory thereof. */ 605130803Smarcel iterpool = svn_pool_create(scratch_pool); 606130803Smarcel for (hi = apr_hash_first(scratch_pool, *prefetched_changes); 607130803Smarcel hi; 608130803Smarcel hi = apr_hash_next(hi)) 609130803Smarcel { 610130803Smarcel const void *key; 611130803Smarcel void *val; 612130803Smarcel svn_fs_path_change2_t *change; 613130803Smarcel const char *changed_path, *base_path = NULL; 614130803Smarcel svn_revnum_t base_rev = SVN_INVALID_REVNUM; 615130803Smarcel svn_fs_root_t *base_root = NULL; 616130803Smarcel svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value; 617130803Smarcel 618130803Smarcel svn_pool_clear(iterpool); 619130803Smarcel 620130803Smarcel /* KEY will be the path, VAL the change. */ 621130803Smarcel apr_hash_this(hi, &key, NULL, &val); 622130803Smarcel changed_path = key; 623130803Smarcel change = val; 624130803Smarcel 625130803Smarcel /* If there was no property change on this item, ignore it. */ 626130803Smarcel if (! change->prop_mod) 627130803Smarcel continue; 628130803Smarcel 629130803Smarcel switch (change->change_kind) 630130803Smarcel { 631130803Smarcel 632130803Smarcel /* ### TODO: Can the add, replace, and modify cases be joined 633130803Smarcel ### together to all use svn_repos__prev_location()? The 634130803Smarcel ### difference would be the fallback case (path/rev-1 for 635130803Smarcel ### modifies, NULL otherwise). -- cmpilato */ 636130803Smarcel 637130803Smarcel /* If the path was added or replaced, see if it was created via 638130803Smarcel copy. If so, that will tell us where its previous location 639130803Smarcel was. If not, there's no previous location to examine. */ 640130803Smarcel case svn_fs_path_change_add: 641130803Smarcel case svn_fs_path_change_replace: 642130803Smarcel { 643130803Smarcel const char *copyfrom_path; 644130803Smarcel svn_revnum_t copyfrom_rev; 645130803Smarcel 646130803Smarcel SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 647130803Smarcel root, changed_path, iterpool)); 648130803Smarcel if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 649130803Smarcel { 650130803Smarcel base_path = apr_pstrdup(scratch_pool, copyfrom_path); 651130803Smarcel base_rev = copyfrom_rev; 652130803Smarcel } 653130803Smarcel break; 654130803Smarcel } 655130803Smarcel 656130803Smarcel /* If the path was merely modified, see if its previous 657130803Smarcel location was affected by a copy which happened in this 658130803Smarcel revision before assuming it holds the same path it did the 659130803Smarcel previous revision. */ 660130803Smarcel case svn_fs_path_change_modify: 661130803Smarcel { 662130803Smarcel svn_revnum_t appeared_rev; 663130803Smarcel 664130803Smarcel SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path, 665130803Smarcel &base_rev, fs, rev, 666130803Smarcel changed_path, iterpool)); 667130803Smarcel 668130803Smarcel /* If this path isn't the result of a copy that occurred 669130803Smarcel in this revision, we can find the previous version of 670130803Smarcel it in REV - 1 at the same path. */ 671130803Smarcel if (! (base_path && SVN_IS_VALID_REVNUM(base_rev) 672130803Smarcel && (appeared_rev == rev))) 673130803Smarcel { 674130803Smarcel base_path = changed_path; 675130803Smarcel base_rev = rev - 1; 676130803Smarcel } 677130803Smarcel break; 678130803Smarcel } 679130803Smarcel 680130803Smarcel /* We don't care about any of the other cases. */ 681130803Smarcel case svn_fs_path_change_delete: 682130803Smarcel case svn_fs_path_change_reset: 683130803Smarcel default: 684130803Smarcel continue; 685130803Smarcel } 686130803Smarcel 687130803Smarcel /* If there was a base location, fetch its mergeinfo property value. */ 688130803Smarcel if (base_path && SVN_IS_VALID_REVNUM(base_rev)) 689130803Smarcel { 690130803Smarcel SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool)); 691130803Smarcel SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path, 692130803Smarcel SVN_PROP_MERGEINFO, iterpool)); 693130803Smarcel } 694130803Smarcel 695130803Smarcel /* Now fetch the current (as of REV) mergeinfo property value. */ 696130803Smarcel SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path, 697130803Smarcel SVN_PROP_MERGEINFO, iterpool)); 698130803Smarcel 699130803Smarcel /* No mergeinfo on either the new or previous location? Just 700130803Smarcel skip it. (If there *was* a change, it would have been in 701130803Smarcel inherited mergeinfo only, which should be picked up by the 702130803Smarcel iteration of this loop that finds the parent paths that 703130803Smarcel really got changed.) */ 704130803Smarcel if (! (mergeinfo_value || prev_mergeinfo_value)) 705130803Smarcel continue; 706130803Smarcel 707130803Smarcel /* If mergeinfo was explicitly added or removed on this path, we 708130803Smarcel need to check to see if that was a real semantic change of 709130803Smarcel meaning. So, fill in the "missing" mergeinfo value with the 710130803Smarcel inherited mergeinfo for that path/revision. */ 711130803Smarcel if (prev_mergeinfo_value && (! mergeinfo_value)) 712130803Smarcel { 713130803Smarcel apr_array_header_t *query_paths = 714130803Smarcel apr_array_make(iterpool, 1, sizeof(const char *)); 715130803Smarcel svn_mergeinfo_t tmp_mergeinfo; 716130803Smarcel svn_mergeinfo_catalog_t tmp_catalog; 717130803Smarcel 718130803Smarcel APR_ARRAY_PUSH(query_paths, const char *) = changed_path; 719130803Smarcel SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, 720130803Smarcel query_paths, svn_mergeinfo_inherited, 721130803Smarcel FALSE, TRUE, iterpool, iterpool)); 722130803Smarcel tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path); 723130803Smarcel if (tmp_mergeinfo) 724130803Smarcel SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value, 725130803Smarcel tmp_mergeinfo, 726130803Smarcel iterpool)); 727130803Smarcel } 728130803Smarcel else if (mergeinfo_value && (! prev_mergeinfo_value) 729130803Smarcel && base_path && SVN_IS_VALID_REVNUM(base_rev)) 730130803Smarcel { 731130803Smarcel apr_array_header_t *query_paths = 732130803Smarcel apr_array_make(iterpool, 1, sizeof(const char *)); 733130803Smarcel svn_mergeinfo_t tmp_mergeinfo; 734130803Smarcel svn_mergeinfo_catalog_t tmp_catalog; 735130803Smarcel 736130803Smarcel APR_ARRAY_PUSH(query_paths, const char *) = base_path; 737130803Smarcel SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root, 738130803Smarcel query_paths, svn_mergeinfo_inherited, 739130803Smarcel FALSE, TRUE, iterpool, iterpool)); 740130803Smarcel tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path); 741130803Smarcel if (tmp_mergeinfo) 742130803Smarcel SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value, 743130803Smarcel tmp_mergeinfo, 744130803Smarcel iterpool)); 745130803Smarcel } 746130803Smarcel 747130803Smarcel /* If the old and new mergeinfo differ in any way, store the 748130803Smarcel before and after mergeinfo values in our return hashes. */ 749130803Smarcel if ((prev_mergeinfo_value && (! mergeinfo_value)) 750130803Smarcel || ((! prev_mergeinfo_value) && mergeinfo_value) 751130803Smarcel || (prev_mergeinfo_value && mergeinfo_value 752130803Smarcel && (! svn_string_compare(mergeinfo_value, 753130803Smarcel prev_mergeinfo_value)))) 754130803Smarcel { 755130803Smarcel svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL; 756130803Smarcel svn_mergeinfo_t deleted, added; 757130803Smarcel const char *hash_path; 758130803Smarcel 759130803Smarcel if (mergeinfo_value) 760130803Smarcel SVN_ERR(svn_mergeinfo_parse(&mergeinfo, 761130803Smarcel mergeinfo_value->data, iterpool)); 762130803Smarcel if (prev_mergeinfo_value) 763130803Smarcel SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo, 764130803Smarcel prev_mergeinfo_value->data, iterpool)); 765130803Smarcel SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 766130803Smarcel mergeinfo, FALSE, result_pool, 767130803Smarcel iterpool)); 768130803Smarcel 769130803Smarcel /* Toss interesting stuff into our return catalogs. */ 770130803Smarcel hash_path = apr_pstrdup(result_pool, changed_path); 771130803Smarcel svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted); 772130803Smarcel svn_hash_sets(*added_mergeinfo_catalog, hash_path, added); 773130803Smarcel } 774130803Smarcel } 775130803Smarcel 776130803Smarcel svn_pool_destroy(iterpool); 777130803Smarcel return SVN_NO_ERROR; 778130803Smarcel} 779130803Smarcel 780130803Smarcel 781130803Smarcel/* Determine what (if any) mergeinfo for PATHS was modified in 782130803Smarcel revision REV, returning the differences for added mergeinfo in 783130803Smarcel *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. 784130803Smarcel If *PREFETCHED_CAHNGES already contains the changed paths for 785130803Smarcel REV, use that. Otherwise, request that data and return it in 786130803Smarcel *PREFETCHED_CHANGES. 787130803Smarcel Use POOL for all allocations. */ 788130803Smarcelstatic svn_error_t * 789130803Smarcelget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo, 790130803Smarcel svn_mergeinfo_t *deleted_mergeinfo, 791130803Smarcel apr_hash_t **prefetched_changes, 792130803Smarcel svn_fs_t *fs, 793130803Smarcel const apr_array_header_t *paths, 794130803Smarcel svn_revnum_t rev, 795130803Smarcel apr_pool_t *result_pool, 796130803Smarcel apr_pool_t *scratch_pool) 797130803Smarcel{ 798130803Smarcel svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog; 799130803Smarcel apr_hash_index_t *hi; 800130803Smarcel svn_fs_root_t *root; 801130803Smarcel apr_pool_t *iterpool; 802130803Smarcel int i; 803130803Smarcel svn_error_t *err; 804130803Smarcel 805130803Smarcel /* Initialize return value. */ 806130803Smarcel *added_mergeinfo = svn_hash__make(result_pool); 807130803Smarcel *deleted_mergeinfo = svn_hash__make(result_pool); 808130803Smarcel 809130803Smarcel /* If we're asking about revision 0, there's no mergeinfo to be found. */ 810130803Smarcel if (rev == 0) 811130803Smarcel return SVN_NO_ERROR; 812130803Smarcel 813130803Smarcel /* No paths? No mergeinfo. */ 814130803Smarcel if (! paths->nelts) 815130803Smarcel return SVN_NO_ERROR; 816130803Smarcel 817130803Smarcel /* Create a work subpool and get a root for REV. */ 818130803Smarcel SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 819130803Smarcel 820130803Smarcel /* Fetch the mergeinfo changes for REV. */ 821130803Smarcel err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog, 822130803Smarcel &added_mergeinfo_catalog, 823130803Smarcel prefetched_changes, 824130803Smarcel fs, rev, scratch_pool, scratch_pool); 825130803Smarcel if (err) 826130803Smarcel { 827130803Smarcel if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 828130803Smarcel { 829130803Smarcel /* Issue #3896: If invalid mergeinfo is encountered the 830130803Smarcel best we can do is ignore it and act as if there were 831130803Smarcel no mergeinfo modifications. */ 832130803Smarcel svn_error_clear(err); 833130803Smarcel return SVN_NO_ERROR; 834130803Smarcel } 835130803Smarcel else 836130803Smarcel { 837130803Smarcel return svn_error_trace(err); 838130803Smarcel } 839130803Smarcel } 840130803Smarcel 841130803Smarcel /* In most revisions, there will be no mergeinfo change at all. */ 842130803Smarcel if ( apr_hash_count(deleted_mergeinfo_catalog) == 0 843130803Smarcel && apr_hash_count(added_mergeinfo_catalog) == 0) 844130803Smarcel return SVN_NO_ERROR; 845130803Smarcel 846130803Smarcel /* Check our PATHS for any changes to their inherited mergeinfo. 847130803Smarcel (We deal with changes to mergeinfo directly *on* the paths in the 848130803Smarcel following loop.) */ 849130803Smarcel iterpool = svn_pool_create(scratch_pool); 850130803Smarcel for (i = 0; i < paths->nelts; i++) 851130803Smarcel { 852130803Smarcel const char *path = APR_ARRAY_IDX(paths, i, const char *); 853130803Smarcel const char *prev_path; 854130803Smarcel apr_ssize_t klen; 855130803Smarcel svn_revnum_t appeared_rev, prev_rev; 856130803Smarcel svn_fs_root_t *prev_root; 857130803Smarcel svn_mergeinfo_catalog_t catalog, inherited_catalog; 858130803Smarcel svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added, 859130803Smarcel prev_inherited_mergeinfo, inherited_mergeinfo; 860130803Smarcel apr_array_header_t *query_paths; 861130803Smarcel 862130803Smarcel svn_pool_clear(iterpool); 863130803Smarcel 864130803Smarcel /* If this path is represented in the changed-mergeinfo hashes, 865130803Smarcel we'll deal with it in the loop below. */ 866130803Smarcel if (svn_hash_gets(deleted_mergeinfo_catalog, path)) 867130803Smarcel continue; 868130803Smarcel 869130803Smarcel /* Figure out what path/rev to compare against. Ignore 870130803Smarcel not-found errors returned by the filesystem. */ 871130803Smarcel err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 872130803Smarcel fs, rev, path, iterpool); 873130803Smarcel if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 874130803Smarcel err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)) 875130803Smarcel { 876130803Smarcel svn_error_clear(err); 877130803Smarcel err = SVN_NO_ERROR; 878130803Smarcel continue; 879130803Smarcel } 880130803Smarcel SVN_ERR(err); 881130803Smarcel 882130803Smarcel /* If this path isn't the result of a copy that occurred in this 883130803Smarcel revision, we can find the previous version of it in REV - 1 884130803Smarcel at the same path. */ 885130803Smarcel if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev) 886130803Smarcel && (appeared_rev == rev))) 887130803Smarcel { 888130803Smarcel prev_path = path; 889130803Smarcel prev_rev = rev - 1; 890130803Smarcel } 891130803Smarcel 892130803Smarcel /* Fetch the previous mergeinfo (including inherited stuff) for 893130803Smarcel this path. Ignore not-found errors returned by the 894130803Smarcel filesystem or invalid mergeinfo (Issue #3896).*/ 895130803Smarcel SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool)); 896130803Smarcel query_paths = apr_array_make(iterpool, 1, sizeof(const char *)); 897130803Smarcel APR_ARRAY_PUSH(query_paths, const char *) = prev_path; 898130803Smarcel err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths, 899130803Smarcel svn_mergeinfo_inherited, FALSE, TRUE, 900130803Smarcel iterpool, iterpool); 901130803Smarcel if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 902130803Smarcel err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 903130803Smarcel err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 904130803Smarcel { 905130803Smarcel svn_error_clear(err); 906130803Smarcel err = SVN_NO_ERROR; 907130803Smarcel continue; 908130803Smarcel } 909130803Smarcel SVN_ERR(err); 910130803Smarcel 911130803Smarcel /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due 912130803Smarcel to move as a merge': A copy where the source and destination inherit 913130803Smarcel mergeinfo from the same parent means the inherited mergeinfo of the 914130803Smarcel source and destination will differ, but this diffrence is not 915130803Smarcel indicative of a merge unless the mergeinfo on the inherited parent 916130803Smarcel has actually changed. 917130803Smarcel 918130803Smarcel To check for this we must fetch the "raw" previous inherited 919130803Smarcel mergeinfo and the "raw" mergeinfo @REV then compare these. */ 920130803Smarcel SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths, 921130803Smarcel svn_mergeinfo_nearest_ancestor, FALSE, 922130803Smarcel FALSE, /* adjust_inherited_mergeinfo */ 923130803Smarcel iterpool, iterpool)); 924130803Smarcel 925130803Smarcel klen = strlen(prev_path); 926130803Smarcel prev_mergeinfo = apr_hash_get(catalog, prev_path, klen); 927130803Smarcel prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen); 928130803Smarcel 929130803Smarcel /* Fetch the current mergeinfo (as of REV, and including 930130803Smarcel inherited stuff) for this path. */ 931130803Smarcel APR_ARRAY_IDX(query_paths, 0, const char *) = path; 932130803Smarcel SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths, 933130803Smarcel svn_mergeinfo_inherited, FALSE, TRUE, 934130803Smarcel iterpool, iterpool)); 935130803Smarcel 936130803Smarcel /* Issue #4022 again, fetch the raw inherited mergeinfo. */ 937130803Smarcel SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths, 938130803Smarcel svn_mergeinfo_nearest_ancestor, FALSE, 939130803Smarcel FALSE, /* adjust_inherited_mergeinfo */ 940130803Smarcel iterpool, iterpool)); 941130803Smarcel 942130803Smarcel klen = strlen(path); 943130803Smarcel mergeinfo = apr_hash_get(catalog, path, klen); 944130803Smarcel inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen); 945130803Smarcel 946130803Smarcel if (!prev_mergeinfo && !mergeinfo) 947130803Smarcel continue; 948130803Smarcel 949130803Smarcel /* Last bit of issue #4022 checking. */ 950130803Smarcel if (prev_inherited_mergeinfo && inherited_mergeinfo) 951130803Smarcel { 952130803Smarcel svn_boolean_t inherits_same_mergeinfo; 953130803Smarcel 954130803Smarcel SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo, 955130803Smarcel prev_inherited_mergeinfo, 956130803Smarcel inherited_mergeinfo, 957130803Smarcel TRUE, iterpool)); 958130803Smarcel /* If a copy rather than an actual merge brought about an 959130803Smarcel inherited mergeinfo change then we are finished. */ 960130803Smarcel if (inherits_same_mergeinfo) 961130803Smarcel continue; 962130803Smarcel } 963130803Smarcel else 964130803Smarcel { 965130803Smarcel svn_boolean_t same_mergeinfo; 966130803Smarcel SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo, 967130803Smarcel prev_inherited_mergeinfo, 968130803Smarcel FALSE, 969130803Smarcel TRUE, iterpool)); 970130803Smarcel if (same_mergeinfo) 971130803Smarcel continue; 972130803Smarcel } 973130803Smarcel 974130803Smarcel /* Compare, constrast, and combine the results. */ 975130803Smarcel SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 976130803Smarcel mergeinfo, FALSE, result_pool, iterpool)); 977130803Smarcel SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted, 978130803Smarcel result_pool, iterpool)); 979130803Smarcel SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added, 980130803Smarcel result_pool, iterpool)); 981130803Smarcel } 982130803Smarcel 983130803Smarcel /* Merge all the mergeinfos which are, or are children of, one of 984130803Smarcel our paths of interest into one giant delta mergeinfo. */ 985130803Smarcel for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog); 986130803Smarcel hi; hi = apr_hash_next(hi)) 987130803Smarcel { 988130803Smarcel const void *key; 989130803Smarcel apr_ssize_t klen; 990130803Smarcel void *val; 991130803Smarcel const char *changed_path; 992130803Smarcel svn_mergeinfo_t added, deleted; 993130803Smarcel 994130803Smarcel /* The path is the key, the mergeinfo delta is the value. */ 995130803Smarcel apr_hash_this(hi, &key, &klen, &val); 996130803Smarcel changed_path = key; 997130803Smarcel added = val; 998130803Smarcel 999130803Smarcel for (i = 0; i < paths->nelts; i++) 1000130803Smarcel { 1001130803Smarcel const char *path = APR_ARRAY_IDX(paths, i, const char *); 1002130803Smarcel if (! svn_fspath__skip_ancestor(path, changed_path)) 1003130803Smarcel continue; 1004130803Smarcel svn_pool_clear(iterpool); 1005130803Smarcel deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen); 1006130803Smarcel SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, 1007130803Smarcel svn_mergeinfo_dup(deleted, result_pool), 1008130803Smarcel result_pool, iterpool)); 1009130803Smarcel SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, 1010130803Smarcel svn_mergeinfo_dup(added, result_pool), 1011130803Smarcel result_pool, iterpool)); 1012130803Smarcel 1013130803Smarcel break; 1014130803Smarcel } 1015130803Smarcel } 1016130803Smarcel 1017130803Smarcel svn_pool_destroy(iterpool); 1018130803Smarcel return SVN_NO_ERROR; 1019130803Smarcel} 1020130803Smarcel 1021130803Smarcel 1022130803Smarcel/* Fill LOG_ENTRY with history information in FS at REV. */ 1023130803Smarcelstatic svn_error_t * 1024130803Smarcelfill_log_entry(svn_log_entry_t *log_entry, 1025130803Smarcel svn_revnum_t rev, 1026130803Smarcel svn_fs_t *fs, 1027130803Smarcel apr_hash_t *prefetched_changes, 1028130803Smarcel svn_boolean_t discover_changed_paths, 1029130803Smarcel const apr_array_header_t *revprops, 1030130803Smarcel svn_repos_authz_func_t authz_read_func, 1031130803Smarcel void *authz_read_baton, 1032130803Smarcel apr_pool_t *pool) 1033130803Smarcel{ 1034130803Smarcel apr_hash_t *r_props, *changed_paths = NULL; 1035130803Smarcel svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE; 1036130803Smarcel 1037130803Smarcel /* Discover changed paths if the user requested them 1038130803Smarcel or if we need to check that they are readable. */ 1039130803Smarcel if ((rev > 0) 1040130803Smarcel && (authz_read_func || discover_changed_paths)) 1041130803Smarcel { 1042130803Smarcel svn_fs_root_t *newroot; 1043130803Smarcel svn_error_t *patherr; 1044130803Smarcel 1045130803Smarcel SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool)); 1046130803Smarcel patherr = detect_changed(&changed_paths, 1047130803Smarcel newroot, fs, prefetched_changes, 1048130803Smarcel authz_read_func, authz_read_baton, 1049130803Smarcel pool); 1050130803Smarcel 1051130803Smarcel if (patherr 1052130803Smarcel && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE) 1053130803Smarcel { 1054130803Smarcel /* All changed-paths are unreadable, so clear all fields. */ 1055130803Smarcel svn_error_clear(patherr); 1056130803Smarcel changed_paths = NULL; 1057130803Smarcel get_revprops = FALSE; 1058130803Smarcel } 1059130803Smarcel else if (patherr 1060130803Smarcel && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE) 1061130803Smarcel { 1062130803Smarcel /* At least one changed-path was unreadable, so censor all 1063130803Smarcel but author and date. (The unreadable paths are already 1064130803Smarcel missing from the hash.) */ 1065130803Smarcel svn_error_clear(patherr); 1066130803Smarcel censor_revprops = TRUE; 1067130803Smarcel } 1068130803Smarcel else if (patherr) 1069130803Smarcel return patherr; 1070130803Smarcel 1071130803Smarcel /* It may be the case that an authz func was passed in, but 1072130803Smarcel the user still doesn't want to see any changed-paths. */ 1073130803Smarcel if (! discover_changed_paths) 1074130803Smarcel changed_paths = NULL; 1075130803Smarcel } 1076130803Smarcel 1077130803Smarcel if (get_revprops) 1078130803Smarcel { 1079130803Smarcel /* User is allowed to see at least some revprops. */ 1080130803Smarcel SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool)); 1081130803Smarcel if (revprops == NULL) 1082130803Smarcel { 1083130803Smarcel /* Requested all revprops... */ 1084130803Smarcel if (censor_revprops) 1085130803Smarcel { 1086130803Smarcel /* ... but we can only return author/date. */ 1087130803Smarcel log_entry->revprops = svn_hash__make(pool); 1088130803Smarcel svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, 1089130803Smarcel svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR)); 1090130803Smarcel svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, 1091130803Smarcel svn_hash_gets(r_props, SVN_PROP_REVISION_DATE)); 1092130803Smarcel } 1093130803Smarcel else 1094130803Smarcel /* ... so return all we got. */ 1095130803Smarcel log_entry->revprops = r_props; 1096130803Smarcel } 1097130803Smarcel else 1098130803Smarcel { 1099130803Smarcel /* Requested only some revprops... */ 1100130803Smarcel int i; 1101130803Smarcel for (i = 0; i < revprops->nelts; i++) 1102130803Smarcel { 1103130803Smarcel char *name = APR_ARRAY_IDX(revprops, i, char *); 1104130803Smarcel svn_string_t *value = svn_hash_gets(r_props, name); 1105130803Smarcel if (censor_revprops 1106130803Smarcel && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0 1107130803Smarcel || strcmp(name, SVN_PROP_REVISION_DATE) == 0)) 1108130803Smarcel /* ... but we can only return author/date. */ 1109130803Smarcel continue; 1110130803Smarcel if (log_entry->revprops == NULL) 1111130803Smarcel log_entry->revprops = svn_hash__make(pool); 1112130803Smarcel svn_hash_sets(log_entry->revprops, name, value); 1113130803Smarcel } 1114130803Smarcel } 1115130803Smarcel } 1116130803Smarcel 1117130803Smarcel log_entry->changed_paths = changed_paths; 1118130803Smarcel log_entry->changed_paths2 = changed_paths; 1119130803Smarcel log_entry->revision = rev; 1120130803Smarcel 1121130803Smarcel return SVN_NO_ERROR; 1122130803Smarcel} 1123130803Smarcel 1124130803Smarcel/* Send a log message for REV to RECEIVER with its RECEIVER_BATON. 1125130803Smarcel 1126130803Smarcel FS is used with REV to fetch the interesting history information, 1127130803Smarcel such as changed paths, revprops, etc. 1128130803Smarcel 1129130803Smarcel The detect_changed function is used if either AUTHZ_READ_FUNC is 1130130803Smarcel not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details. 1131130803Smarcel 1132130803Smarcel If DESCENDING_ORDER is true, send child messages in descending order. 1133130803Smarcel 1134130803Smarcel If REVPROPS is NULL, retrieve all revision properties; else, retrieve 1135130803Smarcel only the revision properties named by the (const char *) array elements 1136130803Smarcel (i.e. retrieve none if the array is empty). 1137130803Smarcel 1138130803Smarcel LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and 1139130803Smarcel NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If 1140130803Smarcel HANDLING_MERGED_REVISION is true and *all* changed paths within REV are 1141130803Smarcel already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send 1142130803Smarcel the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was 1143130803Smarcel reverse merged. 1144130803Smarcel 1145130803Smarcel If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise 1146130803Smarcel if NESTED_MERGES is not NULL and REV is contained in it, then don't send 1147130803Smarcel the log for REV, otherwise send it normally and add REV to 1148130803Smarcel NESTED_MERGES. */ 1149130803Smarcelstatic svn_error_t * 1150130803Smarcelsend_log(svn_revnum_t rev, 1151130803Smarcel svn_fs_t *fs, 1152130803Smarcel apr_hash_t *prefetched_changes, 1153130803Smarcel svn_mergeinfo_t log_target_history_as_mergeinfo, 1154130803Smarcel apr_hash_t *nested_merges, 1155130803Smarcel svn_boolean_t discover_changed_paths, 1156130803Smarcel svn_boolean_t subtractive_merge, 1157130803Smarcel svn_boolean_t handling_merged_revision, 1158130803Smarcel const apr_array_header_t *revprops, 1159130803Smarcel svn_boolean_t has_children, 1160130803Smarcel svn_log_entry_receiver_t receiver, 1161130803Smarcel void *receiver_baton, 1162130803Smarcel svn_repos_authz_func_t authz_read_func, 1163130803Smarcel void *authz_read_baton, 1164130803Smarcel apr_pool_t *pool) 1165130803Smarcel{ 1166130803Smarcel svn_log_entry_t *log_entry; 1167130803Smarcel /* Assume we want to send the log for REV. */ 1168130803Smarcel svn_boolean_t found_rev_of_interest = TRUE; 1169130803Smarcel 1170130803Smarcel log_entry = svn_log_entry_create(pool); 1171130803Smarcel SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes, 1172130803Smarcel discover_changed_paths || handling_merged_revision, 1173130803Smarcel revprops, authz_read_func, authz_read_baton, 1174130803Smarcel pool)); 1175130803Smarcel log_entry->has_children = has_children; 1176130803Smarcel log_entry->subtractive_merge = subtractive_merge; 1177130803Smarcel 1178130803Smarcel /* Is REV a merged revision that is already part of 1179130803Smarcel LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no 1180130803Smarcel need to send it, since it already was (or will be) sent. */ 1181130803Smarcel if (handling_merged_revision 1182130803Smarcel && log_entry->changed_paths2 1183130803Smarcel && log_target_history_as_mergeinfo 1184130803Smarcel && apr_hash_count(log_target_history_as_mergeinfo)) 1185130803Smarcel { 1186130803Smarcel apr_hash_index_t *hi; 1187130803Smarcel apr_pool_t *subpool = svn_pool_create(pool); 1188130803Smarcel 1189130803Smarcel /* REV was merged in, but it might already be part of the log target's 1190130803Smarcel natural history, so change our starting assumption. */ 1191130803Smarcel found_rev_of_interest = FALSE; 1192130803Smarcel 1193130803Smarcel /* Look at each changed path in REV. */ 1194130803Smarcel for (hi = apr_hash_first(subpool, log_entry->changed_paths2); 1195130803Smarcel hi; 1196130803Smarcel hi = apr_hash_next(hi)) 1197130803Smarcel { 1198130803Smarcel svn_boolean_t path_is_in_history = FALSE; 1199130803Smarcel const char *changed_path = svn__apr_hash_index_key(hi); 1200130803Smarcel apr_hash_index_t *hi2; 1201130803Smarcel apr_pool_t *inner_subpool = svn_pool_create(subpool); 1202130803Smarcel 1203130803Smarcel /* Look at each path on the log target's mergeinfo. */ 1204130803Smarcel for (hi2 = apr_hash_first(inner_subpool, 1205130803Smarcel log_target_history_as_mergeinfo); 1206130803Smarcel hi2; 1207130803Smarcel hi2 = apr_hash_next(hi2)) 1208130803Smarcel { 1209130803Smarcel const char *mergeinfo_path = 1210130803Smarcel svn__apr_hash_index_key(hi2); 1211130803Smarcel svn_rangelist_t *rangelist = 1212130803Smarcel svn__apr_hash_index_val(hi2); 1213130803Smarcel 1214130803Smarcel /* Check whether CHANGED_PATH at revision REV is a child of 1215130803Smarcel a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */ 1216130803Smarcel if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path)) 1217130803Smarcel { 1218130803Smarcel int i; 1219130803Smarcel 1220130803Smarcel for (i = 0; i < rangelist->nelts; i++) 1221130803Smarcel { 1222130803Smarcel svn_merge_range_t *range = 1223130803Smarcel APR_ARRAY_IDX(rangelist, i, 1224130803Smarcel svn_merge_range_t *); 1225130803Smarcel if (rev > range->start && rev <= range->end) 1226130803Smarcel { 1227130803Smarcel path_is_in_history = TRUE; 1228130803Smarcel break; 1229130803Smarcel } 1230130803Smarcel } 1231130803Smarcel } 1232130803Smarcel if (path_is_in_history) 1233130803Smarcel break; 1234130803Smarcel } 1235130803Smarcel svn_pool_destroy(inner_subpool); 1236130803Smarcel 1237130803Smarcel if (!path_is_in_history) 1238130803Smarcel { 1239130803Smarcel /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of 1240130803Smarcel LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the 1241130803Smarcel log for REV. */ 1242130803Smarcel found_rev_of_interest = TRUE; 1243130803Smarcel break; 1244130803Smarcel } 1245130803Smarcel } 1246130803Smarcel svn_pool_destroy(subpool); 1247130803Smarcel } 1248130803Smarcel 1249130803Smarcel /* If we only got changed paths the sake of detecting redundant merged 1250130803Smarcel revisions, then be sure we don't send that info to the receiver. */ 1251130803Smarcel if (!discover_changed_paths && handling_merged_revision) 1252130803Smarcel log_entry->changed_paths = log_entry->changed_paths2 = NULL; 1253130803Smarcel 1254130803Smarcel /* Send the entry to the receiver, unless it is a redundant merged 1255130803Smarcel revision. */ 1256130803Smarcel if (found_rev_of_interest) 1257130803Smarcel { 1258130803Smarcel /* Is REV a merged revision we've already sent? */ 1259130803Smarcel if (nested_merges && handling_merged_revision) 1260130803Smarcel { 1261130803Smarcel svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev, 1262130803Smarcel sizeof(svn_revnum_t *)); 1263130803Smarcel 1264130803Smarcel if (merged_rev) 1265130803Smarcel { 1266130803Smarcel /* We already sent REV. */ 1267130803Smarcel return SVN_NO_ERROR; 1268130803Smarcel } 1269130803Smarcel else 1270130803Smarcel { 1271130803Smarcel /* NESTED_REVS needs to last across all the send_log, do_logs, 1272130803Smarcel handle_merged_revisions() recursions, so use the pool it 1273130803Smarcel was created in at the top of the recursion. */ 1274130803Smarcel apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges); 1275130803Smarcel svn_revnum_t *long_lived_rev = apr_palloc(hash_pool, 1276130803Smarcel sizeof(svn_revnum_t)); 1277130803Smarcel *long_lived_rev = rev; 1278130803Smarcel apr_hash_set(nested_merges, long_lived_rev, 1279130803Smarcel sizeof(svn_revnum_t *), long_lived_rev); 1280130803Smarcel } 1281130803Smarcel } 1282130803Smarcel 1283130803Smarcel return (*receiver)(receiver_baton, log_entry, pool); 1284130803Smarcel } 1285130803Smarcel else 1286130803Smarcel { 1287130803Smarcel return SVN_NO_ERROR; 1288130803Smarcel } 1289130803Smarcel} 1290130803Smarcel 1291130803Smarcel/* This controls how many history objects we keep open. For any targets 1292130803Smarcel over this number we have to open and close their histories as needed, 1293130803Smarcel which is CPU intensive, but keeps us from using an unbounded amount of 1294130803Smarcel memory. */ 1295130803Smarcel#define MAX_OPEN_HISTORIES 32 1296130803Smarcel 1297130803Smarcel/* Get the histories for PATHS, and store them in *HISTORIES. 1298130803Smarcel 1299130803Smarcel If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1300130803Smarcel repository locations as fatal -- just ignore them. */ 1301130803Smarcelstatic svn_error_t * 1302130803Smarcelget_path_histories(apr_array_header_t **histories, 1303130803Smarcel svn_fs_t *fs, 1304130803Smarcel const apr_array_header_t *paths, 1305130803Smarcel svn_revnum_t hist_start, 1306130803Smarcel svn_revnum_t hist_end, 1307130803Smarcel svn_boolean_t strict_node_history, 1308130803Smarcel svn_boolean_t ignore_missing_locations, 1309130803Smarcel svn_repos_authz_func_t authz_read_func, 1310130803Smarcel void *authz_read_baton, 1311130803Smarcel apr_pool_t *pool) 1312130803Smarcel{ 1313130803Smarcel svn_fs_root_t *root; 1314130803Smarcel apr_pool_t *iterpool; 1315130803Smarcel svn_error_t *err; 1316130803Smarcel int i; 1317130803Smarcel 1318130803Smarcel /* Create a history object for each path so we can walk through 1319130803Smarcel them all at the same time until we have all changes or LIMIT 1320130803Smarcel is reached. 1321130803Smarcel 1322130803Smarcel There is some pool fun going on due to the fact that we have 1323130803Smarcel to hold on to the old pool with the history before we can 1324130803Smarcel get the next history. 1325130803Smarcel */ 1326130803Smarcel *histories = apr_array_make(pool, paths->nelts, 1327130803Smarcel sizeof(struct path_info *)); 1328130803Smarcel 1329130803Smarcel SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool)); 1330130803Smarcel 1331130803Smarcel iterpool = svn_pool_create(pool); 1332130803Smarcel for (i = 0; i < paths->nelts; i++) 1333130803Smarcel { 1334130803Smarcel const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 1335130803Smarcel struct path_info *info = apr_palloc(pool, 1336130803Smarcel sizeof(struct path_info)); 1337130803Smarcel 1338130803Smarcel if (authz_read_func) 1339130803Smarcel { 1340130803Smarcel svn_boolean_t readable; 1341130803Smarcel 1342130803Smarcel svn_pool_clear(iterpool); 1343130803Smarcel 1344130803Smarcel SVN_ERR(authz_read_func(&readable, root, this_path, 1345130803Smarcel authz_read_baton, iterpool)); 1346130803Smarcel if (! readable) 1347130803Smarcel return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 1348130803Smarcel } 1349130803Smarcel 1350130803Smarcel info->path = svn_stringbuf_create(this_path, pool); 1351130803Smarcel info->done = FALSE; 1352130803Smarcel info->history_rev = hist_end; 1353130803Smarcel info->first_time = TRUE; 1354130803Smarcel 1355130803Smarcel if (i < MAX_OPEN_HISTORIES) 1356130803Smarcel { 1357130803Smarcel err = svn_fs_node_history(&info->hist, root, this_path, pool); 1358130803Smarcel if (err 1359130803Smarcel && ignore_missing_locations 1360130803Smarcel && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1361130803Smarcel err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1362130803Smarcel err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1363130803Smarcel { 1364130803Smarcel svn_error_clear(err); 1365130803Smarcel continue; 1366130803Smarcel } 1367130803Smarcel SVN_ERR(err); 1368130803Smarcel info->newpool = svn_pool_create(pool); 1369130803Smarcel info->oldpool = svn_pool_create(pool); 1370130803Smarcel } 1371130803Smarcel else 1372130803Smarcel { 1373130803Smarcel info->hist = NULL; 1374130803Smarcel info->oldpool = NULL; 1375130803Smarcel info->newpool = NULL; 1376130803Smarcel } 1377130803Smarcel 1378130803Smarcel err = get_history(info, fs, 1379130803Smarcel strict_node_history, 1380130803Smarcel authz_read_func, authz_read_baton, 1381130803Smarcel hist_start, pool); 1382130803Smarcel if (err 1383130803Smarcel && ignore_missing_locations 1384130803Smarcel && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1385130803Smarcel err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1386130803Smarcel err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1387130803Smarcel { 1388130803Smarcel svn_error_clear(err); 1389130803Smarcel continue; 1390130803Smarcel } 1391130803Smarcel SVN_ERR(err); 1392130803Smarcel APR_ARRAY_PUSH(*histories, struct path_info *) = info; 1393130803Smarcel } 1394130803Smarcel svn_pool_destroy(iterpool); 1395130803Smarcel 1396130803Smarcel return SVN_NO_ERROR; 1397130803Smarcel} 1398130803Smarcel 1399130803Smarcel/* Remove and return the first item from ARR. */ 1400130803Smarcelstatic void * 1401130803Smarcelarray_pop_front(apr_array_header_t *arr) 1402130803Smarcel{ 1403130803Smarcel void *item = arr->elts; 1404130803Smarcel 1405130803Smarcel if (apr_is_empty_array(arr)) 1406130803Smarcel return NULL; 1407130803Smarcel 1408130803Smarcel arr->elts += arr->elt_size; 1409130803Smarcel arr->nelts -= 1; 1410130803Smarcel arr->nalloc -= 1; 1411130803Smarcel return item; 1412130803Smarcel} 1413130803Smarcel 1414130803Smarcel/* A struct which represents a single revision range, and the paths which 1415130803Smarcel have mergeinfo in that range. */ 1416130803Smarcelstruct path_list_range 1417130803Smarcel{ 1418130803Smarcel apr_array_header_t *paths; 1419130803Smarcel svn_merge_range_t range; 1420130803Smarcel 1421130803Smarcel /* Is RANGE the result of a reverse merge? */ 1422130803Smarcel svn_boolean_t reverse_merge; 1423130803Smarcel}; 1424130803Smarcel 1425130803Smarcel/* A struct which represents "inverse mergeinfo", that is, instead of having 1426130803Smarcel a path->revision_range_list mapping, which is the way mergeinfo is commonly 1427130803Smarcel represented, this struct enables a revision_range_list,path tuple, where 1428130803Smarcel the paths can be accessed by revision. */ 1429130803Smarcelstruct rangelist_path 1430130803Smarcel{ 1431130803Smarcel svn_rangelist_t *rangelist; 1432130803Smarcel const char *path; 1433130803Smarcel}; 1434130803Smarcel 1435130803Smarcel/* Comparator function for combine_mergeinfo_path_lists(). Sorts 1436130803Smarcel rangelist_path structs in increasing order based upon starting revision, 1437130803Smarcel then ending revision of the first element in the rangelist. 1438130803Smarcel 1439130803Smarcel This does not sort rangelists based upon subsequent elements, only the 1440130803Smarcel first range. We'll sort any subsequent ranges in the correct order 1441130803Smarcel when they get bumped up to the front by removal of earlier ones, so we 1442130803Smarcel don't really have to sort them here. See combine_mergeinfo_path_lists() 1443130803Smarcel for details. */ 1444130803Smarcelstatic int 1445130803Smarcelcompare_rangelist_paths(const void *a, const void *b) 1446130803Smarcel{ 1447130803Smarcel struct rangelist_path *rpa = *((struct rangelist_path *const *) a); 1448130803Smarcel struct rangelist_path *rpb = *((struct rangelist_path *const *) b); 1449130803Smarcel svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0, 1450130803Smarcel svn_merge_range_t *); 1451130803Smarcel svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0, 1452130803Smarcel svn_merge_range_t *); 1453130803Smarcel 1454130803Smarcel if (mra->start < mrb->start) 1455130803Smarcel return -1; 1456130803Smarcel if (mra->start > mrb->start) 1457130803Smarcel return 1; 1458130803Smarcel if (mra->end < mrb->end) 1459130803Smarcel return -1; 1460130803Smarcel if (mra->end > mrb->end) 1461130803Smarcel return 1; 1462130803Smarcel 1463130803Smarcel return 0; 1464130803Smarcel} 1465130803Smarcel 1466130803Smarcel/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of 1467130803Smarcel 'struct path_list_range's. This list represents the rangelists in 1468130803Smarcel MERGEINFO and each path which has mergeinfo in that range. 1469130803Smarcel If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed 1470130803Smarcel as the result of a reverse merge. */ 1471130803Smarcelstatic svn_error_t * 1472130803Smarcelcombine_mergeinfo_path_lists(apr_array_header_t **combined_list, 1473130803Smarcel svn_mergeinfo_t mergeinfo, 1474130803Smarcel svn_boolean_t reverse_merge, 1475130803Smarcel apr_pool_t *pool) 1476130803Smarcel{ 1477130803Smarcel apr_hash_index_t *hi; 1478130803Smarcel apr_array_header_t *rangelist_paths; 1479130803Smarcel apr_pool_t *subpool = svn_pool_create(pool); 1480130803Smarcel 1481130803Smarcel /* Create a list of (revision range, path) tuples from MERGEINFO. */ 1482130803Smarcel rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo), 1483130803Smarcel sizeof(struct rangelist_path *)); 1484130803Smarcel for (hi = apr_hash_first(subpool, mergeinfo); hi; 1485130803Smarcel hi = apr_hash_next(hi)) 1486130803Smarcel { 1487130803Smarcel int i; 1488130803Smarcel struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp)); 1489130803Smarcel apr_hash_this(hi, (void *) &rp->path, NULL, 1490130803Smarcel (void *) &rp->rangelist); 1491130803Smarcel APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp; 1492130803Smarcel 1493130803Smarcel /* We need to make local copies of the rangelist, since we will be 1494130803Smarcel modifying it, below. */ 1495130803Smarcel rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool); 1496130803Smarcel 1497130803Smarcel /* Make all of the rangelists inclusive, both start and end. */ 1498214947Sgonzo for (i = 0; i < rp->rangelist->nelts; i++) 1499130803Smarcel APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1; 1500130803Smarcel } 1501130803Smarcel 1502130803Smarcel /* Loop over the (revision range, path) tuples, chopping them into 1503130803Smarcel (revision range, paths) tuples, and appending those to the output 1504130803Smarcel list. */ 1505130803Smarcel if (! *combined_list) 1506130803Smarcel *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *)); 1507130803Smarcel 1508130803Smarcel while (rangelist_paths->nelts > 1) 1509130803Smarcel { 1510130803Smarcel svn_revnum_t youngest, next_youngest, tail, youngest_end; 1511130803Smarcel struct path_list_range *plr; 1512130803Smarcel struct rangelist_path *rp; 1513130803Smarcel int num_revs; 1514130803Smarcel int i; 1515130803Smarcel 1516130803Smarcel /* First, sort the list such that the start revision of the first 1517214947Sgonzo revision arrays are sorted. */ 1518214947Sgonzo qsort(rangelist_paths->elts, rangelist_paths->nelts, 1519130803Smarcel rangelist_paths->elt_size, compare_rangelist_paths); 1520130803Smarcel 1521130803Smarcel /* Next, find the number of revision ranges which start with the same 1522130803Smarcel revision. */ 1523130803Smarcel rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1524130803Smarcel youngest = 1525130803Smarcel APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start; 1526130803Smarcel next_youngest = youngest; 1527130803Smarcel for (num_revs = 1; next_youngest == youngest; num_revs++) 1528130803Smarcel { 1529130803Smarcel if (num_revs == rangelist_paths->nelts) 1530130803Smarcel { 1531130803Smarcel num_revs += 1; 1532130803Smarcel break; 1533130803Smarcel } 1534130803Smarcel rp = APR_ARRAY_IDX(rangelist_paths, num_revs, 1535130803Smarcel struct rangelist_path *); 1536130803Smarcel next_youngest = APR_ARRAY_IDX(rp->rangelist, 0, 1537130803Smarcel struct svn_merge_range_t *)->start; 1538130803Smarcel } 1539130803Smarcel num_revs -= 1; 1540130803Smarcel 1541130803Smarcel /* The start of the new range will be YOUNGEST, and we now find the end 1542130803Smarcel of the new range, which should be either one less than the next 1543130803Smarcel earliest start of a rangelist, or the end of the first rangelist. */ 1544130803Smarcel youngest_end = 1545130803Smarcel APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0, 1546130803Smarcel struct rangelist_path *)->rangelist, 1547130803Smarcel 0, svn_merge_range_t *)->end; 1548130803Smarcel if ( (next_youngest == youngest) || (youngest_end < next_youngest) ) 1549130803Smarcel tail = youngest_end; 1550130803Smarcel else 1551130803Smarcel tail = next_youngest - 1; 1552130803Smarcel 1553130803Smarcel /* Insert the (earliest, tail) tuple into the output list, along with 1554130803Smarcel a list of paths which match it. */ 1555130803Smarcel plr = apr_palloc(pool, sizeof(*plr)); 1556130803Smarcel plr->reverse_merge = reverse_merge; 1557130803Smarcel plr->range.start = youngest; 1558130803Smarcel plr->range.end = tail; 1559130803Smarcel plr->paths = apr_array_make(pool, num_revs, sizeof(const char *)); 1560130803Smarcel for (i = 0; i < num_revs; i++) 1561130803Smarcel APR_ARRAY_PUSH(plr->paths, const char *) = 1562130803Smarcel APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path; 1563130803Smarcel APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1564130803Smarcel 1565130803Smarcel /* Now, check to see which (rangelist path) combinations we can remove, 1566130803Smarcel and do so. */ 1567130803Smarcel for (i = 0; i < num_revs; i++) 1568130803Smarcel { 1569130803Smarcel svn_merge_range_t *range; 1570130803Smarcel rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *); 1571130803Smarcel range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *); 1572130803Smarcel 1573214947Sgonzo /* Set the start of the range to beyond the end of the range we 1574130803Smarcel just built. If the range is now "inverted", we can get pop it 1575130803Smarcel off the list. */ 1576130803Smarcel range->start = tail + 1; 1577130803Smarcel if (range->start > range->end) 1578130803Smarcel { 1579130803Smarcel if (rp->rangelist->nelts == 1) 1580130803Smarcel { 1581130803Smarcel /* The range is the only on its list, so we should remove 1582130803Smarcel the entire rangelist_path, adjusting our loop control 1583130803Smarcel variables appropriately. */ 1584130803Smarcel array_pop_front(rangelist_paths); 1585130803Smarcel i--; 1586130803Smarcel num_revs--; 1587130803Smarcel } 1588130803Smarcel else 1589130803Smarcel { 1590130803Smarcel /* We have more than one range on the list, so just remove 1591130803Smarcel the first one. */ 1592130803Smarcel array_pop_front(rp->rangelist); 1593130803Smarcel } 1594130803Smarcel } 1595130803Smarcel } 1596130803Smarcel } 1597130803Smarcel 1598130803Smarcel /* Finally, add the last remaining (revision range, path) to the output 1599130803Smarcel list. */ 1600130803Smarcel if (rangelist_paths->nelts > 0) 1601130803Smarcel { 1602130803Smarcel struct rangelist_path *first_rp = 1603130803Smarcel APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1604130803Smarcel while (first_rp->rangelist->nelts > 0) 1605130803Smarcel { 1606130803Smarcel struct path_list_range *plr = apr_palloc(pool, sizeof(*plr)); 1607130803Smarcel 1608130803Smarcel plr->reverse_merge = reverse_merge; 1609130803Smarcel plr->paths = apr_array_make(pool, 1, sizeof(const char *)); 1610130803Smarcel APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path; 1611130803Smarcel plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0, 1612130803Smarcel svn_merge_range_t *); 1613130803Smarcel array_pop_front(first_rp->rangelist); 1614130803Smarcel APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1615130803Smarcel } 1616130803Smarcel } 1617130803Smarcel 1618130803Smarcel svn_pool_destroy(subpool); 1619130803Smarcel 1620130803Smarcel return SVN_NO_ERROR; 1621130803Smarcel} 1622130803Smarcel 1623130803Smarcel 1624130803Smarcel/* Pity that C is so ... linear. */ 1625130803Smarcelstatic svn_error_t * 1626130803Smarceldo_logs(svn_fs_t *fs, 1627130803Smarcel const apr_array_header_t *paths, 1628130803Smarcel svn_mergeinfo_t log_target_history_as_mergeinfo, 1629130803Smarcel svn_mergeinfo_t processed, 1630130803Smarcel apr_hash_t *nested_merges, 1631130803Smarcel svn_revnum_t hist_start, 1632130803Smarcel svn_revnum_t hist_end, 1633130803Smarcel int limit, 1634130803Smarcel svn_boolean_t discover_changed_paths, 1635130803Smarcel svn_boolean_t strict_node_history, 1636130803Smarcel svn_boolean_t include_merged_revisions, 1637130803Smarcel svn_boolean_t handling_merged_revisions, 1638130803Smarcel svn_boolean_t subtractive_merge, 1639130803Smarcel svn_boolean_t ignore_missing_locations, 1640130803Smarcel const apr_array_header_t *revprops, 1641130803Smarcel svn_boolean_t descending_order, 1642130803Smarcel svn_log_entry_receiver_t receiver, 1643130803Smarcel void *receiver_baton, 1644130803Smarcel svn_repos_authz_func_t authz_read_func, 1645130803Smarcel void *authz_read_baton, 1646130803Smarcel apr_pool_t *pool); 1647130803Smarcel 1648130803Smarcel/* Comparator function for handle_merged_revisions(). Sorts path_list_range 1649130803Smarcel structs in increasing order based on the struct's RANGE.START revision, 1650130803Smarcel then RANGE.END revision. */ 1651130803Smarcelstatic int 1652130803Smarcelcompare_path_list_range(const void *a, const void *b) 1653130803Smarcel{ 1654130803Smarcel struct path_list_range *plr_a = *((struct path_list_range *const *) a); 1655130803Smarcel struct path_list_range *plr_b = *((struct path_list_range *const *) b); 1656130803Smarcel 1657130803Smarcel if (plr_a->range.start < plr_b->range.start) 1658130803Smarcel return -1; 1659130803Smarcel if (plr_a->range.start > plr_b->range.start) 1660130803Smarcel return 1; 1661130803Smarcel if (plr_a->range.end < plr_b->range.end) 1662130803Smarcel return -1; 1663130803Smarcel if (plr_a->range.end > plr_b->range.end) 1664130803Smarcel return 1; 1665130803Smarcel 1666130803Smarcel return 0; 1667130803Smarcel} 1668130803Smarcel 1669130803Smarcel/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS 1670130803Smarcel (as collected by examining paths of interest to a log operation), and 1671130803Smarcel determine which revisions to report as having been merged or reverse-merged 1672130803Smarcel via the commit resulting in REV. 1673130803Smarcel 1674130803Smarcel Silently ignore some failures to find the revisions mentioned in the 1675130803Smarcel added/deleted mergeinfos, as might happen if there is invalid mergeinfo. 1676130803Smarcel 1677130803Smarcel Other parameters are as described by do_logs(), around which this 1678130803Smarcel is a recursion wrapper. */ 1679130803Smarcelstatic svn_error_t * 1680130803Smarcelhandle_merged_revisions(svn_revnum_t rev, 1681130803Smarcel svn_fs_t *fs, 1682130803Smarcel svn_mergeinfo_t log_target_history_as_mergeinfo, 1683130803Smarcel apr_hash_t *nested_merges, 1684130803Smarcel svn_mergeinfo_t processed, 1685130803Smarcel svn_mergeinfo_t added_mergeinfo, 1686130803Smarcel svn_mergeinfo_t deleted_mergeinfo, 1687130803Smarcel svn_boolean_t discover_changed_paths, 1688130803Smarcel svn_boolean_t strict_node_history, 1689130803Smarcel const apr_array_header_t *revprops, 1690130803Smarcel svn_log_entry_receiver_t receiver, 1691130803Smarcel void *receiver_baton, 1692130803Smarcel svn_repos_authz_func_t authz_read_func, 1693130803Smarcel void *authz_read_baton, 1694130803Smarcel apr_pool_t *pool) 1695130803Smarcel{ 1696130803Smarcel apr_array_header_t *combined_list = NULL; 1697130803Smarcel svn_log_entry_t *empty_log_entry; 1698130803Smarcel apr_pool_t *iterpool; 1699130803Smarcel int i; 1700130803Smarcel 1701130803Smarcel if (apr_hash_count(added_mergeinfo) == 0 1702130803Smarcel && apr_hash_count(deleted_mergeinfo) == 0) 1703130803Smarcel return SVN_NO_ERROR; 1704130803Smarcel 1705130803Smarcel if (apr_hash_count(added_mergeinfo)) 1706130803Smarcel SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo, 1707130803Smarcel FALSE, pool)); 1708130803Smarcel 1709130803Smarcel if (apr_hash_count(deleted_mergeinfo)) 1710130803Smarcel SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo, 1711130803Smarcel TRUE, pool)); 1712130803Smarcel 1713130803Smarcel SVN_ERR_ASSERT(combined_list != NULL); 1714130803Smarcel qsort(combined_list->elts, combined_list->nelts, 1715130803Smarcel combined_list->elt_size, compare_path_list_range); 1716130803Smarcel 1717130803Smarcel /* Because the combined_lists are ordered youngest to oldest, 1718130803Smarcel iterate over them in reverse. */ 1719130803Smarcel iterpool = svn_pool_create(pool); 1720130803Smarcel for (i = combined_list->nelts - 1; i >= 0; i--) 1721130803Smarcel { 1722130803Smarcel struct path_list_range *pl_range 1723130803Smarcel = APR_ARRAY_IDX(combined_list, i, struct path_list_range *); 1724130803Smarcel 1725130803Smarcel svn_pool_clear(iterpool); 1726130803Smarcel SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo, 1727130803Smarcel processed, nested_merges, 1728130803Smarcel pl_range->range.start, pl_range->range.end, 0, 1729130803Smarcel discover_changed_paths, strict_node_history, 1730130803Smarcel TRUE, pl_range->reverse_merge, TRUE, TRUE, 1731130803Smarcel revprops, TRUE, receiver, receiver_baton, 1732130803Smarcel authz_read_func, authz_read_baton, iterpool)); 1733130803Smarcel } 1734130803Smarcel svn_pool_destroy(iterpool); 1735130803Smarcel 1736130803Smarcel /* Send the empty revision. */ 1737130803Smarcel empty_log_entry = svn_log_entry_create(pool); 1738130803Smarcel empty_log_entry->revision = SVN_INVALID_REVNUM; 1739130803Smarcel return (*receiver)(receiver_baton, empty_log_entry, pool); 1740130803Smarcel} 1741130803Smarcel 1742130803Smarcel/* This is used by do_logs to differentiate between forward and 1743130803Smarcel reverse merges. */ 1744130803Smarcelstruct added_deleted_mergeinfo 1745130803Smarcel{ 1746130803Smarcel svn_mergeinfo_t added_mergeinfo; 1747130803Smarcel svn_mergeinfo_t deleted_mergeinfo; 1748130803Smarcel}; 1749130803Smarcel 1750130803Smarcel/* Reduce the search range PATHS, HIST_START, HIST_END by removing 1751130803Smarcel parts already covered by PROCESSED. If reduction is possible 1752130803Smarcel elements may be removed from PATHS and *START_REDUCED and 1753130803Smarcel *END_REDUCED may be set to a narrower range. */ 1754130803Smarcelstatic svn_error_t * 1755130803Smarcelreduce_search(apr_array_header_t *paths, 1756130803Smarcel svn_revnum_t *hist_start, 1757130803Smarcel svn_revnum_t *hist_end, 1758130803Smarcel svn_mergeinfo_t processed, 1759130803Smarcel apr_pool_t *scratch_pool) 1760130803Smarcel{ 1761130803Smarcel /* We add 1 to end to compensate for store_search */ 1762130803Smarcel svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end; 1763130803Smarcel svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1; 1764130803Smarcel int i; 1765130803Smarcel 1766130803Smarcel for (i = 0; i < paths->nelts; ++i) 1767130803Smarcel { 1768130803Smarcel const char *path = APR_ARRAY_IDX(paths, i, const char *); 1769130803Smarcel svn_rangelist_t *ranges = svn_hash_gets(processed, path); 1770130803Smarcel int j; 1771130803Smarcel 1772130803Smarcel if (!ranges) 1773130803Smarcel continue; 1774130803Smarcel 1775130803Smarcel /* ranges is ordered, could we use some sort of binary search 1776130803Smarcel rather than iterating? */ 1777130803Smarcel for (j = 0; j < ranges->nelts; ++j) 1778130803Smarcel { 1779130803Smarcel svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j, 1780130803Smarcel svn_merge_range_t *); 1781130803Smarcel if (range->start <= start && range->end >= end) 1782130803Smarcel { 1783130803Smarcel for (j = i; j < paths->nelts - 1; ++j) 1784130803Smarcel APR_ARRAY_IDX(paths, j, const char *) 1785130803Smarcel = APR_ARRAY_IDX(paths, j + 1, const char *); 1786130803Smarcel 1787130803Smarcel --paths->nelts; 1788130803Smarcel --i; 1789130803Smarcel break; 1790130803Smarcel } 1791130803Smarcel 1792130803Smarcel /* If there is only one path then we also check for a 1793130803Smarcel partial overlap rather than the full overlap above, and 1794130803Smarcel reduce the [hist_start, hist_end] range rather than 1795130803Smarcel dropping the path. */ 1796130803Smarcel if (paths->nelts == 1) 1797130803Smarcel { 1798130803Smarcel if (range->start <= start && range->end > start) 1799130803Smarcel { 1800130803Smarcel if (start == *hist_start) 1801130803Smarcel *hist_start = range->end - 1; 1802130803Smarcel else 1803130803Smarcel *hist_end = range->end - 1; 1804130803Smarcel break; 1805130803Smarcel } 1806130803Smarcel if (range->start < end && range->end >= end) 1807130803Smarcel { 1808130803Smarcel if (start == *hist_start) 1809130803Smarcel *hist_end = range->start; 1810130803Smarcel else 1811130803Smarcel *hist_start = range->start; 1812130803Smarcel break; 1813130803Smarcel } 1814130803Smarcel } 1815130803Smarcel } 1816130803Smarcel } 1817130803Smarcel 1818130803Smarcel return SVN_NO_ERROR; 1819130803Smarcel} 1820130803Smarcel 1821130803Smarcel/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */ 1822130803Smarcelstatic svn_error_t * 1823130803Smarcelstore_search(svn_mergeinfo_t processed, 1824130803Smarcel const apr_array_header_t *paths, 1825130803Smarcel svn_revnum_t hist_start, 1826130803Smarcel svn_revnum_t hist_end, 1827130803Smarcel apr_pool_t *scratch_pool) 1828130803Smarcel{ 1829130803Smarcel /* We add 1 to end so that we can use the mergeinfo API to handle 1830130803Smarcel singe revisions where HIST_START is equal to HIST_END. */ 1831130803Smarcel svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end; 1832130803Smarcel svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1; 1833130803Smarcel svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool); 1834130803Smarcel apr_pool_t *processed_pool = apr_hash_pool_get(processed); 1835130803Smarcel int i; 1836130803Smarcel 1837130803Smarcel for (i = 0; i < paths->nelts; ++i) 1838130803Smarcel { 1839130803Smarcel const char *path = APR_ARRAY_IDX(paths, i, const char *); 1840130803Smarcel svn_rangelist_t *ranges = apr_array_make(processed_pool, 1, 1841130803Smarcel sizeof(svn_merge_range_t*)); 1842130803Smarcel svn_merge_range_t *range = apr_palloc(processed_pool, 1843130803Smarcel sizeof(svn_merge_range_t)); 1844130803Smarcel 1845130803Smarcel range->start = start; 1846130803Smarcel range->end = end; 1847130803Smarcel range->inheritable = TRUE; 1848130803Smarcel APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range; 1849130803Smarcel svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges); 1850130803Smarcel } 1851130803Smarcel SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo, 1852130803Smarcel apr_hash_pool_get(processed), scratch_pool)); 1853130803Smarcel 1854130803Smarcel return SVN_NO_ERROR; 1855130803Smarcel} 1856130803Smarcel 1857130803Smarcel/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke 1858130803Smarcel RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send 1859130803Smarcel the logs back as we find them, else buffer the logs and send them back 1860130803Smarcel in youngest->oldest order. 1861130803Smarcel 1862130803Smarcel If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1863130803Smarcel repository locations as fatal -- just ignore them. 1864130803Smarcel 1865130803Smarcel If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo 1866130803Smarcel representing the history of PATHS between HIST_START and HIST_END. 1867130803Smarcel 1868130803Smarcel If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for 1869130803Smarcel merged revisions, see INCLUDE_MERGED_REVISIONS argument to 1870130803Smarcel svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a 1871130803Smarcel recursive call for reverse merged revisions. 1872130803Smarcel 1873130803Smarcel If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t * 1874130803Smarcel mapped to svn_revnum_t *) for logs that were previously sent. On the first 1875130803Smarcel call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is 1876130803Smarcel TRUE, then NESTED_MERGES will be created on the first call to do_logs, 1877130803Smarcel allocated in POOL. It is then shared across 1878130803Smarcel do_logs()/send_logs()/handle_merge_revisions() recursions, see also the 1879130803Smarcel argument of the same name in send_logs(). 1880130803Smarcel 1881130803Smarcel PROCESSED is a mergeinfo hash that represents the paths and 1882130803Smarcel revisions that have already been searched. Allocated like 1883130803Smarcel NESTED_MERGES above. 1884130803Smarcel 1885130803Smarcel All other parameters are the same as svn_repos_get_logs4(). 1886130803Smarcel */ 1887130803Smarcelstatic svn_error_t * 1888130803Smarceldo_logs(svn_fs_t *fs, 1889130803Smarcel const apr_array_header_t *paths, 1890130803Smarcel svn_mergeinfo_t log_target_history_as_mergeinfo, 1891130803Smarcel svn_mergeinfo_t processed, 1892130803Smarcel apr_hash_t *nested_merges, 1893130803Smarcel svn_revnum_t hist_start, 1894130803Smarcel svn_revnum_t hist_end, 1895130803Smarcel int limit, 1896130803Smarcel svn_boolean_t discover_changed_paths, 1897130803Smarcel svn_boolean_t strict_node_history, 1898130803Smarcel svn_boolean_t include_merged_revisions, 1899130803Smarcel svn_boolean_t subtractive_merge, 1900130803Smarcel svn_boolean_t handling_merged_revisions, 1901130803Smarcel svn_boolean_t ignore_missing_locations, 1902130803Smarcel const apr_array_header_t *revprops, 1903130803Smarcel svn_boolean_t descending_order, 1904130803Smarcel svn_log_entry_receiver_t receiver, 1905130803Smarcel void *receiver_baton, 1906130803Smarcel svn_repos_authz_func_t authz_read_func, 1907130803Smarcel void *authz_read_baton, 1908130803Smarcel apr_pool_t *pool) 1909130803Smarcel{ 1910130803Smarcel apr_pool_t *iterpool; 1911130803Smarcel apr_pool_t *subpool = NULL; 1912130803Smarcel apr_array_header_t *revs = NULL; 1913130803Smarcel apr_hash_t *rev_mergeinfo = NULL; 1914130803Smarcel svn_revnum_t current; 1915130803Smarcel apr_array_header_t *histories; 1916130803Smarcel svn_boolean_t any_histories_left = TRUE; 1917130803Smarcel int send_count = 0; 1918130803Smarcel int i; 1919130803Smarcel 1920130803Smarcel if (processed) 1921130803Smarcel { 1922130803Smarcel /* Casting away const. This only happens on recursive calls when 1923130803Smarcel it is known to be safe because we allocated paths. */ 1924130803Smarcel SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end, 1925130803Smarcel processed, pool)); 1926130803Smarcel } 1927130803Smarcel 1928130803Smarcel if (!paths->nelts) 1929130803Smarcel return SVN_NO_ERROR; 1930130803Smarcel 1931130803Smarcel if (processed) 1932130803Smarcel SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool)); 1933130803Smarcel 1934130803Smarcel /* We have a list of paths and a revision range. But we don't care 1935130803Smarcel about all the revisions in the range -- only the ones in which 1936130803Smarcel one of our paths was changed. So let's go figure out which 1937130803Smarcel revisions contain real changes to at least one of our paths. */ 1938130803Smarcel SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end, 1939130803Smarcel strict_node_history, ignore_missing_locations, 1940130803Smarcel authz_read_func, authz_read_baton, pool)); 1941130803Smarcel 1942130803Smarcel /* Loop through all the revisions in the range and add any 1943130803Smarcel where a path was changed to the array, or if they wanted 1944130803Smarcel history in reverse order just send it to them right away. */ 1945130803Smarcel iterpool = svn_pool_create(pool); 1946130803Smarcel for (current = hist_end; 1947130803Smarcel any_histories_left; 1948130803Smarcel current = next_history_rev(histories)) 1949130803Smarcel { 1950130803Smarcel svn_boolean_t changed = FALSE; 1951130803Smarcel any_histories_left = FALSE; 1952130803Smarcel svn_pool_clear(iterpool); 1953130803Smarcel 1954130803Smarcel for (i = 0; i < histories->nelts; i++) 1955130803Smarcel { 1956130803Smarcel struct path_info *info = APR_ARRAY_IDX(histories, i, 1957130803Smarcel struct path_info *); 1958130803Smarcel 1959130803Smarcel /* Check history for this path in current rev. */ 1960130803Smarcel SVN_ERR(check_history(&changed, info, fs, current, 1961130803Smarcel strict_node_history, authz_read_func, 1962130803Smarcel authz_read_baton, hist_start, pool)); 1963130803Smarcel if (! info->done) 1964130803Smarcel any_histories_left = TRUE; 1965130803Smarcel } 1966130803Smarcel 1967130803Smarcel /* If any of the paths changed in this rev then add or send it. */ 1968130803Smarcel if (changed) 1969130803Smarcel { 1970130803Smarcel svn_mergeinfo_t added_mergeinfo = NULL; 1971130803Smarcel svn_mergeinfo_t deleted_mergeinfo = NULL; 1972130803Smarcel svn_boolean_t has_children = FALSE; 1973130803Smarcel apr_hash_t *changes = NULL; 1974130803Smarcel 1975130803Smarcel /* If we're including merged revisions, we need to calculate 1976130803Smarcel the mergeinfo deltas committed in this revision to our 1977130803Smarcel various paths. */ 1978130803Smarcel if (include_merged_revisions) 1979130803Smarcel { 1980130803Smarcel apr_array_header_t *cur_paths = 1981130803Smarcel apr_array_make(iterpool, paths->nelts, sizeof(const char *)); 1982130803Smarcel 1983130803Smarcel /* Get the current paths of our history objects so we can 1984130803Smarcel query mergeinfo. */ 1985130803Smarcel /* ### TODO: Should this be ignoring depleted history items? */ 1986130803Smarcel for (i = 0; i < histories->nelts; i++) 1987130803Smarcel { 1988130803Smarcel struct path_info *info = APR_ARRAY_IDX(histories, i, 1989130803Smarcel struct path_info *); 1990130803Smarcel APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data; 1991130803Smarcel } 1992130803Smarcel SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo, 1993130803Smarcel &deleted_mergeinfo, 1994130803Smarcel &changes, 1995130803Smarcel fs, cur_paths, 1996130803Smarcel current, iterpool, 1997130803Smarcel iterpool)); 1998130803Smarcel has_children = (apr_hash_count(added_mergeinfo) > 0 1999130803Smarcel || apr_hash_count(deleted_mergeinfo) > 0); 2000130803Smarcel } 2001130803Smarcel 2002130803Smarcel /* If our caller wants logs in descending order, we can send 2003130803Smarcel 'em now (because that's the order we're crawling history 2004130803Smarcel in anyway). */ 2005130803Smarcel if (descending_order) 2006130803Smarcel { 2007130803Smarcel SVN_ERR(send_log(current, fs, changes, 2008130803Smarcel log_target_history_as_mergeinfo, nested_merges, 2009130803Smarcel discover_changed_paths, 2010130803Smarcel subtractive_merge, handling_merged_revisions, 2011130803Smarcel revprops, has_children, 2012130803Smarcel receiver, receiver_baton, 2013130803Smarcel authz_read_func, authz_read_baton, iterpool)); 2014130803Smarcel 2015130803Smarcel if (has_children) /* Implies include_merged_revisions == TRUE */ 2016130803Smarcel { 2017130803Smarcel if (!nested_merges) 2018130803Smarcel { 2019130803Smarcel /* We're at the start of the recursion stack, create a 2020130803Smarcel single hash to be shared across all of the merged 2021130803Smarcel recursions so we can track and squelch duplicates. */ 2022130803Smarcel subpool = svn_pool_create(pool); 2023130803Smarcel nested_merges = svn_hash__make(subpool); 2024130803Smarcel processed = svn_hash__make(subpool); 2025130803Smarcel } 2026130803Smarcel 2027130803Smarcel SVN_ERR(handle_merged_revisions( 2028130803Smarcel current, fs, 2029130803Smarcel log_target_history_as_mergeinfo, nested_merges, 2030130803Smarcel processed, 2031130803Smarcel added_mergeinfo, deleted_mergeinfo, 2032130803Smarcel discover_changed_paths, 2033130803Smarcel strict_node_history, 2034130803Smarcel revprops, 2035130803Smarcel receiver, receiver_baton, 2036130803Smarcel authz_read_func, 2037130803Smarcel authz_read_baton, 2038130803Smarcel iterpool)); 2039130803Smarcel } 2040130803Smarcel if (limit && ++send_count >= limit) 2041130803Smarcel break; 2042130803Smarcel } 2043130803Smarcel /* Otherwise, the caller wanted logs in ascending order, so 2044130803Smarcel we have to buffer up a list of revs and (if doing 2045130803Smarcel mergeinfo) a hash of related mergeinfo deltas, and 2046130803Smarcel process them later. */ 2047130803Smarcel else 2048130803Smarcel { 2049130803Smarcel if (! revs) 2050130803Smarcel revs = apr_array_make(pool, 64, sizeof(svn_revnum_t)); 2051130803Smarcel APR_ARRAY_PUSH(revs, svn_revnum_t) = current; 2052130803Smarcel 2053130803Smarcel if (added_mergeinfo || deleted_mergeinfo) 2054130803Smarcel { 2055130803Smarcel svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev)); 2056130803Smarcel struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2057130803Smarcel apr_palloc(pool, sizeof(*add_and_del_mergeinfo)); 2058130803Smarcel 2059130803Smarcel if (added_mergeinfo) 2060130803Smarcel add_and_del_mergeinfo->added_mergeinfo = 2061130803Smarcel svn_mergeinfo_dup(added_mergeinfo, pool); 2062130803Smarcel 2063130803Smarcel if (deleted_mergeinfo) 2064130803Smarcel add_and_del_mergeinfo->deleted_mergeinfo = 2065130803Smarcel svn_mergeinfo_dup(deleted_mergeinfo, pool); 2066130803Smarcel 2067130803Smarcel *cur_rev = current; 2068130803Smarcel if (! rev_mergeinfo) 2069130803Smarcel rev_mergeinfo = svn_hash__make(pool); 2070130803Smarcel apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev), 2071130803Smarcel add_and_del_mergeinfo); 2072130803Smarcel } 2073130803Smarcel } 2074130803Smarcel } 2075130803Smarcel } 2076130803Smarcel svn_pool_destroy(iterpool); 2077130803Smarcel 2078130803Smarcel if (subpool) 2079130803Smarcel { 2080130803Smarcel nested_merges = NULL; 2081130803Smarcel svn_pool_destroy(subpool); 2082130803Smarcel } 2083130803Smarcel 2084130803Smarcel if (revs) 2085130803Smarcel { 2086130803Smarcel /* Work loop for processing the revisions we found since they wanted 2087130803Smarcel history in forward order. */ 2088130803Smarcel iterpool = svn_pool_create(pool); 2089130803Smarcel for (i = 0; i < revs->nelts; ++i) 2090130803Smarcel { 2091130803Smarcel svn_mergeinfo_t added_mergeinfo; 2092130803Smarcel svn_mergeinfo_t deleted_mergeinfo; 2093130803Smarcel svn_boolean_t has_children = FALSE; 2094130803Smarcel 2095130803Smarcel svn_pool_clear(iterpool); 2096130803Smarcel current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t); 2097130803Smarcel 2098130803Smarcel /* If we've got a hash of revision mergeinfo (which can only 2099130803Smarcel happen if INCLUDE_MERGED_REVISIONS was set), we check to 2100130803Smarcel see if this revision is one which merged in other 2101130803Smarcel revisions we need to handle recursively. */ 2102130803Smarcel if (rev_mergeinfo) 2103130803Smarcel { 2104130803Smarcel struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2105130803Smarcel apr_hash_get(rev_mergeinfo, ¤t, sizeof(svn_revnum_t)); 2106130803Smarcel added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo; 2107130803Smarcel deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo; 2108130803Smarcel has_children = (apr_hash_count(added_mergeinfo) > 0 2109130803Smarcel || apr_hash_count(deleted_mergeinfo) > 0); 2110130803Smarcel } 2111130803Smarcel 2112130803Smarcel SVN_ERR(send_log(current, fs, NULL, 2113130803Smarcel log_target_history_as_mergeinfo, nested_merges, 2114130803Smarcel discover_changed_paths, subtractive_merge, 2115130803Smarcel handling_merged_revisions, revprops, has_children, 2116130803Smarcel receiver, receiver_baton, authz_read_func, 2117130803Smarcel authz_read_baton, iterpool)); 2118130803Smarcel if (has_children) 2119130803Smarcel { 2120130803Smarcel if (!nested_merges) 2121130803Smarcel { 2122130803Smarcel subpool = svn_pool_create(pool); 2123130803Smarcel nested_merges = svn_hash__make(subpool); 2124130803Smarcel } 2125130803Smarcel 2126130803Smarcel SVN_ERR(handle_merged_revisions(current, fs, 2127130803Smarcel log_target_history_as_mergeinfo, 2128130803Smarcel nested_merges, 2129130803Smarcel processed, 2130130803Smarcel added_mergeinfo, 2131130803Smarcel deleted_mergeinfo, 2132130803Smarcel discover_changed_paths, 2133130803Smarcel strict_node_history, revprops, 2134130803Smarcel receiver, receiver_baton, 2135130803Smarcel authz_read_func, 2136130803Smarcel authz_read_baton, 2137130803Smarcel iterpool)); 2138130803Smarcel } 2139130803Smarcel if (limit && i + 1 >= limit) 2140130803Smarcel break; 2141130803Smarcel } 2142130803Smarcel svn_pool_destroy(iterpool); 2143130803Smarcel } 2144130803Smarcel 2145130803Smarcel return SVN_NO_ERROR; 2146130803Smarcel} 2147130803Smarcel 2148130803Smarcelstruct location_segment_baton 2149130803Smarcel{ 2150130803Smarcel apr_array_header_t *history_segments; 2151130803Smarcel apr_pool_t *pool; 2152130803Smarcel}; 2153130803Smarcel 2154130803Smarcel/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */ 2155130803Smarcelstatic svn_error_t * 2156130803Smarcellocation_segment_receiver(svn_location_segment_t *segment, 2157130803Smarcel void *baton, 2158130803Smarcel apr_pool_t *pool) 2159130803Smarcel{ 2160130803Smarcel struct location_segment_baton *b = baton; 2161130803Smarcel 2162130803Smarcel APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) = 2163130803Smarcel svn_location_segment_dup(segment, b->pool); 2164130803Smarcel 2165130803Smarcel return SVN_NO_ERROR; 2166130803Smarcel} 2167214947Sgonzo 2168214947Sgonzo 2169214947Sgonzo/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined 2170130803Smarcel history of each path in PATHS between START_REV and END_REV in REPOS's 2171130803Smarcel filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL 2172130803Smarcel is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all 2173130803Smarcel other (temporary) allocations. Other parameters are the same as 2174130803Smarcel svn_repos_get_logs4(). */ 2175214947Sgonzostatic svn_error_t * 2176214947Sgonzoget_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo, 2177214947Sgonzo svn_repos_t *repos, 2178214947Sgonzo const apr_array_header_t *paths, 2179130803Smarcel svn_revnum_t start_rev, 2180130803Smarcel svn_revnum_t end_rev, 2181130803Smarcel svn_repos_authz_func_t authz_read_func, 2182130803Smarcel void *authz_read_baton, 2183130803Smarcel apr_pool_t *result_pool, 2184130803Smarcel apr_pool_t *scratch_pool) 2185130803Smarcel{ 2186130803Smarcel int i; 2187130803Smarcel svn_mergeinfo_t path_history_mergeinfo; 2188130803Smarcel apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2189130803Smarcel 2190130803Smarcel SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev)); 2191130803Smarcel SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev)); 2192130803Smarcel 2193130803Smarcel /* Ensure START_REV is the youngest revision, as required by 2194130803Smarcel svn_repos_node_location_segments, for which this is an iterative 2195130803Smarcel wrapper. */ 2196130803Smarcel if (start_rev < end_rev) 2197130803Smarcel { 2198130803Smarcel svn_revnum_t tmp_rev = start_rev; 2199130803Smarcel start_rev = end_rev; 2200130803Smarcel end_rev = tmp_rev; 2201130803Smarcel } 2202130803Smarcel 2203130803Smarcel *paths_history_mergeinfo = svn_hash__make(result_pool); 2204130803Smarcel 2205130803Smarcel for (i = 0; i < paths->nelts; i++) 2206130803Smarcel { 2207130803Smarcel const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 2208130803Smarcel struct location_segment_baton loc_seg_baton; 2209130803Smarcel 2210130803Smarcel svn_pool_clear(iterpool); 2211130803Smarcel loc_seg_baton.pool = scratch_pool; 2212130803Smarcel loc_seg_baton.history_segments = 2213130803Smarcel apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *)); 2214130803Smarcel 2215130803Smarcel SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev, 2216130803Smarcel start_rev, end_rev, 2217130803Smarcel location_segment_receiver, 2218130803Smarcel &loc_seg_baton, 2219130803Smarcel authz_read_func, 2220130803Smarcel authz_read_baton, 2221130803Smarcel iterpool)); 2222130803Smarcel 2223130803Smarcel SVN_ERR(svn_mergeinfo__mergeinfo_from_segments( 2224130803Smarcel &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool)); 2225130803Smarcel SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo, 2226130803Smarcel svn_mergeinfo_dup(path_history_mergeinfo, 2227130803Smarcel result_pool), 2228130803Smarcel result_pool, iterpool)); 2229130803Smarcel } 2230130803Smarcel svn_pool_destroy(iterpool); 2231130803Smarcel return SVN_NO_ERROR; 2232130803Smarcel} 2233130803Smarcel 2234130803Smarcelsvn_error_t * 2235130803Smarcelsvn_repos_get_logs4(svn_repos_t *repos, 2236130803Smarcel const apr_array_header_t *paths, 2237130803Smarcel svn_revnum_t start, 2238130803Smarcel svn_revnum_t end, 2239130803Smarcel int limit, 2240130803Smarcel svn_boolean_t discover_changed_paths, 2241130803Smarcel svn_boolean_t strict_node_history, 2242130803Smarcel svn_boolean_t include_merged_revisions, 2243130803Smarcel const apr_array_header_t *revprops, 2244130803Smarcel svn_repos_authz_func_t authz_read_func, 2245130803Smarcel void *authz_read_baton, 2246130803Smarcel svn_log_entry_receiver_t receiver, 2247130803Smarcel void *receiver_baton, 2248130803Smarcel apr_pool_t *pool) 2249130803Smarcel{ 2250130803Smarcel svn_revnum_t head = SVN_INVALID_REVNUM; 2251130803Smarcel svn_fs_t *fs = repos->fs; 2252130803Smarcel svn_boolean_t descending_order; 2253130803Smarcel svn_mergeinfo_t paths_history_mergeinfo = NULL; 2254130803Smarcel 2255130803Smarcel /* Setup log range. */ 2256130803Smarcel SVN_ERR(svn_fs_youngest_rev(&head, fs, pool)); 2257130803Smarcel 2258130803Smarcel if (! SVN_IS_VALID_REVNUM(start)) 2259130803Smarcel start = head; 2260130803Smarcel 2261130803Smarcel if (! SVN_IS_VALID_REVNUM(end)) 2262130803Smarcel end = head; 2263130803Smarcel 2264130803Smarcel /* Check that revisions are sane before ever invoking receiver. */ 2265130803Smarcel if (start > head) 2266130803Smarcel return svn_error_createf 2267130803Smarcel (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2268130803Smarcel _("No such revision %ld"), start); 2269130803Smarcel if (end > head) 2270130803Smarcel return svn_error_createf 2271130803Smarcel (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2272130803Smarcel _("No such revision %ld"), end); 2273130803Smarcel 2274130803Smarcel /* Ensure a youngest-to-oldest revision crawl ordering using our 2275130803Smarcel (possibly sanitized) range values. */ 2276130803Smarcel descending_order = start >= end; 2277130803Smarcel if (descending_order) 2278130803Smarcel { 2279130803Smarcel svn_revnum_t tmp_rev = start; 2280130803Smarcel start = end; 2281130803Smarcel end = tmp_rev; 2282130803Smarcel } 2283130803Smarcel 2284130803Smarcel if (! paths) 2285130803Smarcel paths = apr_array_make(pool, 0, sizeof(const char *)); 2286130803Smarcel 2287130803Smarcel /* If we're not including merged revisions, and we were given no 2288130803Smarcel paths or a single empty (or "/") path, then we can bypass a bunch 2289130803Smarcel of complexity because we already know in which revisions the root 2290130803Smarcel directory was changed -- all of them. */ 2291130803Smarcel if ((! include_merged_revisions) 2292130803Smarcel && ((! paths->nelts) 2293130803Smarcel || ((paths->nelts == 1) 2294130803Smarcel && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *)) 2295130803Smarcel || (strcmp(APR_ARRAY_IDX(paths, 0, const char *), 2296130803Smarcel "/") == 0))))) 2297130803Smarcel { 2298130803Smarcel apr_uint64_t send_count = 0; 2299130803Smarcel int i; 2300130803Smarcel apr_pool_t *iterpool = svn_pool_create(pool); 2301130803Smarcel 2302130803Smarcel /* If we are provided an authz callback function, use it to 2303130803Smarcel verify that the user has read access to the root path in the 2304130803Smarcel first of our revisions. 2305130803Smarcel 2306130803Smarcel ### FIXME: Strictly speaking, we should be checking this 2307130803Smarcel ### access in every revision along the line. But currently, 2308130803Smarcel ### there are no known authz implementations which concern 2309130803Smarcel ### themselves with per-revision access. */ 2310130803Smarcel if (authz_read_func) 2311130803Smarcel { 2312130803Smarcel svn_boolean_t readable; 2313130803Smarcel svn_fs_root_t *rev_root; 2314130803Smarcel 2315130803Smarcel SVN_ERR(svn_fs_revision_root(&rev_root, fs, 2316130803Smarcel descending_order ? end : start, pool)); 2317130803Smarcel SVN_ERR(authz_read_func(&readable, rev_root, "", 2318130803Smarcel authz_read_baton, pool)); 2319130803Smarcel if (! readable) 2320130803Smarcel return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 2321130803Smarcel } 2322130803Smarcel 2323130803Smarcel send_count = end - start + 1; 2324130803Smarcel if (limit && send_count > limit) 2325130803Smarcel send_count = limit; 2326130803Smarcel for (i = 0; i < send_count; ++i) 2327130803Smarcel { 2328130803Smarcel svn_revnum_t rev; 2329130803Smarcel 2330130803Smarcel svn_pool_clear(iterpool); 2331130803Smarcel 2332130803Smarcel if (descending_order) 2333130803Smarcel rev = end - i; 2334130803Smarcel else 2335130803Smarcel rev = start + i; 2336130803Smarcel SVN_ERR(send_log(rev, fs, NULL, NULL, NULL, 2337130803Smarcel discover_changed_paths, FALSE, 2338130803Smarcel FALSE, revprops, FALSE, receiver, 2339130803Smarcel receiver_baton, authz_read_func, 2340130803Smarcel authz_read_baton, iterpool)); 2341130803Smarcel } 2342130803Smarcel svn_pool_destroy(iterpool); 2343130803Smarcel 2344130803Smarcel return SVN_NO_ERROR; 2345130803Smarcel } 2346130803Smarcel 2347130803Smarcel /* If we are including merged revisions, then create mergeinfo that 2348130803Smarcel represents all of PATHS' history between START and END. We will use 2349130803Smarcel this later to squelch duplicate log revisions that might exist in 2350130803Smarcel both natural history and merged-in history. See 2351130803Smarcel http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */ 2352130803Smarcel if (include_merged_revisions) 2353130803Smarcel { 2354130803Smarcel apr_pool_t *subpool = svn_pool_create(pool); 2355130803Smarcel 2356130803Smarcel SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo, 2357130803Smarcel repos, paths, start, end, 2358130803Smarcel authz_read_func, 2359130803Smarcel authz_read_baton, 2360130803Smarcel pool, subpool)); 2361130803Smarcel svn_pool_destroy(subpool); 2362130803Smarcel } 2363130803Smarcel 2364130803Smarcel return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end, 2365130803Smarcel limit, discover_changed_paths, strict_node_history, 2366130803Smarcel include_merged_revisions, FALSE, FALSE, FALSE, revprops, 2367130803Smarcel descending_order, receiver, receiver_baton, 2368130803Smarcel authz_read_func, authz_read_baton, pool); 2369130803Smarcel} 2370130803Smarcel