log.c revision 251881
1203945Sweongyo/* log.c --- retrieving log messages 2203945Sweongyo * 3203945Sweongyo * ==================================================================== 4203945Sweongyo * Licensed to the Apache Software Foundation (ASF) under one 5203945Sweongyo * or more contributor license agreements. See the NOTICE file 6203945Sweongyo * distributed with this work for additional information 7203945Sweongyo * regarding copyright ownership. The ASF licenses this file 8203945Sweongyo * to you under the Apache License, Version 2.0 (the 9203945Sweongyo * "License"); you may not use this file except in compliance 10203945Sweongyo * with the License. You may obtain a copy of the License at 11203945Sweongyo * 12203945Sweongyo * http://www.apache.org/licenses/LICENSE-2.0 13203945Sweongyo * 14203945Sweongyo * Unless required by applicable law or agreed to in writing, 15203945Sweongyo * software distributed under the License is distributed on an 16203945Sweongyo * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17203945Sweongyo * KIND, either express or implied. See the License for the 18203945Sweongyo * specific language governing permissions and limitations 19203945Sweongyo * under the License. 20203945Sweongyo * ==================================================================== 21203945Sweongyo */ 22203945Sweongyo 23203945Sweongyo 24203945Sweongyo#include <stdlib.h> 25203945Sweongyo#define APR_WANT_STRFUNC 26203945Sweongyo#include <apr_want.h> 27203945Sweongyo 28203945Sweongyo#include "svn_compat.h" 29203945Sweongyo#include "svn_private_config.h" 30203945Sweongyo#include "svn_hash.h" 31203945Sweongyo#include "svn_pools.h" 32203945Sweongyo#include "svn_error.h" 33203945Sweongyo#include "svn_path.h" 34203945Sweongyo#include "svn_fs.h" 35203945Sweongyo#include "svn_repos.h" 36203945Sweongyo#include "svn_string.h" 37203945Sweongyo#include "svn_sorts.h" 38203945Sweongyo#include "svn_props.h" 39203945Sweongyo#include "svn_mergeinfo.h" 40203945Sweongyo#include "repos.h" 41203945Sweongyo#include "private/svn_fspath.h" 42203945Sweongyo#include "private/svn_mergeinfo_private.h" 43203945Sweongyo#include "private/svn_subr_private.h" 44203945Sweongyo 45203945Sweongyo 46203945Sweongyo 47203945Sweongyosvn_error_t * 48203945Sweongyosvn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level, 49203945Sweongyo svn_repos_t *repos, 50203945Sweongyo svn_revnum_t revision, 51203945Sweongyo svn_repos_authz_func_t authz_read_func, 52203945Sweongyo void *authz_read_baton, 53203945Sweongyo apr_pool_t *pool) 54203945Sweongyo{ 55203945Sweongyo svn_fs_t *fs = svn_repos_fs(repos); 56203945Sweongyo svn_fs_root_t *rev_root; 57203945Sweongyo apr_hash_t *changes; 58203945Sweongyo apr_hash_index_t *hi; 59203945Sweongyo svn_boolean_t found_readable = FALSE; 60203945Sweongyo svn_boolean_t found_unreadable = FALSE; 61203945Sweongyo apr_pool_t *subpool; 62203945Sweongyo 63203945Sweongyo /* By default, we'll grant full read access to REVISION. */ 64203945Sweongyo *access_level = svn_repos_revision_access_full; 65203945Sweongyo 66203945Sweongyo /* No auth-checking function? We're done. */ 67203945Sweongyo if (! authz_read_func) 68204922Sweongyo return SVN_NO_ERROR; 69204922Sweongyo 70204922Sweongyo /* Fetch the changes associated with REVISION. */ 71204922Sweongyo SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool)); 72204922Sweongyo SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool)); 73204922Sweongyo 74203945Sweongyo /* No changed paths? We're done. */ 75204922Sweongyo if (apr_hash_count(changes) == 0) 76203945Sweongyo return SVN_NO_ERROR; 77204922Sweongyo 78203945Sweongyo /* Otherwise, we have to check the readability of each changed 79203945Sweongyo path, or at least enough to answer the question asked. */ 80203945Sweongyo subpool = svn_pool_create(pool); 81203945Sweongyo for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 82203945Sweongyo { 83203945Sweongyo const void *key; 84203945Sweongyo void *val; 85203945Sweongyo svn_fs_path_change2_t *change; 86203945Sweongyo svn_boolean_t readable; 87203945Sweongyo 88203945Sweongyo svn_pool_clear(subpool); 89203945Sweongyo apr_hash_this(hi, &key, NULL, &val); 90203945Sweongyo change = val; 91203945Sweongyo 92203945Sweongyo SVN_ERR(authz_read_func(&readable, rev_root, key, 93203945Sweongyo authz_read_baton, subpool)); 94203945Sweongyo if (! readable) 95203945Sweongyo found_unreadable = TRUE; 96203945Sweongyo else 97203945Sweongyo found_readable = TRUE; 98203945Sweongyo 99203945Sweongyo /* If we have at least one of each (readable/unreadable), we 100203945Sweongyo have our answer. */ 101203945Sweongyo if (found_readable && found_unreadable) 102203945Sweongyo goto decision; 103203945Sweongyo 104203945Sweongyo switch (change->change_kind) 105203945Sweongyo { 106203945Sweongyo case svn_fs_path_change_add: 107203945Sweongyo case svn_fs_path_change_replace: 108203945Sweongyo { 109203945Sweongyo const char *copyfrom_path; 110203945Sweongyo svn_revnum_t copyfrom_rev; 111203945Sweongyo 112203945Sweongyo SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 113203945Sweongyo rev_root, key, subpool)); 114203945Sweongyo if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 115203945Sweongyo { 116203945Sweongyo svn_fs_root_t *copyfrom_root; 117203945Sweongyo SVN_ERR(svn_fs_revision_root(©from_root, fs, 118203945Sweongyo copyfrom_rev, subpool)); 119203945Sweongyo SVN_ERR(authz_read_func(&readable, 120203945Sweongyo copyfrom_root, copyfrom_path, 121203945Sweongyo authz_read_baton, subpool)); 122203945Sweongyo if (! readable) 123203945Sweongyo found_unreadable = TRUE; 124203945Sweongyo 125203945Sweongyo /* If we have at least one of each (readable/unreadable), we 126203945Sweongyo have our answer. */ 127203945Sweongyo if (found_readable && found_unreadable) 128203945Sweongyo goto decision; 129203945Sweongyo } 130203945Sweongyo } 131203945Sweongyo break; 132203945Sweongyo 133203945Sweongyo case svn_fs_path_change_delete: 134203945Sweongyo case svn_fs_path_change_modify: 135203945Sweongyo default: 136203945Sweongyo break; 137203945Sweongyo } 138203945Sweongyo } 139203945Sweongyo 140203945Sweongyo decision: 141203945Sweongyo svn_pool_destroy(subpool); 142203945Sweongyo 143203945Sweongyo /* Either every changed path was unreadable... */ 144203945Sweongyo if (! found_readable) 145203945Sweongyo *access_level = svn_repos_revision_access_none; 146204922Sweongyo 147203945Sweongyo /* ... or some changed path was unreadable... */ 148203945Sweongyo else if (found_unreadable) 149203945Sweongyo *access_level = svn_repos_revision_access_partial; 150203945Sweongyo 151203945Sweongyo /* ... or every changed path was readable (the default). */ 152203945Sweongyo return SVN_NO_ERROR; 153203945Sweongyo} 154203945Sweongyo 155203945Sweongyo 156203945Sweongyo/* Store as keys in CHANGED the paths of all node in ROOT that show a 157203945Sweongyo * significant change. "Significant" means that the text or 158203945Sweongyo * properties of the node were changed, or that the node was added or 159203945Sweongyo * deleted. 160203945Sweongyo * 161203945Sweongyo * The CHANGED hash set and its keys and values are allocated in POOL; 162203945Sweongyo * keys are const char * paths and values are svn_log_changed_path_t. 163203945Sweongyo * 164203945Sweongyo * To prevent changes from being processed over and over again, the 165203945Sweongyo * changed paths for ROOT may be passed in PREFETCHED_CHANGES. If the 166203945Sweongyo * latter is NULL, we will request the list inside this function. 167203945Sweongyo * 168203945Sweongyo * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 169203945Sweongyo * AUTHZ_READ_BATON and FS) to check whether each changed-path (and 170203945Sweongyo * copyfrom_path) is readable: 171203945Sweongyo * 172203945Sweongyo * - If some paths are readable and some are not, then silently 173203945Sweongyo * omit the unreadable paths from the CHANGED hash, and return 174203945Sweongyo * SVN_ERR_AUTHZ_PARTIALLY_READABLE. 175203945Sweongyo * 176203945Sweongyo * - If absolutely every changed-path (and copyfrom_path) is 177203945Sweongyo * unreadable, then return an empty CHANGED hash and 178203945Sweongyo * SVN_ERR_AUTHZ_UNREADABLE. (This is to distinguish a revision 179203945Sweongyo * which truly has no changed paths from a revision in which all 180203945Sweongyo * paths are unreadable.) 181203945Sweongyo */ 182203945Sweongyostatic svn_error_t * 183203945Sweongyodetect_changed(apr_hash_t **changed, 184203945Sweongyo svn_fs_root_t *root, 185203945Sweongyo svn_fs_t *fs, 186203945Sweongyo apr_hash_t *prefetched_changes, 187203945Sweongyo svn_repos_authz_func_t authz_read_func, 188203945Sweongyo void *authz_read_baton, 189203945Sweongyo apr_pool_t *pool) 190203945Sweongyo{ 191203945Sweongyo apr_hash_t *changes = prefetched_changes; 192203945Sweongyo apr_hash_index_t *hi; 193203945Sweongyo apr_pool_t *subpool; 194203945Sweongyo svn_boolean_t found_readable = FALSE; 195203945Sweongyo svn_boolean_t found_unreadable = FALSE; 196203945Sweongyo 197203945Sweongyo *changed = svn_hash__make(pool); 198203945Sweongyo if (changes == NULL) 199203945Sweongyo SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); 200203945Sweongyo 201203945Sweongyo if (apr_hash_count(changes) == 0) 202203945Sweongyo /* No paths changed in this revision? Uh, sure, I guess the 203203945Sweongyo revision is readable, then. */ 204203945Sweongyo return SVN_NO_ERROR; 205203945Sweongyo 206203945Sweongyo subpool = svn_pool_create(pool); 207203945Sweongyo 208203945Sweongyo for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) 209203945Sweongyo { 210203945Sweongyo /* NOTE: Much of this loop is going to look quite similar to 211203945Sweongyo svn_repos_check_revision_access(), but we have to do more things 212203945Sweongyo here, so we'll live with the duplication. */ 213203945Sweongyo const void *key; 214203945Sweongyo void *val; 215203945Sweongyo svn_fs_path_change2_t *change; 216203945Sweongyo const char *path; 217203945Sweongyo char action; 218203945Sweongyo svn_log_changed_path2_t *item; 219203945Sweongyo 220203945Sweongyo svn_pool_clear(subpool); 221203945Sweongyo 222203945Sweongyo /* KEY will be the path, VAL the change. */ 223203945Sweongyo apr_hash_this(hi, &key, NULL, &val); 224203945Sweongyo path = (const char *) key; 225203945Sweongyo change = val; 226203945Sweongyo 227203945Sweongyo /* Skip path if unreadable. */ 228203945Sweongyo if (authz_read_func) 229203945Sweongyo { 230203945Sweongyo svn_boolean_t readable; 231203945Sweongyo SVN_ERR(authz_read_func(&readable, 232203945Sweongyo root, path, 233203945Sweongyo authz_read_baton, subpool)); 234203945Sweongyo if (! readable) 235203945Sweongyo { 236203945Sweongyo found_unreadable = TRUE; 237203945Sweongyo continue; 238203945Sweongyo } 239203945Sweongyo } 240203945Sweongyo 241203945Sweongyo /* At least one changed-path was readable. */ 242203945Sweongyo found_readable = TRUE; 243203945Sweongyo 244203945Sweongyo switch (change->change_kind) 245203945Sweongyo { 246203945Sweongyo case svn_fs_path_change_reset: 247203945Sweongyo continue; 248203945Sweongyo 249203945Sweongyo case svn_fs_path_change_add: 250203945Sweongyo action = 'A'; 251203945Sweongyo break; 252203945Sweongyo 253203945Sweongyo case svn_fs_path_change_replace: 254203945Sweongyo action = 'R'; 255203945Sweongyo break; 256203945Sweongyo 257203945Sweongyo case svn_fs_path_change_delete: 258203945Sweongyo action = 'D'; 259203945Sweongyo break; 260203945Sweongyo 261203945Sweongyo case svn_fs_path_change_modify: 262203945Sweongyo default: 263203945Sweongyo action = 'M'; 264203945Sweongyo break; 265203945Sweongyo } 266203945Sweongyo 267203945Sweongyo item = svn_log_changed_path2_create(pool); 268203945Sweongyo item->action = action; 269203945Sweongyo item->node_kind = change->node_kind; 270203945Sweongyo item->copyfrom_rev = SVN_INVALID_REVNUM; 271203945Sweongyo item->text_modified = change->text_mod ? svn_tristate_true 272203945Sweongyo : svn_tristate_false; 273203945Sweongyo item->props_modified = change->prop_mod ? svn_tristate_true 274203945Sweongyo : svn_tristate_false; 275203945Sweongyo 276203945Sweongyo /* Pre-1.6 revision files don't store the change path kind, so fetch 277203945Sweongyo it manually. */ 278203945Sweongyo if (item->node_kind == svn_node_unknown) 279203945Sweongyo { 280203945Sweongyo svn_fs_root_t *check_root = root; 281203945Sweongyo const char *check_path = path; 282203945Sweongyo 283203945Sweongyo /* Deleted items don't exist so check earlier revision. We 284203945Sweongyo know the parent must exist and could be a copy */ 285203945Sweongyo if (change->change_kind == svn_fs_path_change_delete) 286203945Sweongyo { 287203945Sweongyo svn_fs_history_t *history; 288203945Sweongyo svn_revnum_t prev_rev; 289203945Sweongyo const char *parent_path, *name; 290203945Sweongyo 291203945Sweongyo svn_fspath__split(&parent_path, &name, path, subpool); 292203945Sweongyo 293203945Sweongyo SVN_ERR(svn_fs_node_history(&history, root, parent_path, 294203945Sweongyo subpool)); 295203945Sweongyo 296203945Sweongyo /* Two calls because the first call returns the original 297203945Sweongyo revision as the deleted child means it is 'interesting' */ 298203945Sweongyo SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 299203945Sweongyo SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool)); 300203945Sweongyo 301203945Sweongyo SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history, 302203945Sweongyo subpool)); 303203945Sweongyo SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool)); 304203945Sweongyo check_path = svn_fspath__join(parent_path, name, subpool); 305203945Sweongyo } 306203945Sweongyo 307203945Sweongyo SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path, 308203945Sweongyo subpool)); 309203945Sweongyo } 310203945Sweongyo 311203945Sweongyo 312203945Sweongyo if ((action == 'A') || (action == 'R')) 313203945Sweongyo { 314203945Sweongyo const char *copyfrom_path = change->copyfrom_path; 315203945Sweongyo svn_revnum_t copyfrom_rev = change->copyfrom_rev; 316203945Sweongyo 317203945Sweongyo /* the following is a potentially expensive operation since on FSFS 318203945Sweongyo we will follow the DAG from ROOT to PATH and that requires 319203945Sweongyo actually reading the directories along the way. */ 320203945Sweongyo if (!change->copyfrom_known) 321203945Sweongyo SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 322203945Sweongyo root, path, subpool)); 323203945Sweongyo 324203945Sweongyo if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 325203945Sweongyo { 326203945Sweongyo svn_boolean_t readable = TRUE; 327203945Sweongyo 328203945Sweongyo if (authz_read_func) 329203945Sweongyo { 330203945Sweongyo svn_fs_root_t *copyfrom_root; 331203945Sweongyo 332203945Sweongyo SVN_ERR(svn_fs_revision_root(©from_root, fs, 333203945Sweongyo copyfrom_rev, subpool)); 334203945Sweongyo SVN_ERR(authz_read_func(&readable, 335203945Sweongyo copyfrom_root, copyfrom_path, 336203945Sweongyo authz_read_baton, subpool)); 337203945Sweongyo if (! readable) 338203945Sweongyo found_unreadable = TRUE; 339203945Sweongyo } 340203945Sweongyo 341203945Sweongyo if (readable) 342203945Sweongyo { 343203945Sweongyo item->copyfrom_path = apr_pstrdup(pool, copyfrom_path); 344203945Sweongyo item->copyfrom_rev = copyfrom_rev; 345203945Sweongyo } 346203945Sweongyo } 347203945Sweongyo } 348203945Sweongyo svn_hash_sets(*changed, apr_pstrdup(pool, path), item); 349203945Sweongyo } 350203945Sweongyo 351203945Sweongyo svn_pool_destroy(subpool); 352203945Sweongyo 353203945Sweongyo if (! found_readable) 354203945Sweongyo /* Every changed-path was unreadable. */ 355203945Sweongyo return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, 356203945Sweongyo NULL, NULL); 357203945Sweongyo 358203945Sweongyo if (found_unreadable) 359203945Sweongyo /* At least one changed-path was unreadable. */ 360203945Sweongyo return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE, 361203945Sweongyo NULL, NULL); 362203945Sweongyo 363203945Sweongyo /* Every changed-path was readable. */ 364203945Sweongyo return SVN_NO_ERROR; 365203945Sweongyo} 366203945Sweongyo 367203945Sweongyo/* This is used by svn_repos_get_logs to keep track of multiple 368203945Sweongyo * path history information while working through history. 369203945Sweongyo * 370203945Sweongyo * The two pools are swapped after each iteration through history because 371203945Sweongyo * to get the next history requires the previous one. 372203945Sweongyo */ 373203945Sweongyostruct path_info 374203945Sweongyo{ 375203945Sweongyo svn_stringbuf_t *path; 376203945Sweongyo svn_revnum_t history_rev; 377203945Sweongyo svn_boolean_t done; 378203945Sweongyo svn_boolean_t first_time; 379203945Sweongyo 380203945Sweongyo /* If possible, we like to keep open the history object for each path, 381203945Sweongyo since it avoids needed to open and close it many times as we walk 382203945Sweongyo backwards in time. To do so we need two pools, so that we can clear 383203945Sweongyo one each time through. If we're not holding the history open for 384203945Sweongyo this path then these three pointers will be NULL. */ 385203945Sweongyo svn_fs_history_t *hist; 386203945Sweongyo apr_pool_t *newpool; 387203945Sweongyo apr_pool_t *oldpool; 388203945Sweongyo}; 389203945Sweongyo 390203945Sweongyo/* Advance to the next history for the path. 391203945Sweongyo * 392203945Sweongyo * If INFO->HIST is not NULL we do this using that existing history object, 393203945Sweongyo * otherwise we open a new one. 394203945Sweongyo * 395203945Sweongyo * If no more history is available or the history revision is less 396203945Sweongyo * (earlier) than START, or the history is not available due 397203945Sweongyo * to authorization, then INFO->DONE is set to TRUE. 398203945Sweongyo * 399203945Sweongyo * A STRICT value of FALSE will indicate to follow history across copied 400203945Sweongyo * paths. 401203945Sweongyo * 402203945Sweongyo * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with 403203945Sweongyo * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if 404203945Sweongyo * we do indeed find more history for the path. 405203945Sweongyo */ 406203945Sweongyostatic svn_error_t * 407203945Sweongyoget_history(struct path_info *info, 408203945Sweongyo svn_fs_t *fs, 409203945Sweongyo svn_boolean_t strict, 410203945Sweongyo svn_repos_authz_func_t authz_read_func, 411203945Sweongyo void *authz_read_baton, 412203945Sweongyo svn_revnum_t start, 413203945Sweongyo apr_pool_t *pool) 414203945Sweongyo{ 415203945Sweongyo svn_fs_root_t *history_root = NULL; 416203945Sweongyo svn_fs_history_t *hist; 417203945Sweongyo apr_pool_t *subpool; 418203945Sweongyo const char *path; 419203945Sweongyo 420203945Sweongyo if (info->hist) 421203945Sweongyo { 422203945Sweongyo subpool = info->newpool; 423203945Sweongyo 424203945Sweongyo SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool)); 425203945Sweongyo 426203945Sweongyo hist = info->hist; 427203945Sweongyo } 428203945Sweongyo else 429203945Sweongyo { 430203945Sweongyo subpool = svn_pool_create(pool); 431203945Sweongyo 432203945Sweongyo /* Open the history located at the last rev we were at. */ 433203945Sweongyo SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev, 434203945Sweongyo subpool)); 435203945Sweongyo 436203945Sweongyo SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data, 437203945Sweongyo subpool)); 438203945Sweongyo 439203945Sweongyo SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 440203945Sweongyo 441203945Sweongyo if (info->first_time) 442203945Sweongyo info->first_time = FALSE; 443203945Sweongyo else 444203945Sweongyo SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool)); 445203945Sweongyo } 446203945Sweongyo 447203945Sweongyo if (! hist) 448203945Sweongyo { 449203945Sweongyo svn_pool_destroy(subpool); 450203945Sweongyo if (info->oldpool) 451203945Sweongyo svn_pool_destroy(info->oldpool); 452203945Sweongyo info->done = TRUE; 453203945Sweongyo return SVN_NO_ERROR; 454203945Sweongyo } 455203945Sweongyo 456203945Sweongyo /* Fetch the location information for this history step. */ 457203945Sweongyo SVN_ERR(svn_fs_history_location(&path, &info->history_rev, 458203945Sweongyo hist, subpool)); 459203945Sweongyo 460203945Sweongyo svn_stringbuf_set(info->path, path); 461203945Sweongyo 462203945Sweongyo /* If this history item predates our START revision then 463203945Sweongyo don't fetch any more for this path. */ 464203945Sweongyo if (info->history_rev < start) 465203945Sweongyo { 466203945Sweongyo svn_pool_destroy(subpool); 467203945Sweongyo if (info->oldpool) 468203945Sweongyo svn_pool_destroy(info->oldpool); 469203945Sweongyo info->done = TRUE; 470203945Sweongyo return SVN_NO_ERROR; 471203945Sweongyo } 472203945Sweongyo 473203945Sweongyo /* Is the history item readable? If not, done with path. */ 474203945Sweongyo if (authz_read_func) 475203945Sweongyo { 476203945Sweongyo svn_boolean_t readable; 477203945Sweongyo SVN_ERR(svn_fs_revision_root(&history_root, fs, 478203945Sweongyo info->history_rev, 479203945Sweongyo subpool)); 480203945Sweongyo SVN_ERR(authz_read_func(&readable, history_root, 481203945Sweongyo info->path->data, 482203945Sweongyo authz_read_baton, 483203945Sweongyo subpool)); 484203945Sweongyo if (! readable) 485203945Sweongyo info->done = TRUE; 486203945Sweongyo } 487203945Sweongyo 488203945Sweongyo if (! info->hist) 489203945Sweongyo { 490203945Sweongyo svn_pool_destroy(subpool); 491203945Sweongyo } 492203945Sweongyo else 493203945Sweongyo { 494203945Sweongyo apr_pool_t *temppool = info->oldpool; 495203945Sweongyo info->oldpool = info->newpool; 496203945Sweongyo svn_pool_clear(temppool); 497203945Sweongyo info->newpool = temppool; 498203945Sweongyo } 499203945Sweongyo 500203945Sweongyo return SVN_NO_ERROR; 501203945Sweongyo} 502203945Sweongyo 503203945Sweongyo/* Set INFO->HIST to the next history for the path *if* there is history 504203945Sweongyo * available and INFO->HISTORY_REV is equal to or greater than CURRENT. 505203945Sweongyo * 506203945Sweongyo * *CHANGED is set to TRUE if the path has history in the CURRENT revision, 507203945Sweongyo * otherwise it is not touched. 508203945Sweongyo * 509203945Sweongyo * If we do need to get the next history revision for the path, call 510203945Sweongyo * get_history to do it -- see it for details. 511203945Sweongyo */ 512203945Sweongyostatic svn_error_t * 513203945Sweongyocheck_history(svn_boolean_t *changed, 514203945Sweongyo struct path_info *info, 515203945Sweongyo svn_fs_t *fs, 516203945Sweongyo svn_revnum_t current, 517203945Sweongyo svn_boolean_t strict, 518203945Sweongyo svn_repos_authz_func_t authz_read_func, 519203945Sweongyo void *authz_read_baton, 520204257Sweongyo svn_revnum_t start, 521204257Sweongyo apr_pool_t *pool) 522203945Sweongyo{ 523203945Sweongyo /* If we're already done with histories for this path, 524203945Sweongyo don't try to fetch any more. */ 525203945Sweongyo if (info->done) 526203945Sweongyo return SVN_NO_ERROR; 527203945Sweongyo 528203945Sweongyo /* If the last rev we got for this path is less than CURRENT, 529203945Sweongyo then just return and don't fetch history for this path. 530203945Sweongyo The caller will get to this rev eventually or else reach 531203945Sweongyo the limit. */ 532203945Sweongyo if (info->history_rev < current) 533203945Sweongyo return SVN_NO_ERROR; 534203945Sweongyo 535203945Sweongyo /* If the last rev we got for this path is equal to CURRENT 536203945Sweongyo then set *CHANGED to true and get the next history 537203945Sweongyo rev where this path was changed. */ 538203945Sweongyo *changed = TRUE; 539203945Sweongyo return get_history(info, fs, strict, authz_read_func, 540203945Sweongyo authz_read_baton, start, pool); 541203945Sweongyo} 542203945Sweongyo 543203945Sweongyo/* Return the next interesting revision in our list of HISTORIES. */ 544203945Sweongyostatic svn_revnum_t 545203945Sweongyonext_history_rev(const apr_array_header_t *histories) 546203945Sweongyo{ 547203945Sweongyo svn_revnum_t next_rev = SVN_INVALID_REVNUM; 548203945Sweongyo int i; 549203945Sweongyo 550203945Sweongyo for (i = 0; i < histories->nelts; ++i) 551203945Sweongyo { 552203945Sweongyo struct path_info *info = APR_ARRAY_IDX(histories, i, 553203945Sweongyo struct path_info *); 554203945Sweongyo if (info->done) 555203945Sweongyo continue; 556203945Sweongyo if (info->history_rev > next_rev) 557203945Sweongyo next_rev = info->history_rev; 558203945Sweongyo } 559203945Sweongyo 560203945Sweongyo return next_rev; 561203945Sweongyo} 562203945Sweongyo 563203945Sweongyo/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to 564203945Sweongyo catalogs describing how mergeinfo values on paths (which are the 565203945Sweongyo keys of those catalogs) were changed in REV. If *PREFETCHED_CAHNGES 566203945Sweongyo already contains the changed paths for REV, use that. Otherwise, 567203945Sweongyo request that data and return it in *PREFETCHED_CHANGES. */ 568203945Sweongyo/* ### TODO: This would make a *great*, useful public function, 569203945Sweongyo ### svn_repos_fs_mergeinfo_changed()! -- cmpilato */ 570203945Sweongyostatic svn_error_t * 571203945Sweongyofs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog, 572203945Sweongyo svn_mergeinfo_catalog_t *added_mergeinfo_catalog, 573203945Sweongyo apr_hash_t **prefetched_changes, 574203945Sweongyo svn_fs_t *fs, 575203945Sweongyo svn_revnum_t rev, 576203945Sweongyo apr_pool_t *result_pool, 577203945Sweongyo apr_pool_t *scratch_pool) 578203945Sweongyo 579203945Sweongyo{ 580203945Sweongyo svn_fs_root_t *root; 581203945Sweongyo apr_pool_t *iterpool; 582203945Sweongyo apr_hash_index_t *hi; 583203945Sweongyo 584203945Sweongyo /* Initialize return variables. */ 585203945Sweongyo *deleted_mergeinfo_catalog = svn_hash__make(result_pool); 586203945Sweongyo *added_mergeinfo_catalog = svn_hash__make(result_pool); 587203945Sweongyo 588203945Sweongyo /* Revision 0 has no mergeinfo and no mergeinfo changes. */ 589203945Sweongyo if (rev == 0) 590203945Sweongyo return SVN_NO_ERROR; 591203945Sweongyo 592203945Sweongyo /* We're going to use the changed-paths information for REV to 593203945Sweongyo narrow down our search. */ 594203945Sweongyo SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 595203945Sweongyo if (*prefetched_changes == NULL) 596203945Sweongyo SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool)); 597203945Sweongyo 598203945Sweongyo /* No changed paths? We're done. */ 599203945Sweongyo if (apr_hash_count(*prefetched_changes) == 0) 600203945Sweongyo return SVN_NO_ERROR; 601203945Sweongyo 602203945Sweongyo /* Loop over changes, looking for anything that might carry an 603203945Sweongyo svn:mergeinfo change and is one of our paths of interest, or a 604203945Sweongyo child or [grand]parent directory thereof. */ 605203945Sweongyo iterpool = svn_pool_create(scratch_pool); 606203945Sweongyo for (hi = apr_hash_first(scratch_pool, *prefetched_changes); 607203945Sweongyo hi; 608203945Sweongyo hi = apr_hash_next(hi)) 609203945Sweongyo { 610203945Sweongyo const void *key; 611203945Sweongyo void *val; 612203945Sweongyo svn_fs_path_change2_t *change; 613203945Sweongyo const char *changed_path, *base_path = NULL; 614203945Sweongyo svn_revnum_t base_rev = SVN_INVALID_REVNUM; 615203945Sweongyo svn_fs_root_t *base_root = NULL; 616203945Sweongyo svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value; 617203945Sweongyo 618203945Sweongyo svn_pool_clear(iterpool); 619203945Sweongyo 620203945Sweongyo /* KEY will be the path, VAL the change. */ 621203945Sweongyo apr_hash_this(hi, &key, NULL, &val); 622203945Sweongyo changed_path = key; 623203945Sweongyo change = val; 624203945Sweongyo 625203945Sweongyo /* If there was no property change on this item, ignore it. */ 626203945Sweongyo if (! change->prop_mod) 627203945Sweongyo continue; 628203945Sweongyo 629203945Sweongyo switch (change->change_kind) 630203945Sweongyo { 631203945Sweongyo 632203945Sweongyo /* ### TODO: Can the add, replace, and modify cases be joined 633203945Sweongyo ### together to all use svn_repos__prev_location()? The 634203945Sweongyo ### difference would be the fallback case (path/rev-1 for 635203945Sweongyo ### modifies, NULL otherwise). -- cmpilato */ 636203945Sweongyo 637203945Sweongyo /* If the path was added or replaced, see if it was created via 638203945Sweongyo copy. If so, that will tell us where its previous location 639203945Sweongyo was. If not, there's no previous location to examine. */ 640203945Sweongyo case svn_fs_path_change_add: 641203945Sweongyo case svn_fs_path_change_replace: 642203945Sweongyo { 643203945Sweongyo const char *copyfrom_path; 644203945Sweongyo svn_revnum_t copyfrom_rev; 645203945Sweongyo 646203945Sweongyo SVN_ERR(svn_fs_copied_from(©from_rev, ©from_path, 647203945Sweongyo root, changed_path, iterpool)); 648203945Sweongyo if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)) 649203945Sweongyo { 650203945Sweongyo base_path = apr_pstrdup(scratch_pool, copyfrom_path); 651203945Sweongyo base_rev = copyfrom_rev; 652203945Sweongyo } 653203945Sweongyo break; 654203945Sweongyo } 655203945Sweongyo 656203945Sweongyo /* If the path was merely modified, see if its previous 657203945Sweongyo location was affected by a copy which happened in this 658203945Sweongyo revision before assuming it holds the same path it did the 659203945Sweongyo previous revision. */ 660203945Sweongyo case svn_fs_path_change_modify: 661203945Sweongyo { 662203945Sweongyo svn_revnum_t appeared_rev; 663203945Sweongyo 664203945Sweongyo SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path, 665203945Sweongyo &base_rev, fs, rev, 666203945Sweongyo changed_path, iterpool)); 667203945Sweongyo 668203945Sweongyo /* If this path isn't the result of a copy that occurred 669203945Sweongyo in this revision, we can find the previous version of 670203945Sweongyo it in REV - 1 at the same path. */ 671203945Sweongyo if (! (base_path && SVN_IS_VALID_REVNUM(base_rev) 672203945Sweongyo && (appeared_rev == rev))) 673203945Sweongyo { 674203945Sweongyo base_path = changed_path; 675203945Sweongyo base_rev = rev - 1; 676203945Sweongyo } 677203945Sweongyo break; 678203945Sweongyo } 679203945Sweongyo 680203945Sweongyo /* We don't care about any of the other cases. */ 681203945Sweongyo case svn_fs_path_change_delete: 682203945Sweongyo case svn_fs_path_change_reset: 683203945Sweongyo default: 684203945Sweongyo continue; 685203945Sweongyo } 686203945Sweongyo 687203945Sweongyo /* If there was a base location, fetch its mergeinfo property value. */ 688203945Sweongyo if (base_path && SVN_IS_VALID_REVNUM(base_rev)) 689203945Sweongyo { 690203945Sweongyo SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool)); 691203945Sweongyo SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path, 692203945Sweongyo SVN_PROP_MERGEINFO, iterpool)); 693203945Sweongyo } 694203945Sweongyo 695203945Sweongyo /* Now fetch the current (as of REV) mergeinfo property value. */ 696203945Sweongyo SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path, 697203945Sweongyo SVN_PROP_MERGEINFO, iterpool)); 698203945Sweongyo 699203945Sweongyo /* No mergeinfo on either the new or previous location? Just 700203945Sweongyo skip it. (If there *was* a change, it would have been in 701203945Sweongyo inherited mergeinfo only, which should be picked up by the 702203945Sweongyo iteration of this loop that finds the parent paths that 703203945Sweongyo really got changed.) */ 704203945Sweongyo if (! (mergeinfo_value || prev_mergeinfo_value)) 705203945Sweongyo continue; 706203945Sweongyo 707203945Sweongyo /* If mergeinfo was explicitly added or removed on this path, we 708203945Sweongyo need to check to see if that was a real semantic change of 709203945Sweongyo meaning. So, fill in the "missing" mergeinfo value with the 710203945Sweongyo inherited mergeinfo for that path/revision. */ 711203945Sweongyo if (prev_mergeinfo_value && (! mergeinfo_value)) 712203945Sweongyo { 713203945Sweongyo apr_array_header_t *query_paths = 714203945Sweongyo apr_array_make(iterpool, 1, sizeof(const char *)); 715203945Sweongyo svn_mergeinfo_t tmp_mergeinfo; 716203945Sweongyo svn_mergeinfo_catalog_t tmp_catalog; 717228399Seadler 718203945Sweongyo APR_ARRAY_PUSH(query_paths, const char *) = changed_path; 719203945Sweongyo SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, 720203945Sweongyo query_paths, svn_mergeinfo_inherited, 721203945Sweongyo FALSE, TRUE, iterpool, iterpool)); 722203945Sweongyo tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path); 723203945Sweongyo if (tmp_mergeinfo) 724203945Sweongyo SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value, 725203945Sweongyo tmp_mergeinfo, 726203945Sweongyo iterpool)); 727203945Sweongyo } 728203945Sweongyo else if (mergeinfo_value && (! prev_mergeinfo_value) 729203945Sweongyo && base_path && SVN_IS_VALID_REVNUM(base_rev)) 730203945Sweongyo { 731203945Sweongyo apr_array_header_t *query_paths = 732203945Sweongyo apr_array_make(iterpool, 1, sizeof(const char *)); 733203945Sweongyo svn_mergeinfo_t tmp_mergeinfo; 734203945Sweongyo svn_mergeinfo_catalog_t tmp_catalog; 735203945Sweongyo 736203945Sweongyo APR_ARRAY_PUSH(query_paths, const char *) = base_path; 737203945Sweongyo SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root, 738203945Sweongyo query_paths, svn_mergeinfo_inherited, 739203945Sweongyo FALSE, TRUE, iterpool, iterpool)); 740203945Sweongyo tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path); 741203945Sweongyo if (tmp_mergeinfo) 742203945Sweongyo SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value, 743203945Sweongyo tmp_mergeinfo, 744203945Sweongyo iterpool)); 745203945Sweongyo } 746203945Sweongyo 747203945Sweongyo /* If the old and new mergeinfo differ in any way, store the 748203945Sweongyo before and after mergeinfo values in our return hashes. */ 749203945Sweongyo if ((prev_mergeinfo_value && (! mergeinfo_value)) 750203945Sweongyo || ((! prev_mergeinfo_value) && mergeinfo_value) 751203945Sweongyo || (prev_mergeinfo_value && mergeinfo_value 752203945Sweongyo && (! svn_string_compare(mergeinfo_value, 753203945Sweongyo prev_mergeinfo_value)))) 754203945Sweongyo { 755203945Sweongyo svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL; 756203945Sweongyo svn_mergeinfo_t deleted, added; 757203945Sweongyo const char *hash_path; 758203945Sweongyo 759203945Sweongyo if (mergeinfo_value) 760203945Sweongyo SVN_ERR(svn_mergeinfo_parse(&mergeinfo, 761203945Sweongyo mergeinfo_value->data, iterpool)); 762203945Sweongyo if (prev_mergeinfo_value) 763203945Sweongyo SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo, 764203945Sweongyo prev_mergeinfo_value->data, iterpool)); 765203945Sweongyo SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 766203945Sweongyo mergeinfo, FALSE, result_pool, 767203945Sweongyo iterpool)); 768203945Sweongyo 769203945Sweongyo /* Toss interesting stuff into our return catalogs. */ 770203945Sweongyo hash_path = apr_pstrdup(result_pool, changed_path); 771203945Sweongyo svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted); 772203945Sweongyo svn_hash_sets(*added_mergeinfo_catalog, hash_path, added); 773203945Sweongyo } 774203945Sweongyo } 775203945Sweongyo 776203945Sweongyo svn_pool_destroy(iterpool); 777203945Sweongyo return SVN_NO_ERROR; 778203945Sweongyo} 779203945Sweongyo 780203945Sweongyo 781203945Sweongyo/* Determine what (if any) mergeinfo for PATHS was modified in 782203945Sweongyo revision REV, returning the differences for added mergeinfo in 783203945Sweongyo *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. 784203945Sweongyo If *PREFETCHED_CAHNGES already contains the changed paths for 785203945Sweongyo REV, use that. Otherwise, request that data and return it in 786203945Sweongyo *PREFETCHED_CHANGES. 787203945Sweongyo Use POOL for all allocations. */ 788203945Sweongyostatic svn_error_t * 789203945Sweongyoget_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo, 790203945Sweongyo svn_mergeinfo_t *deleted_mergeinfo, 791203945Sweongyo apr_hash_t **prefetched_changes, 792203945Sweongyo svn_fs_t *fs, 793203945Sweongyo const apr_array_header_t *paths, 794203945Sweongyo svn_revnum_t rev, 795203945Sweongyo apr_pool_t *result_pool, 796203945Sweongyo apr_pool_t *scratch_pool) 797203945Sweongyo{ 798203945Sweongyo svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog; 799203945Sweongyo apr_hash_index_t *hi; 800203945Sweongyo svn_fs_root_t *root; 801203945Sweongyo apr_pool_t *iterpool; 802203945Sweongyo int i; 803203945Sweongyo svn_error_t *err; 804203945Sweongyo 805203945Sweongyo /* Initialize return value. */ 806203945Sweongyo *added_mergeinfo = svn_hash__make(result_pool); 807203945Sweongyo *deleted_mergeinfo = svn_hash__make(result_pool); 808203945Sweongyo 809203945Sweongyo /* If we're asking about revision 0, there's no mergeinfo to be found. */ 810203945Sweongyo if (rev == 0) 811203945Sweongyo return SVN_NO_ERROR; 812203945Sweongyo 813203945Sweongyo /* No paths? No mergeinfo. */ 814203945Sweongyo if (! paths->nelts) 815203945Sweongyo return SVN_NO_ERROR; 816203945Sweongyo 817203945Sweongyo /* Create a work subpool and get a root for REV. */ 818203945Sweongyo SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool)); 819203945Sweongyo 820203945Sweongyo /* Fetch the mergeinfo changes for REV. */ 821203945Sweongyo err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog, 822203945Sweongyo &added_mergeinfo_catalog, 823203945Sweongyo prefetched_changes, 824203945Sweongyo fs, rev, scratch_pool, scratch_pool); 825203945Sweongyo if (err) 826203945Sweongyo { 827203945Sweongyo if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 828203945Sweongyo { 829203945Sweongyo /* Issue #3896: If invalid mergeinfo is encountered the 830203945Sweongyo best we can do is ignore it and act as if there were 831203945Sweongyo no mergeinfo modifications. */ 832203945Sweongyo svn_error_clear(err); 833203945Sweongyo return SVN_NO_ERROR; 834203945Sweongyo } 835203945Sweongyo else 836203945Sweongyo { 837203945Sweongyo return svn_error_trace(err); 838203945Sweongyo } 839203945Sweongyo } 840203945Sweongyo 841203945Sweongyo /* In most revisions, there will be no mergeinfo change at all. */ 842203945Sweongyo if ( apr_hash_count(deleted_mergeinfo_catalog) == 0 843203945Sweongyo && apr_hash_count(added_mergeinfo_catalog) == 0) 844203945Sweongyo return SVN_NO_ERROR; 845203945Sweongyo 846203945Sweongyo /* Check our PATHS for any changes to their inherited mergeinfo. 847203945Sweongyo (We deal with changes to mergeinfo directly *on* the paths in the 848203945Sweongyo following loop.) */ 849203945Sweongyo iterpool = svn_pool_create(scratch_pool); 850203945Sweongyo for (i = 0; i < paths->nelts; i++) 851203945Sweongyo { 852203945Sweongyo const char *path = APR_ARRAY_IDX(paths, i, const char *); 853203945Sweongyo const char *prev_path; 854203945Sweongyo apr_ssize_t klen; 855203945Sweongyo svn_revnum_t appeared_rev, prev_rev; 856203945Sweongyo svn_fs_root_t *prev_root; 857203945Sweongyo svn_mergeinfo_catalog_t catalog, inherited_catalog; 858203945Sweongyo svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added, 859203945Sweongyo prev_inherited_mergeinfo, inherited_mergeinfo; 860203945Sweongyo apr_array_header_t *query_paths; 861203945Sweongyo 862203945Sweongyo svn_pool_clear(iterpool); 863203945Sweongyo 864203945Sweongyo /* If this path is represented in the changed-mergeinfo hashes, 865203945Sweongyo we'll deal with it in the loop below. */ 866203945Sweongyo if (svn_hash_gets(deleted_mergeinfo_catalog, path)) 867203945Sweongyo continue; 868203945Sweongyo 869203945Sweongyo /* Figure out what path/rev to compare against. Ignore 870203945Sweongyo not-found errors returned by the filesystem. */ 871203945Sweongyo err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 872203945Sweongyo fs, rev, path, iterpool); 873203945Sweongyo if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 874203945Sweongyo err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)) 875203945Sweongyo { 876203945Sweongyo svn_error_clear(err); 877203945Sweongyo err = SVN_NO_ERROR; 878203945Sweongyo continue; 879203945Sweongyo } 880203945Sweongyo SVN_ERR(err); 881203945Sweongyo 882203945Sweongyo /* If this path isn't the result of a copy that occurred in this 883203945Sweongyo revision, we can find the previous version of it in REV - 1 884203945Sweongyo at the same path. */ 885203945Sweongyo if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev) 886203945Sweongyo && (appeared_rev == rev))) 887203945Sweongyo { 888203945Sweongyo prev_path = path; 889203945Sweongyo prev_rev = rev - 1; 890203945Sweongyo } 891203945Sweongyo 892203945Sweongyo /* Fetch the previous mergeinfo (including inherited stuff) for 893203945Sweongyo this path. Ignore not-found errors returned by the 894203945Sweongyo filesystem or invalid mergeinfo (Issue #3896).*/ 895203945Sweongyo SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool)); 896203945Sweongyo query_paths = apr_array_make(iterpool, 1, sizeof(const char *)); 897203945Sweongyo APR_ARRAY_PUSH(query_paths, const char *) = prev_path; 898203945Sweongyo err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths, 899203945Sweongyo svn_mergeinfo_inherited, FALSE, TRUE, 900203945Sweongyo iterpool, iterpool); 901203945Sweongyo if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 902203945Sweongyo err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 903203945Sweongyo err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 904203945Sweongyo { 905203945Sweongyo svn_error_clear(err); 906203945Sweongyo err = SVN_NO_ERROR; 907203945Sweongyo continue; 908203945Sweongyo } 909203945Sweongyo SVN_ERR(err); 910203945Sweongyo 911203945Sweongyo /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due 912203945Sweongyo to move as a merge': A copy where the source and destination inherit 913203945Sweongyo mergeinfo from the same parent means the inherited mergeinfo of the 914203945Sweongyo source and destination will differ, but this diffrence is not 915203945Sweongyo indicative of a merge unless the mergeinfo on the inherited parent 916203945Sweongyo has actually changed. 917203945Sweongyo 918203945Sweongyo To check for this we must fetch the "raw" previous inherited 919203945Sweongyo mergeinfo and the "raw" mergeinfo @REV then compare these. */ 920203945Sweongyo SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths, 921203945Sweongyo svn_mergeinfo_nearest_ancestor, FALSE, 922203945Sweongyo FALSE, /* adjust_inherited_mergeinfo */ 923203945Sweongyo iterpool, iterpool)); 924203945Sweongyo 925203945Sweongyo klen = strlen(prev_path); 926203945Sweongyo prev_mergeinfo = apr_hash_get(catalog, prev_path, klen); 927203945Sweongyo prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen); 928203945Sweongyo 929203945Sweongyo /* Fetch the current mergeinfo (as of REV, and including 930203945Sweongyo inherited stuff) for this path. */ 931203945Sweongyo APR_ARRAY_IDX(query_paths, 0, const char *) = path; 932203945Sweongyo SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths, 933203945Sweongyo svn_mergeinfo_inherited, FALSE, TRUE, 934203945Sweongyo iterpool, iterpool)); 935203945Sweongyo 936203945Sweongyo /* Issue #4022 again, fetch the raw inherited mergeinfo. */ 937203945Sweongyo SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths, 938203945Sweongyo svn_mergeinfo_nearest_ancestor, FALSE, 939203945Sweongyo FALSE, /* adjust_inherited_mergeinfo */ 940203945Sweongyo iterpool, iterpool)); 941203945Sweongyo 942203945Sweongyo klen = strlen(path); 943203945Sweongyo mergeinfo = apr_hash_get(catalog, path, klen); 944203945Sweongyo inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen); 945203945Sweongyo 946203945Sweongyo if (!prev_mergeinfo && !mergeinfo) 947203945Sweongyo continue; 948203945Sweongyo 949203945Sweongyo /* Last bit of issue #4022 checking. */ 950203945Sweongyo if (prev_inherited_mergeinfo && inherited_mergeinfo) 951203945Sweongyo { 952203945Sweongyo svn_boolean_t inherits_same_mergeinfo; 953 954 SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo, 955 prev_inherited_mergeinfo, 956 inherited_mergeinfo, 957 TRUE, iterpool)); 958 /* If a copy rather than an actual merge brought about an 959 inherited mergeinfo change then we are finished. */ 960 if (inherits_same_mergeinfo) 961 continue; 962 } 963 else 964 { 965 svn_boolean_t same_mergeinfo; 966 SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo, 967 prev_inherited_mergeinfo, 968 FALSE, 969 TRUE, iterpool)); 970 if (same_mergeinfo) 971 continue; 972 } 973 974 /* Compare, constrast, and combine the results. */ 975 SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo, 976 mergeinfo, FALSE, result_pool, iterpool)); 977 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted, 978 result_pool, iterpool)); 979 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added, 980 result_pool, iterpool)); 981 } 982 983 /* Merge all the mergeinfos which are, or are children of, one of 984 our paths of interest into one giant delta mergeinfo. */ 985 for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog); 986 hi; hi = apr_hash_next(hi)) 987 { 988 const void *key; 989 apr_ssize_t klen; 990 void *val; 991 const char *changed_path; 992 svn_mergeinfo_t added, deleted; 993 994 /* The path is the key, the mergeinfo delta is the value. */ 995 apr_hash_this(hi, &key, &klen, &val); 996 changed_path = key; 997 added = val; 998 999 for (i = 0; i < paths->nelts; i++) 1000 { 1001 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1002 if (! svn_fspath__skip_ancestor(path, changed_path)) 1003 continue; 1004 svn_pool_clear(iterpool); 1005 deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen); 1006 SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, 1007 svn_mergeinfo_dup(deleted, result_pool), 1008 result_pool, iterpool)); 1009 SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, 1010 svn_mergeinfo_dup(added, result_pool), 1011 result_pool, iterpool)); 1012 1013 break; 1014 } 1015 } 1016 1017 svn_pool_destroy(iterpool); 1018 return SVN_NO_ERROR; 1019} 1020 1021 1022/* Fill LOG_ENTRY with history information in FS at REV. */ 1023static svn_error_t * 1024fill_log_entry(svn_log_entry_t *log_entry, 1025 svn_revnum_t rev, 1026 svn_fs_t *fs, 1027 apr_hash_t *prefetched_changes, 1028 svn_boolean_t discover_changed_paths, 1029 const apr_array_header_t *revprops, 1030 svn_repos_authz_func_t authz_read_func, 1031 void *authz_read_baton, 1032 apr_pool_t *pool) 1033{ 1034 apr_hash_t *r_props, *changed_paths = NULL; 1035 svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE; 1036 1037 /* Discover changed paths if the user requested them 1038 or if we need to check that they are readable. */ 1039 if ((rev > 0) 1040 && (authz_read_func || discover_changed_paths)) 1041 { 1042 svn_fs_root_t *newroot; 1043 svn_error_t *patherr; 1044 1045 SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool)); 1046 patherr = detect_changed(&changed_paths, 1047 newroot, fs, prefetched_changes, 1048 authz_read_func, authz_read_baton, 1049 pool); 1050 1051 if (patherr 1052 && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE) 1053 { 1054 /* All changed-paths are unreadable, so clear all fields. */ 1055 svn_error_clear(patherr); 1056 changed_paths = NULL; 1057 get_revprops = FALSE; 1058 } 1059 else if (patherr 1060 && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE) 1061 { 1062 /* At least one changed-path was unreadable, so censor all 1063 but author and date. (The unreadable paths are already 1064 missing from the hash.) */ 1065 svn_error_clear(patherr); 1066 censor_revprops = TRUE; 1067 } 1068 else if (patherr) 1069 return patherr; 1070 1071 /* It may be the case that an authz func was passed in, but 1072 the user still doesn't want to see any changed-paths. */ 1073 if (! discover_changed_paths) 1074 changed_paths = NULL; 1075 } 1076 1077 if (get_revprops) 1078 { 1079 /* User is allowed to see at least some revprops. */ 1080 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool)); 1081 if (revprops == NULL) 1082 { 1083 /* Requested all revprops... */ 1084 if (censor_revprops) 1085 { 1086 /* ... but we can only return author/date. */ 1087 log_entry->revprops = svn_hash__make(pool); 1088 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, 1089 svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR)); 1090 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, 1091 svn_hash_gets(r_props, SVN_PROP_REVISION_DATE)); 1092 } 1093 else 1094 /* ... so return all we got. */ 1095 log_entry->revprops = r_props; 1096 } 1097 else 1098 { 1099 /* Requested only some revprops... */ 1100 int i; 1101 for (i = 0; i < revprops->nelts; i++) 1102 { 1103 char *name = APR_ARRAY_IDX(revprops, i, char *); 1104 svn_string_t *value = svn_hash_gets(r_props, name); 1105 if (censor_revprops 1106 && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0 1107 || strcmp(name, SVN_PROP_REVISION_DATE) == 0)) 1108 /* ... but we can only return author/date. */ 1109 continue; 1110 if (log_entry->revprops == NULL) 1111 log_entry->revprops = svn_hash__make(pool); 1112 svn_hash_sets(log_entry->revprops, name, value); 1113 } 1114 } 1115 } 1116 1117 log_entry->changed_paths = changed_paths; 1118 log_entry->changed_paths2 = changed_paths; 1119 log_entry->revision = rev; 1120 1121 return SVN_NO_ERROR; 1122} 1123 1124/* Send a log message for REV to RECEIVER with its RECEIVER_BATON. 1125 1126 FS is used with REV to fetch the interesting history information, 1127 such as changed paths, revprops, etc. 1128 1129 The detect_changed function is used if either AUTHZ_READ_FUNC is 1130 not NULL, or if DISCOVER_CHANGED_PATHS is TRUE. See it for details. 1131 1132 If DESCENDING_ORDER is true, send child messages in descending order. 1133 1134 If REVPROPS is NULL, retrieve all revision properties; else, retrieve 1135 only the revision properties named by the (const char *) array elements 1136 (i.e. retrieve none if the array is empty). 1137 1138 LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and 1139 NESTED_MERGES are as per the arguments of the same name to DO_LOGS. If 1140 HANDLING_MERGED_REVISION is true and *all* changed paths within REV are 1141 already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send 1142 the log message for REV. If SUBTRACTIVE_MERGE is true, then REV was 1143 reverse merged. 1144 1145 If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES. Otherwise 1146 if NESTED_MERGES is not NULL and REV is contained in it, then don't send 1147 the log for REV, otherwise send it normally and add REV to 1148 NESTED_MERGES. */ 1149static svn_error_t * 1150send_log(svn_revnum_t rev, 1151 svn_fs_t *fs, 1152 apr_hash_t *prefetched_changes, 1153 svn_mergeinfo_t log_target_history_as_mergeinfo, 1154 apr_hash_t *nested_merges, 1155 svn_boolean_t discover_changed_paths, 1156 svn_boolean_t subtractive_merge, 1157 svn_boolean_t handling_merged_revision, 1158 const apr_array_header_t *revprops, 1159 svn_boolean_t has_children, 1160 svn_log_entry_receiver_t receiver, 1161 void *receiver_baton, 1162 svn_repos_authz_func_t authz_read_func, 1163 void *authz_read_baton, 1164 apr_pool_t *pool) 1165{ 1166 svn_log_entry_t *log_entry; 1167 /* Assume we want to send the log for REV. */ 1168 svn_boolean_t found_rev_of_interest = TRUE; 1169 1170 log_entry = svn_log_entry_create(pool); 1171 SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes, 1172 discover_changed_paths || handling_merged_revision, 1173 revprops, authz_read_func, authz_read_baton, 1174 pool)); 1175 log_entry->has_children = has_children; 1176 log_entry->subtractive_merge = subtractive_merge; 1177 1178 /* Is REV a merged revision that is already part of 1179 LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no 1180 need to send it, since it already was (or will be) sent. */ 1181 if (handling_merged_revision 1182 && log_entry->changed_paths2 1183 && log_target_history_as_mergeinfo 1184 && apr_hash_count(log_target_history_as_mergeinfo)) 1185 { 1186 apr_hash_index_t *hi; 1187 apr_pool_t *subpool = svn_pool_create(pool); 1188 1189 /* REV was merged in, but it might already be part of the log target's 1190 natural history, so change our starting assumption. */ 1191 found_rev_of_interest = FALSE; 1192 1193 /* Look at each changed path in REV. */ 1194 for (hi = apr_hash_first(subpool, log_entry->changed_paths2); 1195 hi; 1196 hi = apr_hash_next(hi)) 1197 { 1198 svn_boolean_t path_is_in_history = FALSE; 1199 const char *changed_path = svn__apr_hash_index_key(hi); 1200 apr_hash_index_t *hi2; 1201 apr_pool_t *inner_subpool = svn_pool_create(subpool); 1202 1203 /* Look at each path on the log target's mergeinfo. */ 1204 for (hi2 = apr_hash_first(inner_subpool, 1205 log_target_history_as_mergeinfo); 1206 hi2; 1207 hi2 = apr_hash_next(hi2)) 1208 { 1209 const char *mergeinfo_path = 1210 svn__apr_hash_index_key(hi2); 1211 svn_rangelist_t *rangelist = 1212 svn__apr_hash_index_val(hi2); 1213 1214 /* Check whether CHANGED_PATH at revision REV is a child of 1215 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */ 1216 if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path)) 1217 { 1218 int i; 1219 1220 for (i = 0; i < rangelist->nelts; i++) 1221 { 1222 svn_merge_range_t *range = 1223 APR_ARRAY_IDX(rangelist, i, 1224 svn_merge_range_t *); 1225 if (rev > range->start && rev <= range->end) 1226 { 1227 path_is_in_history = TRUE; 1228 break; 1229 } 1230 } 1231 } 1232 if (path_is_in_history) 1233 break; 1234 } 1235 svn_pool_destroy(inner_subpool); 1236 1237 if (!path_is_in_history) 1238 { 1239 /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of 1240 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the 1241 log for REV. */ 1242 found_rev_of_interest = TRUE; 1243 break; 1244 } 1245 } 1246 svn_pool_destroy(subpool); 1247 } 1248 1249 /* If we only got changed paths the sake of detecting redundant merged 1250 revisions, then be sure we don't send that info to the receiver. */ 1251 if (!discover_changed_paths && handling_merged_revision) 1252 log_entry->changed_paths = log_entry->changed_paths2 = NULL; 1253 1254 /* Send the entry to the receiver, unless it is a redundant merged 1255 revision. */ 1256 if (found_rev_of_interest) 1257 { 1258 /* Is REV a merged revision we've already sent? */ 1259 if (nested_merges && handling_merged_revision) 1260 { 1261 svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev, 1262 sizeof(svn_revnum_t *)); 1263 1264 if (merged_rev) 1265 { 1266 /* We already sent REV. */ 1267 return SVN_NO_ERROR; 1268 } 1269 else 1270 { 1271 /* NESTED_REVS needs to last across all the send_log, do_logs, 1272 handle_merged_revisions() recursions, so use the pool it 1273 was created in at the top of the recursion. */ 1274 apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges); 1275 svn_revnum_t *long_lived_rev = apr_palloc(hash_pool, 1276 sizeof(svn_revnum_t)); 1277 *long_lived_rev = rev; 1278 apr_hash_set(nested_merges, long_lived_rev, 1279 sizeof(svn_revnum_t *), long_lived_rev); 1280 } 1281 } 1282 1283 return (*receiver)(receiver_baton, log_entry, pool); 1284 } 1285 else 1286 { 1287 return SVN_NO_ERROR; 1288 } 1289} 1290 1291/* This controls how many history objects we keep open. For any targets 1292 over this number we have to open and close their histories as needed, 1293 which is CPU intensive, but keeps us from using an unbounded amount of 1294 memory. */ 1295#define MAX_OPEN_HISTORIES 32 1296 1297/* Get the histories for PATHS, and store them in *HISTORIES. 1298 1299 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1300 repository locations as fatal -- just ignore them. */ 1301static svn_error_t * 1302get_path_histories(apr_array_header_t **histories, 1303 svn_fs_t *fs, 1304 const apr_array_header_t *paths, 1305 svn_revnum_t hist_start, 1306 svn_revnum_t hist_end, 1307 svn_boolean_t strict_node_history, 1308 svn_boolean_t ignore_missing_locations, 1309 svn_repos_authz_func_t authz_read_func, 1310 void *authz_read_baton, 1311 apr_pool_t *pool) 1312{ 1313 svn_fs_root_t *root; 1314 apr_pool_t *iterpool; 1315 svn_error_t *err; 1316 int i; 1317 1318 /* Create a history object for each path so we can walk through 1319 them all at the same time until we have all changes or LIMIT 1320 is reached. 1321 1322 There is some pool fun going on due to the fact that we have 1323 to hold on to the old pool with the history before we can 1324 get the next history. 1325 */ 1326 *histories = apr_array_make(pool, paths->nelts, 1327 sizeof(struct path_info *)); 1328 1329 SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool)); 1330 1331 iterpool = svn_pool_create(pool); 1332 for (i = 0; i < paths->nelts; i++) 1333 { 1334 const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 1335 struct path_info *info = apr_palloc(pool, 1336 sizeof(struct path_info)); 1337 1338 if (authz_read_func) 1339 { 1340 svn_boolean_t readable; 1341 1342 svn_pool_clear(iterpool); 1343 1344 SVN_ERR(authz_read_func(&readable, root, this_path, 1345 authz_read_baton, iterpool)); 1346 if (! readable) 1347 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 1348 } 1349 1350 info->path = svn_stringbuf_create(this_path, pool); 1351 info->done = FALSE; 1352 info->history_rev = hist_end; 1353 info->first_time = TRUE; 1354 1355 if (i < MAX_OPEN_HISTORIES) 1356 { 1357 err = svn_fs_node_history(&info->hist, root, this_path, pool); 1358 if (err 1359 && ignore_missing_locations 1360 && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1361 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1362 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1363 { 1364 svn_error_clear(err); 1365 continue; 1366 } 1367 SVN_ERR(err); 1368 info->newpool = svn_pool_create(pool); 1369 info->oldpool = svn_pool_create(pool); 1370 } 1371 else 1372 { 1373 info->hist = NULL; 1374 info->oldpool = NULL; 1375 info->newpool = NULL; 1376 } 1377 1378 err = get_history(info, fs, 1379 strict_node_history, 1380 authz_read_func, authz_read_baton, 1381 hist_start, pool); 1382 if (err 1383 && ignore_missing_locations 1384 && (err->apr_err == SVN_ERR_FS_NOT_FOUND || 1385 err->apr_err == SVN_ERR_FS_NOT_DIRECTORY || 1386 err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)) 1387 { 1388 svn_error_clear(err); 1389 continue; 1390 } 1391 SVN_ERR(err); 1392 APR_ARRAY_PUSH(*histories, struct path_info *) = info; 1393 } 1394 svn_pool_destroy(iterpool); 1395 1396 return SVN_NO_ERROR; 1397} 1398 1399/* Remove and return the first item from ARR. */ 1400static void * 1401array_pop_front(apr_array_header_t *arr) 1402{ 1403 void *item = arr->elts; 1404 1405 if (apr_is_empty_array(arr)) 1406 return NULL; 1407 1408 arr->elts += arr->elt_size; 1409 arr->nelts -= 1; 1410 arr->nalloc -= 1; 1411 return item; 1412} 1413 1414/* A struct which represents a single revision range, and the paths which 1415 have mergeinfo in that range. */ 1416struct path_list_range 1417{ 1418 apr_array_header_t *paths; 1419 svn_merge_range_t range; 1420 1421 /* Is RANGE the result of a reverse merge? */ 1422 svn_boolean_t reverse_merge; 1423}; 1424 1425/* A struct which represents "inverse mergeinfo", that is, instead of having 1426 a path->revision_range_list mapping, which is the way mergeinfo is commonly 1427 represented, this struct enables a revision_range_list,path tuple, where 1428 the paths can be accessed by revision. */ 1429struct rangelist_path 1430{ 1431 svn_rangelist_t *rangelist; 1432 const char *path; 1433}; 1434 1435/* Comparator function for combine_mergeinfo_path_lists(). Sorts 1436 rangelist_path structs in increasing order based upon starting revision, 1437 then ending revision of the first element in the rangelist. 1438 1439 This does not sort rangelists based upon subsequent elements, only the 1440 first range. We'll sort any subsequent ranges in the correct order 1441 when they get bumped up to the front by removal of earlier ones, so we 1442 don't really have to sort them here. See combine_mergeinfo_path_lists() 1443 for details. */ 1444static int 1445compare_rangelist_paths(const void *a, const void *b) 1446{ 1447 struct rangelist_path *rpa = *((struct rangelist_path *const *) a); 1448 struct rangelist_path *rpb = *((struct rangelist_path *const *) b); 1449 svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0, 1450 svn_merge_range_t *); 1451 svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0, 1452 svn_merge_range_t *); 1453 1454 if (mra->start < mrb->start) 1455 return -1; 1456 if (mra->start > mrb->start) 1457 return 1; 1458 if (mra->end < mrb->end) 1459 return -1; 1460 if (mra->end > mrb->end) 1461 return 1; 1462 1463 return 0; 1464} 1465 1466/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of 1467 'struct path_list_range's. This list represents the rangelists in 1468 MERGEINFO and each path which has mergeinfo in that range. 1469 If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed 1470 as the result of a reverse merge. */ 1471static svn_error_t * 1472combine_mergeinfo_path_lists(apr_array_header_t **combined_list, 1473 svn_mergeinfo_t mergeinfo, 1474 svn_boolean_t reverse_merge, 1475 apr_pool_t *pool) 1476{ 1477 apr_hash_index_t *hi; 1478 apr_array_header_t *rangelist_paths; 1479 apr_pool_t *subpool = svn_pool_create(pool); 1480 1481 /* Create a list of (revision range, path) tuples from MERGEINFO. */ 1482 rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo), 1483 sizeof(struct rangelist_path *)); 1484 for (hi = apr_hash_first(subpool, mergeinfo); hi; 1485 hi = apr_hash_next(hi)) 1486 { 1487 int i; 1488 struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp)); 1489 apr_hash_this(hi, (void *) &rp->path, NULL, 1490 (void *) &rp->rangelist); 1491 APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp; 1492 1493 /* We need to make local copies of the rangelist, since we will be 1494 modifying it, below. */ 1495 rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool); 1496 1497 /* Make all of the rangelists inclusive, both start and end. */ 1498 for (i = 0; i < rp->rangelist->nelts; i++) 1499 APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1; 1500 } 1501 1502 /* Loop over the (revision range, path) tuples, chopping them into 1503 (revision range, paths) tuples, and appending those to the output 1504 list. */ 1505 if (! *combined_list) 1506 *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *)); 1507 1508 while (rangelist_paths->nelts > 1) 1509 { 1510 svn_revnum_t youngest, next_youngest, tail, youngest_end; 1511 struct path_list_range *plr; 1512 struct rangelist_path *rp; 1513 int num_revs; 1514 int i; 1515 1516 /* First, sort the list such that the start revision of the first 1517 revision arrays are sorted. */ 1518 qsort(rangelist_paths->elts, rangelist_paths->nelts, 1519 rangelist_paths->elt_size, compare_rangelist_paths); 1520 1521 /* Next, find the number of revision ranges which start with the same 1522 revision. */ 1523 rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1524 youngest = 1525 APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start; 1526 next_youngest = youngest; 1527 for (num_revs = 1; next_youngest == youngest; num_revs++) 1528 { 1529 if (num_revs == rangelist_paths->nelts) 1530 { 1531 num_revs += 1; 1532 break; 1533 } 1534 rp = APR_ARRAY_IDX(rangelist_paths, num_revs, 1535 struct rangelist_path *); 1536 next_youngest = APR_ARRAY_IDX(rp->rangelist, 0, 1537 struct svn_merge_range_t *)->start; 1538 } 1539 num_revs -= 1; 1540 1541 /* The start of the new range will be YOUNGEST, and we now find the end 1542 of the new range, which should be either one less than the next 1543 earliest start of a rangelist, or the end of the first rangelist. */ 1544 youngest_end = 1545 APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0, 1546 struct rangelist_path *)->rangelist, 1547 0, svn_merge_range_t *)->end; 1548 if ( (next_youngest == youngest) || (youngest_end < next_youngest) ) 1549 tail = youngest_end; 1550 else 1551 tail = next_youngest - 1; 1552 1553 /* Insert the (earliest, tail) tuple into the output list, along with 1554 a list of paths which match it. */ 1555 plr = apr_palloc(pool, sizeof(*plr)); 1556 plr->reverse_merge = reverse_merge; 1557 plr->range.start = youngest; 1558 plr->range.end = tail; 1559 plr->paths = apr_array_make(pool, num_revs, sizeof(const char *)); 1560 for (i = 0; i < num_revs; i++) 1561 APR_ARRAY_PUSH(plr->paths, const char *) = 1562 APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path; 1563 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1564 1565 /* Now, check to see which (rangelist path) combinations we can remove, 1566 and do so. */ 1567 for (i = 0; i < num_revs; i++) 1568 { 1569 svn_merge_range_t *range; 1570 rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *); 1571 range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *); 1572 1573 /* Set the start of the range to beyond the end of the range we 1574 just built. If the range is now "inverted", we can get pop it 1575 off the list. */ 1576 range->start = tail + 1; 1577 if (range->start > range->end) 1578 { 1579 if (rp->rangelist->nelts == 1) 1580 { 1581 /* The range is the only on its list, so we should remove 1582 the entire rangelist_path, adjusting our loop control 1583 variables appropriately. */ 1584 array_pop_front(rangelist_paths); 1585 i--; 1586 num_revs--; 1587 } 1588 else 1589 { 1590 /* We have more than one range on the list, so just remove 1591 the first one. */ 1592 array_pop_front(rp->rangelist); 1593 } 1594 } 1595 } 1596 } 1597 1598 /* Finally, add the last remaining (revision range, path) to the output 1599 list. */ 1600 if (rangelist_paths->nelts > 0) 1601 { 1602 struct rangelist_path *first_rp = 1603 APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *); 1604 while (first_rp->rangelist->nelts > 0) 1605 { 1606 struct path_list_range *plr = apr_palloc(pool, sizeof(*plr)); 1607 1608 plr->reverse_merge = reverse_merge; 1609 plr->paths = apr_array_make(pool, 1, sizeof(const char *)); 1610 APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path; 1611 plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0, 1612 svn_merge_range_t *); 1613 array_pop_front(first_rp->rangelist); 1614 APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr; 1615 } 1616 } 1617 1618 svn_pool_destroy(subpool); 1619 1620 return SVN_NO_ERROR; 1621} 1622 1623 1624/* Pity that C is so ... linear. */ 1625static svn_error_t * 1626do_logs(svn_fs_t *fs, 1627 const apr_array_header_t *paths, 1628 svn_mergeinfo_t log_target_history_as_mergeinfo, 1629 svn_mergeinfo_t processed, 1630 apr_hash_t *nested_merges, 1631 svn_revnum_t hist_start, 1632 svn_revnum_t hist_end, 1633 int limit, 1634 svn_boolean_t discover_changed_paths, 1635 svn_boolean_t strict_node_history, 1636 svn_boolean_t include_merged_revisions, 1637 svn_boolean_t handling_merged_revisions, 1638 svn_boolean_t subtractive_merge, 1639 svn_boolean_t ignore_missing_locations, 1640 const apr_array_header_t *revprops, 1641 svn_boolean_t descending_order, 1642 svn_log_entry_receiver_t receiver, 1643 void *receiver_baton, 1644 svn_repos_authz_func_t authz_read_func, 1645 void *authz_read_baton, 1646 apr_pool_t *pool); 1647 1648/* Comparator function for handle_merged_revisions(). Sorts path_list_range 1649 structs in increasing order based on the struct's RANGE.START revision, 1650 then RANGE.END revision. */ 1651static int 1652compare_path_list_range(const void *a, const void *b) 1653{ 1654 struct path_list_range *plr_a = *((struct path_list_range *const *) a); 1655 struct path_list_range *plr_b = *((struct path_list_range *const *) b); 1656 1657 if (plr_a->range.start < plr_b->range.start) 1658 return -1; 1659 if (plr_a->range.start > plr_b->range.start) 1660 return 1; 1661 if (plr_a->range.end < plr_b->range.end) 1662 return -1; 1663 if (plr_a->range.end > plr_b->range.end) 1664 return 1; 1665 1666 return 0; 1667} 1668 1669/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS 1670 (as collected by examining paths of interest to a log operation), and 1671 determine which revisions to report as having been merged or reverse-merged 1672 via the commit resulting in REV. 1673 1674 Silently ignore some failures to find the revisions mentioned in the 1675 added/deleted mergeinfos, as might happen if there is invalid mergeinfo. 1676 1677 Other parameters are as described by do_logs(), around which this 1678 is a recursion wrapper. */ 1679static svn_error_t * 1680handle_merged_revisions(svn_revnum_t rev, 1681 svn_fs_t *fs, 1682 svn_mergeinfo_t log_target_history_as_mergeinfo, 1683 apr_hash_t *nested_merges, 1684 svn_mergeinfo_t processed, 1685 svn_mergeinfo_t added_mergeinfo, 1686 svn_mergeinfo_t deleted_mergeinfo, 1687 svn_boolean_t discover_changed_paths, 1688 svn_boolean_t strict_node_history, 1689 const apr_array_header_t *revprops, 1690 svn_log_entry_receiver_t receiver, 1691 void *receiver_baton, 1692 svn_repos_authz_func_t authz_read_func, 1693 void *authz_read_baton, 1694 apr_pool_t *pool) 1695{ 1696 apr_array_header_t *combined_list = NULL; 1697 svn_log_entry_t *empty_log_entry; 1698 apr_pool_t *iterpool; 1699 int i; 1700 1701 if (apr_hash_count(added_mergeinfo) == 0 1702 && apr_hash_count(deleted_mergeinfo) == 0) 1703 return SVN_NO_ERROR; 1704 1705 if (apr_hash_count(added_mergeinfo)) 1706 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo, 1707 FALSE, pool)); 1708 1709 if (apr_hash_count(deleted_mergeinfo)) 1710 SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo, 1711 TRUE, pool)); 1712 1713 SVN_ERR_ASSERT(combined_list != NULL); 1714 qsort(combined_list->elts, combined_list->nelts, 1715 combined_list->elt_size, compare_path_list_range); 1716 1717 /* Because the combined_lists are ordered youngest to oldest, 1718 iterate over them in reverse. */ 1719 iterpool = svn_pool_create(pool); 1720 for (i = combined_list->nelts - 1; i >= 0; i--) 1721 { 1722 struct path_list_range *pl_range 1723 = APR_ARRAY_IDX(combined_list, i, struct path_list_range *); 1724 1725 svn_pool_clear(iterpool); 1726 SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo, 1727 processed, nested_merges, 1728 pl_range->range.start, pl_range->range.end, 0, 1729 discover_changed_paths, strict_node_history, 1730 TRUE, pl_range->reverse_merge, TRUE, TRUE, 1731 revprops, TRUE, receiver, receiver_baton, 1732 authz_read_func, authz_read_baton, iterpool)); 1733 } 1734 svn_pool_destroy(iterpool); 1735 1736 /* Send the empty revision. */ 1737 empty_log_entry = svn_log_entry_create(pool); 1738 empty_log_entry->revision = SVN_INVALID_REVNUM; 1739 return (*receiver)(receiver_baton, empty_log_entry, pool); 1740} 1741 1742/* This is used by do_logs to differentiate between forward and 1743 reverse merges. */ 1744struct added_deleted_mergeinfo 1745{ 1746 svn_mergeinfo_t added_mergeinfo; 1747 svn_mergeinfo_t deleted_mergeinfo; 1748}; 1749 1750/* Reduce the search range PATHS, HIST_START, HIST_END by removing 1751 parts already covered by PROCESSED. If reduction is possible 1752 elements may be removed from PATHS and *START_REDUCED and 1753 *END_REDUCED may be set to a narrower range. */ 1754static svn_error_t * 1755reduce_search(apr_array_header_t *paths, 1756 svn_revnum_t *hist_start, 1757 svn_revnum_t *hist_end, 1758 svn_mergeinfo_t processed, 1759 apr_pool_t *scratch_pool) 1760{ 1761 /* We add 1 to end to compensate for store_search */ 1762 svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end; 1763 svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1; 1764 int i; 1765 1766 for (i = 0; i < paths->nelts; ++i) 1767 { 1768 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1769 svn_rangelist_t *ranges = svn_hash_gets(processed, path); 1770 int j; 1771 1772 if (!ranges) 1773 continue; 1774 1775 /* ranges is ordered, could we use some sort of binary search 1776 rather than iterating? */ 1777 for (j = 0; j < ranges->nelts; ++j) 1778 { 1779 svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j, 1780 svn_merge_range_t *); 1781 if (range->start <= start && range->end >= end) 1782 { 1783 for (j = i; j < paths->nelts - 1; ++j) 1784 APR_ARRAY_IDX(paths, j, const char *) 1785 = APR_ARRAY_IDX(paths, j + 1, const char *); 1786 1787 --paths->nelts; 1788 --i; 1789 break; 1790 } 1791 1792 /* If there is only one path then we also check for a 1793 partial overlap rather than the full overlap above, and 1794 reduce the [hist_start, hist_end] range rather than 1795 dropping the path. */ 1796 if (paths->nelts == 1) 1797 { 1798 if (range->start <= start && range->end > start) 1799 { 1800 if (start == *hist_start) 1801 *hist_start = range->end - 1; 1802 else 1803 *hist_end = range->end - 1; 1804 break; 1805 } 1806 if (range->start < end && range->end >= end) 1807 { 1808 if (start == *hist_start) 1809 *hist_end = range->start; 1810 else 1811 *hist_start = range->start; 1812 break; 1813 } 1814 } 1815 } 1816 } 1817 1818 return SVN_NO_ERROR; 1819} 1820 1821/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */ 1822static svn_error_t * 1823store_search(svn_mergeinfo_t processed, 1824 const apr_array_header_t *paths, 1825 svn_revnum_t hist_start, 1826 svn_revnum_t hist_end, 1827 apr_pool_t *scratch_pool) 1828{ 1829 /* We add 1 to end so that we can use the mergeinfo API to handle 1830 singe revisions where HIST_START is equal to HIST_END. */ 1831 svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end; 1832 svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1; 1833 svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool); 1834 apr_pool_t *processed_pool = apr_hash_pool_get(processed); 1835 int i; 1836 1837 for (i = 0; i < paths->nelts; ++i) 1838 { 1839 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1840 svn_rangelist_t *ranges = apr_array_make(processed_pool, 1, 1841 sizeof(svn_merge_range_t*)); 1842 svn_merge_range_t *range = apr_palloc(processed_pool, 1843 sizeof(svn_merge_range_t)); 1844 1845 range->start = start; 1846 range->end = end; 1847 range->inheritable = TRUE; 1848 APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range; 1849 svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges); 1850 } 1851 SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo, 1852 apr_hash_pool_get(processed), scratch_pool)); 1853 1854 return SVN_NO_ERROR; 1855} 1856 1857/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke 1858 RECEIVER with RECEIVER_BATON on them. If DESCENDING_ORDER is TRUE, send 1859 the logs back as we find them, else buffer the logs and send them back 1860 in youngest->oldest order. 1861 1862 If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus 1863 repository locations as fatal -- just ignore them. 1864 1865 If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo 1866 representing the history of PATHS between HIST_START and HIST_END. 1867 1868 If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for 1869 merged revisions, see INCLUDE_MERGED_REVISIONS argument to 1870 svn_repos_get_logs4(). If SUBTRACTIVE_MERGE is true, then this is a 1871 recursive call for reverse merged revisions. 1872 1873 If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t * 1874 mapped to svn_revnum_t *) for logs that were previously sent. On the first 1875 call to do_logs it should always be NULL. If INCLUDE_MERGED_REVISIONS is 1876 TRUE, then NESTED_MERGES will be created on the first call to do_logs, 1877 allocated in POOL. It is then shared across 1878 do_logs()/send_logs()/handle_merge_revisions() recursions, see also the 1879 argument of the same name in send_logs(). 1880 1881 PROCESSED is a mergeinfo hash that represents the paths and 1882 revisions that have already been searched. Allocated like 1883 NESTED_MERGES above. 1884 1885 All other parameters are the same as svn_repos_get_logs4(). 1886 */ 1887static svn_error_t * 1888do_logs(svn_fs_t *fs, 1889 const apr_array_header_t *paths, 1890 svn_mergeinfo_t log_target_history_as_mergeinfo, 1891 svn_mergeinfo_t processed, 1892 apr_hash_t *nested_merges, 1893 svn_revnum_t hist_start, 1894 svn_revnum_t hist_end, 1895 int limit, 1896 svn_boolean_t discover_changed_paths, 1897 svn_boolean_t strict_node_history, 1898 svn_boolean_t include_merged_revisions, 1899 svn_boolean_t subtractive_merge, 1900 svn_boolean_t handling_merged_revisions, 1901 svn_boolean_t ignore_missing_locations, 1902 const apr_array_header_t *revprops, 1903 svn_boolean_t descending_order, 1904 svn_log_entry_receiver_t receiver, 1905 void *receiver_baton, 1906 svn_repos_authz_func_t authz_read_func, 1907 void *authz_read_baton, 1908 apr_pool_t *pool) 1909{ 1910 apr_pool_t *iterpool; 1911 apr_pool_t *subpool = NULL; 1912 apr_array_header_t *revs = NULL; 1913 apr_hash_t *rev_mergeinfo = NULL; 1914 svn_revnum_t current; 1915 apr_array_header_t *histories; 1916 svn_boolean_t any_histories_left = TRUE; 1917 int send_count = 0; 1918 int i; 1919 1920 if (processed) 1921 { 1922 /* Casting away const. This only happens on recursive calls when 1923 it is known to be safe because we allocated paths. */ 1924 SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end, 1925 processed, pool)); 1926 } 1927 1928 if (!paths->nelts) 1929 return SVN_NO_ERROR; 1930 1931 if (processed) 1932 SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool)); 1933 1934 /* We have a list of paths and a revision range. But we don't care 1935 about all the revisions in the range -- only the ones in which 1936 one of our paths was changed. So let's go figure out which 1937 revisions contain real changes to at least one of our paths. */ 1938 SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end, 1939 strict_node_history, ignore_missing_locations, 1940 authz_read_func, authz_read_baton, pool)); 1941 1942 /* Loop through all the revisions in the range and add any 1943 where a path was changed to the array, or if they wanted 1944 history in reverse order just send it to them right away. */ 1945 iterpool = svn_pool_create(pool); 1946 for (current = hist_end; 1947 any_histories_left; 1948 current = next_history_rev(histories)) 1949 { 1950 svn_boolean_t changed = FALSE; 1951 any_histories_left = FALSE; 1952 svn_pool_clear(iterpool); 1953 1954 for (i = 0; i < histories->nelts; i++) 1955 { 1956 struct path_info *info = APR_ARRAY_IDX(histories, i, 1957 struct path_info *); 1958 1959 /* Check history for this path in current rev. */ 1960 SVN_ERR(check_history(&changed, info, fs, current, 1961 strict_node_history, authz_read_func, 1962 authz_read_baton, hist_start, pool)); 1963 if (! info->done) 1964 any_histories_left = TRUE; 1965 } 1966 1967 /* If any of the paths changed in this rev then add or send it. */ 1968 if (changed) 1969 { 1970 svn_mergeinfo_t added_mergeinfo = NULL; 1971 svn_mergeinfo_t deleted_mergeinfo = NULL; 1972 svn_boolean_t has_children = FALSE; 1973 apr_hash_t *changes = NULL; 1974 1975 /* If we're including merged revisions, we need to calculate 1976 the mergeinfo deltas committed in this revision to our 1977 various paths. */ 1978 if (include_merged_revisions) 1979 { 1980 apr_array_header_t *cur_paths = 1981 apr_array_make(iterpool, paths->nelts, sizeof(const char *)); 1982 1983 /* Get the current paths of our history objects so we can 1984 query mergeinfo. */ 1985 /* ### TODO: Should this be ignoring depleted history items? */ 1986 for (i = 0; i < histories->nelts; i++) 1987 { 1988 struct path_info *info = APR_ARRAY_IDX(histories, i, 1989 struct path_info *); 1990 APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data; 1991 } 1992 SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo, 1993 &deleted_mergeinfo, 1994 &changes, 1995 fs, cur_paths, 1996 current, iterpool, 1997 iterpool)); 1998 has_children = (apr_hash_count(added_mergeinfo) > 0 1999 || apr_hash_count(deleted_mergeinfo) > 0); 2000 } 2001 2002 /* If our caller wants logs in descending order, we can send 2003 'em now (because that's the order we're crawling history 2004 in anyway). */ 2005 if (descending_order) 2006 { 2007 SVN_ERR(send_log(current, fs, changes, 2008 log_target_history_as_mergeinfo, nested_merges, 2009 discover_changed_paths, 2010 subtractive_merge, handling_merged_revisions, 2011 revprops, has_children, 2012 receiver, receiver_baton, 2013 authz_read_func, authz_read_baton, iterpool)); 2014 2015 if (has_children) /* Implies include_merged_revisions == TRUE */ 2016 { 2017 if (!nested_merges) 2018 { 2019 /* We're at the start of the recursion stack, create a 2020 single hash to be shared across all of the merged 2021 recursions so we can track and squelch duplicates. */ 2022 subpool = svn_pool_create(pool); 2023 nested_merges = svn_hash__make(subpool); 2024 processed = svn_hash__make(subpool); 2025 } 2026 2027 SVN_ERR(handle_merged_revisions( 2028 current, fs, 2029 log_target_history_as_mergeinfo, nested_merges, 2030 processed, 2031 added_mergeinfo, deleted_mergeinfo, 2032 discover_changed_paths, 2033 strict_node_history, 2034 revprops, 2035 receiver, receiver_baton, 2036 authz_read_func, 2037 authz_read_baton, 2038 iterpool)); 2039 } 2040 if (limit && ++send_count >= limit) 2041 break; 2042 } 2043 /* Otherwise, the caller wanted logs in ascending order, so 2044 we have to buffer up a list of revs and (if doing 2045 mergeinfo) a hash of related mergeinfo deltas, and 2046 process them later. */ 2047 else 2048 { 2049 if (! revs) 2050 revs = apr_array_make(pool, 64, sizeof(svn_revnum_t)); 2051 APR_ARRAY_PUSH(revs, svn_revnum_t) = current; 2052 2053 if (added_mergeinfo || deleted_mergeinfo) 2054 { 2055 svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev)); 2056 struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2057 apr_palloc(pool, sizeof(*add_and_del_mergeinfo)); 2058 2059 if (added_mergeinfo) 2060 add_and_del_mergeinfo->added_mergeinfo = 2061 svn_mergeinfo_dup(added_mergeinfo, pool); 2062 2063 if (deleted_mergeinfo) 2064 add_and_del_mergeinfo->deleted_mergeinfo = 2065 svn_mergeinfo_dup(deleted_mergeinfo, pool); 2066 2067 *cur_rev = current; 2068 if (! rev_mergeinfo) 2069 rev_mergeinfo = svn_hash__make(pool); 2070 apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev), 2071 add_and_del_mergeinfo); 2072 } 2073 } 2074 } 2075 } 2076 svn_pool_destroy(iterpool); 2077 2078 if (subpool) 2079 { 2080 nested_merges = NULL; 2081 svn_pool_destroy(subpool); 2082 } 2083 2084 if (revs) 2085 { 2086 /* Work loop for processing the revisions we found since they wanted 2087 history in forward order. */ 2088 iterpool = svn_pool_create(pool); 2089 for (i = 0; i < revs->nelts; ++i) 2090 { 2091 svn_mergeinfo_t added_mergeinfo; 2092 svn_mergeinfo_t deleted_mergeinfo; 2093 svn_boolean_t has_children = FALSE; 2094 2095 svn_pool_clear(iterpool); 2096 current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t); 2097 2098 /* If we've got a hash of revision mergeinfo (which can only 2099 happen if INCLUDE_MERGED_REVISIONS was set), we check to 2100 see if this revision is one which merged in other 2101 revisions we need to handle recursively. */ 2102 if (rev_mergeinfo) 2103 { 2104 struct added_deleted_mergeinfo *add_and_del_mergeinfo = 2105 apr_hash_get(rev_mergeinfo, ¤t, sizeof(svn_revnum_t)); 2106 added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo; 2107 deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo; 2108 has_children = (apr_hash_count(added_mergeinfo) > 0 2109 || apr_hash_count(deleted_mergeinfo) > 0); 2110 } 2111 2112 SVN_ERR(send_log(current, fs, NULL, 2113 log_target_history_as_mergeinfo, nested_merges, 2114 discover_changed_paths, subtractive_merge, 2115 handling_merged_revisions, revprops, has_children, 2116 receiver, receiver_baton, authz_read_func, 2117 authz_read_baton, iterpool)); 2118 if (has_children) 2119 { 2120 if (!nested_merges) 2121 { 2122 subpool = svn_pool_create(pool); 2123 nested_merges = svn_hash__make(subpool); 2124 } 2125 2126 SVN_ERR(handle_merged_revisions(current, fs, 2127 log_target_history_as_mergeinfo, 2128 nested_merges, 2129 processed, 2130 added_mergeinfo, 2131 deleted_mergeinfo, 2132 discover_changed_paths, 2133 strict_node_history, revprops, 2134 receiver, receiver_baton, 2135 authz_read_func, 2136 authz_read_baton, 2137 iterpool)); 2138 } 2139 if (limit && i + 1 >= limit) 2140 break; 2141 } 2142 svn_pool_destroy(iterpool); 2143 } 2144 2145 return SVN_NO_ERROR; 2146} 2147 2148struct location_segment_baton 2149{ 2150 apr_array_header_t *history_segments; 2151 apr_pool_t *pool; 2152}; 2153 2154/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */ 2155static svn_error_t * 2156location_segment_receiver(svn_location_segment_t *segment, 2157 void *baton, 2158 apr_pool_t *pool) 2159{ 2160 struct location_segment_baton *b = baton; 2161 2162 APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) = 2163 svn_location_segment_dup(segment, b->pool); 2164 2165 return SVN_NO_ERROR; 2166} 2167 2168 2169/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined 2170 history of each path in PATHS between START_REV and END_REV in REPOS's 2171 filesystem. START_REV and END_REV must be valid revisions. RESULT_POOL 2172 is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all 2173 other (temporary) allocations. Other parameters are the same as 2174 svn_repos_get_logs4(). */ 2175static svn_error_t * 2176get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo, 2177 svn_repos_t *repos, 2178 const apr_array_header_t *paths, 2179 svn_revnum_t start_rev, 2180 svn_revnum_t end_rev, 2181 svn_repos_authz_func_t authz_read_func, 2182 void *authz_read_baton, 2183 apr_pool_t *result_pool, 2184 apr_pool_t *scratch_pool) 2185{ 2186 int i; 2187 svn_mergeinfo_t path_history_mergeinfo; 2188 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2189 2190 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev)); 2191 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev)); 2192 2193 /* Ensure START_REV is the youngest revision, as required by 2194 svn_repos_node_location_segments, for which this is an iterative 2195 wrapper. */ 2196 if (start_rev < end_rev) 2197 { 2198 svn_revnum_t tmp_rev = start_rev; 2199 start_rev = end_rev; 2200 end_rev = tmp_rev; 2201 } 2202 2203 *paths_history_mergeinfo = svn_hash__make(result_pool); 2204 2205 for (i = 0; i < paths->nelts; i++) 2206 { 2207 const char *this_path = APR_ARRAY_IDX(paths, i, const char *); 2208 struct location_segment_baton loc_seg_baton; 2209 2210 svn_pool_clear(iterpool); 2211 loc_seg_baton.pool = scratch_pool; 2212 loc_seg_baton.history_segments = 2213 apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *)); 2214 2215 SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev, 2216 start_rev, end_rev, 2217 location_segment_receiver, 2218 &loc_seg_baton, 2219 authz_read_func, 2220 authz_read_baton, 2221 iterpool)); 2222 2223 SVN_ERR(svn_mergeinfo__mergeinfo_from_segments( 2224 &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool)); 2225 SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo, 2226 svn_mergeinfo_dup(path_history_mergeinfo, 2227 result_pool), 2228 result_pool, iterpool)); 2229 } 2230 svn_pool_destroy(iterpool); 2231 return SVN_NO_ERROR; 2232} 2233 2234svn_error_t * 2235svn_repos_get_logs4(svn_repos_t *repos, 2236 const apr_array_header_t *paths, 2237 svn_revnum_t start, 2238 svn_revnum_t end, 2239 int limit, 2240 svn_boolean_t discover_changed_paths, 2241 svn_boolean_t strict_node_history, 2242 svn_boolean_t include_merged_revisions, 2243 const apr_array_header_t *revprops, 2244 svn_repos_authz_func_t authz_read_func, 2245 void *authz_read_baton, 2246 svn_log_entry_receiver_t receiver, 2247 void *receiver_baton, 2248 apr_pool_t *pool) 2249{ 2250 svn_revnum_t head = SVN_INVALID_REVNUM; 2251 svn_fs_t *fs = repos->fs; 2252 svn_boolean_t descending_order; 2253 svn_mergeinfo_t paths_history_mergeinfo = NULL; 2254 2255 /* Setup log range. */ 2256 SVN_ERR(svn_fs_youngest_rev(&head, fs, pool)); 2257 2258 if (! SVN_IS_VALID_REVNUM(start)) 2259 start = head; 2260 2261 if (! SVN_IS_VALID_REVNUM(end)) 2262 end = head; 2263 2264 /* Check that revisions are sane before ever invoking receiver. */ 2265 if (start > head) 2266 return svn_error_createf 2267 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2268 _("No such revision %ld"), start); 2269 if (end > head) 2270 return svn_error_createf 2271 (SVN_ERR_FS_NO_SUCH_REVISION, 0, 2272 _("No such revision %ld"), end); 2273 2274 /* Ensure a youngest-to-oldest revision crawl ordering using our 2275 (possibly sanitized) range values. */ 2276 descending_order = start >= end; 2277 if (descending_order) 2278 { 2279 svn_revnum_t tmp_rev = start; 2280 start = end; 2281 end = tmp_rev; 2282 } 2283 2284 if (! paths) 2285 paths = apr_array_make(pool, 0, sizeof(const char *)); 2286 2287 /* If we're not including merged revisions, and we were given no 2288 paths or a single empty (or "/") path, then we can bypass a bunch 2289 of complexity because we already know in which revisions the root 2290 directory was changed -- all of them. */ 2291 if ((! include_merged_revisions) 2292 && ((! paths->nelts) 2293 || ((paths->nelts == 1) 2294 && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *)) 2295 || (strcmp(APR_ARRAY_IDX(paths, 0, const char *), 2296 "/") == 0))))) 2297 { 2298 apr_uint64_t send_count = 0; 2299 int i; 2300 apr_pool_t *iterpool = svn_pool_create(pool); 2301 2302 /* If we are provided an authz callback function, use it to 2303 verify that the user has read access to the root path in the 2304 first of our revisions. 2305 2306 ### FIXME: Strictly speaking, we should be checking this 2307 ### access in every revision along the line. But currently, 2308 ### there are no known authz implementations which concern 2309 ### themselves with per-revision access. */ 2310 if (authz_read_func) 2311 { 2312 svn_boolean_t readable; 2313 svn_fs_root_t *rev_root; 2314 2315 SVN_ERR(svn_fs_revision_root(&rev_root, fs, 2316 descending_order ? end : start, pool)); 2317 SVN_ERR(authz_read_func(&readable, rev_root, "", 2318 authz_read_baton, pool)); 2319 if (! readable) 2320 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 2321 } 2322 2323 send_count = end - start + 1; 2324 if (limit && send_count > limit) 2325 send_count = limit; 2326 for (i = 0; i < send_count; ++i) 2327 { 2328 svn_revnum_t rev; 2329 2330 svn_pool_clear(iterpool); 2331 2332 if (descending_order) 2333 rev = end - i; 2334 else 2335 rev = start + i; 2336 SVN_ERR(send_log(rev, fs, NULL, NULL, NULL, 2337 discover_changed_paths, FALSE, 2338 FALSE, revprops, FALSE, receiver, 2339 receiver_baton, authz_read_func, 2340 authz_read_baton, iterpool)); 2341 } 2342 svn_pool_destroy(iterpool); 2343 2344 return SVN_NO_ERROR; 2345 } 2346 2347 /* If we are including merged revisions, then create mergeinfo that 2348 represents all of PATHS' history between START and END. We will use 2349 this later to squelch duplicate log revisions that might exist in 2350 both natural history and merged-in history. See 2351 http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */ 2352 if (include_merged_revisions) 2353 { 2354 apr_pool_t *subpool = svn_pool_create(pool); 2355 2356 SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo, 2357 repos, paths, start, end, 2358 authz_read_func, 2359 authz_read_baton, 2360 pool, subpool)); 2361 svn_pool_destroy(subpool); 2362 } 2363 2364 return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end, 2365 limit, discover_changed_paths, strict_node_history, 2366 include_merged_revisions, FALSE, FALSE, FALSE, revprops, 2367 descending_order, receiver, receiver_baton, 2368 authz_read_func, authz_read_baton, pool); 2369} 2370