1251881Speter/* rev_hunt.c --- routines to hunt down particular fs revisions and 2251881Speter * their properties. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter#include <string.h> 26251881Speter#include "svn_compat.h" 27251881Speter#include "svn_private_config.h" 28251881Speter#include "svn_hash.h" 29251881Speter#include "svn_pools.h" 30251881Speter#include "svn_error.h" 31251881Speter#include "svn_error_codes.h" 32251881Speter#include "svn_fs.h" 33251881Speter#include "svn_repos.h" 34251881Speter#include "svn_string.h" 35251881Speter#include "svn_time.h" 36251881Speter#include "svn_sorts.h" 37251881Speter#include "svn_props.h" 38251881Speter#include "svn_mergeinfo.h" 39251881Speter#include "repos.h" 40251881Speter#include "private/svn_fspath.h" 41251881Speter 42251881Speter 43251881Speter/* Note: this binary search assumes that the datestamp properties on 44251881Speter each revision are in chronological order. That is if revision A > 45251881Speter revision B, then A's datestamp is younger then B's datestamp. 46251881Speter 47251881Speter If someone comes along and sets a bogus datestamp, this routine 48251881Speter might not work right. 49251881Speter 50251881Speter ### todo: you know, we *could* have svn_fs_change_rev_prop() do 51251881Speter some semantic checking when it's asked to change special reserved 52251881Speter svn: properties. It could prevent such a problem. */ 53251881Speter 54251881Speter 55251881Speter/* helper for svn_repos_dated_revision(). 56251881Speter 57251881Speter Set *TM to the apr_time_t datestamp on revision REV in FS. */ 58251881Speterstatic svn_error_t * 59251881Speterget_time(apr_time_t *tm, 60251881Speter svn_fs_t *fs, 61251881Speter svn_revnum_t rev, 62251881Speter apr_pool_t *pool) 63251881Speter{ 64251881Speter svn_string_t *date_str; 65251881Speter 66251881Speter SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE, 67251881Speter pool)); 68251881Speter if (! date_str) 69251881Speter return svn_error_createf 70251881Speter (SVN_ERR_FS_GENERAL, NULL, 71251881Speter _("Failed to find time on revision %ld"), rev); 72251881Speter 73251881Speter return svn_time_from_cstring(tm, date_str->data, pool); 74251881Speter} 75251881Speter 76251881Speter 77251881Spetersvn_error_t * 78251881Spetersvn_repos_dated_revision(svn_revnum_t *revision, 79251881Speter svn_repos_t *repos, 80251881Speter apr_time_t tm, 81251881Speter apr_pool_t *pool) 82251881Speter{ 83251881Speter svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest; 84251881Speter apr_time_t this_time; 85251881Speter svn_fs_t *fs = repos->fs; 86251881Speter 87251881Speter /* Initialize top and bottom values of binary search. */ 88251881Speter SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool)); 89251881Speter rev_bot = 0; 90251881Speter rev_top = rev_latest; 91251881Speter 92251881Speter while (rev_bot <= rev_top) 93251881Speter { 94251881Speter rev_mid = (rev_top + rev_bot) / 2; 95251881Speter SVN_ERR(get_time(&this_time, fs, rev_mid, pool)); 96251881Speter 97251881Speter if (this_time > tm)/* we've overshot */ 98251881Speter { 99251881Speter apr_time_t previous_time; 100251881Speter 101251881Speter if ((rev_mid - 1) < 0) 102251881Speter { 103251881Speter *revision = 0; 104251881Speter break; 105251881Speter } 106251881Speter 107251881Speter /* see if time falls between rev_mid and rev_mid-1: */ 108251881Speter SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool)); 109251881Speter if (previous_time <= tm) 110251881Speter { 111251881Speter *revision = rev_mid - 1; 112251881Speter break; 113251881Speter } 114251881Speter 115251881Speter rev_top = rev_mid - 1; 116251881Speter } 117251881Speter 118251881Speter else if (this_time < tm) /* we've undershot */ 119251881Speter { 120251881Speter apr_time_t next_time; 121251881Speter 122251881Speter if ((rev_mid + 1) > rev_latest) 123251881Speter { 124251881Speter *revision = rev_latest; 125251881Speter break; 126251881Speter } 127251881Speter 128251881Speter /* see if time falls between rev_mid and rev_mid+1: */ 129251881Speter SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool)); 130251881Speter if (next_time > tm) 131251881Speter { 132251881Speter *revision = rev_mid; 133251881Speter break; 134251881Speter } 135251881Speter 136251881Speter rev_bot = rev_mid + 1; 137251881Speter } 138251881Speter 139251881Speter else 140251881Speter { 141251881Speter *revision = rev_mid; /* exact match! */ 142251881Speter break; 143251881Speter } 144251881Speter } 145251881Speter 146251881Speter return SVN_NO_ERROR; 147251881Speter} 148251881Speter 149251881Speter 150251881Spetersvn_error_t * 151251881Spetersvn_repos_get_committed_info(svn_revnum_t *committed_rev, 152251881Speter const char **committed_date, 153251881Speter const char **last_author, 154251881Speter svn_fs_root_t *root, 155251881Speter const char *path, 156251881Speter apr_pool_t *pool) 157251881Speter{ 158251881Speter apr_hash_t *revprops; 159251881Speter 160251881Speter svn_fs_t *fs = svn_fs_root_fs(root); 161251881Speter 162251881Speter /* ### It might be simpler just to declare that revision 163251881Speter properties have char * (i.e., UTF-8) values, not arbitrary 164251881Speter binary values, hmmm. */ 165251881Speter svn_string_t *committed_date_s, *last_author_s; 166251881Speter 167251881Speter /* Get the CR field out of the node's skel. */ 168251881Speter SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool)); 169251881Speter 170251881Speter /* Get the revision properties of this revision. */ 171251881Speter SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool)); 172251881Speter 173251881Speter /* Extract date and author from these revprops. */ 174251881Speter committed_date_s = apr_hash_get(revprops, 175251881Speter SVN_PROP_REVISION_DATE, 176251881Speter sizeof(SVN_PROP_REVISION_DATE)-1); 177251881Speter last_author_s = apr_hash_get(revprops, 178251881Speter SVN_PROP_REVISION_AUTHOR, 179251881Speter sizeof(SVN_PROP_REVISION_AUTHOR)-1); 180251881Speter 181251881Speter *committed_date = committed_date_s ? committed_date_s->data : NULL; 182251881Speter *last_author = last_author_s ? last_author_s->data : NULL; 183251881Speter 184251881Speter return SVN_NO_ERROR; 185251881Speter} 186251881Speter 187251881Spetersvn_error_t * 188251881Spetersvn_repos_history2(svn_fs_t *fs, 189251881Speter const char *path, 190251881Speter svn_repos_history_func_t history_func, 191251881Speter void *history_baton, 192251881Speter svn_repos_authz_func_t authz_read_func, 193251881Speter void *authz_read_baton, 194251881Speter svn_revnum_t start, 195251881Speter svn_revnum_t end, 196251881Speter svn_boolean_t cross_copies, 197251881Speter apr_pool_t *pool) 198251881Speter{ 199251881Speter svn_fs_history_t *history; 200251881Speter apr_pool_t *oldpool = svn_pool_create(pool); 201251881Speter apr_pool_t *newpool = svn_pool_create(pool); 202251881Speter const char *history_path; 203251881Speter svn_revnum_t history_rev; 204251881Speter svn_fs_root_t *root; 205251881Speter 206251881Speter /* Validate the revisions. */ 207251881Speter if (! SVN_IS_VALID_REVNUM(start)) 208251881Speter return svn_error_createf 209251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, 0, 210251881Speter _("Invalid start revision %ld"), start); 211251881Speter if (! SVN_IS_VALID_REVNUM(end)) 212251881Speter return svn_error_createf 213251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, 0, 214251881Speter _("Invalid end revision %ld"), end); 215251881Speter 216251881Speter /* Ensure that the input is ordered. */ 217251881Speter if (start > end) 218251881Speter { 219251881Speter svn_revnum_t tmprev = start; 220251881Speter start = end; 221251881Speter end = tmprev; 222251881Speter } 223251881Speter 224251881Speter /* Get a revision root for END, and an initial HISTORY baton. */ 225251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); 226251881Speter 227251881Speter if (authz_read_func) 228251881Speter { 229251881Speter svn_boolean_t readable; 230251881Speter SVN_ERR(authz_read_func(&readable, root, path, 231251881Speter authz_read_baton, pool)); 232251881Speter if (! readable) 233251881Speter return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL); 234251881Speter } 235251881Speter 236251881Speter SVN_ERR(svn_fs_node_history(&history, root, path, oldpool)); 237251881Speter 238251881Speter /* Now, we loop over the history items, calling svn_fs_history_prev(). */ 239251881Speter do 240251881Speter { 241251881Speter /* Note that we have to do some crazy pool work here. We can't 242251881Speter get rid of the old history until we use it to get the new, so 243251881Speter we alternate back and forth between our subpools. */ 244251881Speter apr_pool_t *tmppool; 245251881Speter svn_error_t *err; 246251881Speter 247251881Speter SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool)); 248251881Speter 249251881Speter /* Only continue if there is further history to deal with. */ 250251881Speter if (! history) 251251881Speter break; 252251881Speter 253251881Speter /* Fetch the location information for this history step. */ 254251881Speter SVN_ERR(svn_fs_history_location(&history_path, &history_rev, 255251881Speter history, newpool)); 256251881Speter 257251881Speter /* If this history item predates our START revision, quit 258251881Speter here. */ 259251881Speter if (history_rev < start) 260251881Speter break; 261251881Speter 262251881Speter /* Is the history item readable? If not, quit. */ 263251881Speter if (authz_read_func) 264251881Speter { 265251881Speter svn_boolean_t readable; 266251881Speter svn_fs_root_t *history_root; 267251881Speter SVN_ERR(svn_fs_revision_root(&history_root, fs, 268251881Speter history_rev, newpool)); 269251881Speter SVN_ERR(authz_read_func(&readable, history_root, history_path, 270251881Speter authz_read_baton, newpool)); 271251881Speter if (! readable) 272251881Speter break; 273251881Speter } 274251881Speter 275251881Speter /* Call the user-provided callback function. */ 276251881Speter err = history_func(history_baton, history_path, history_rev, newpool); 277251881Speter if (err) 278251881Speter { 279251881Speter if (err->apr_err == SVN_ERR_CEASE_INVOCATION) 280251881Speter { 281251881Speter svn_error_clear(err); 282251881Speter goto cleanup; 283251881Speter } 284251881Speter else 285251881Speter { 286251881Speter return svn_error_trace(err); 287251881Speter } 288251881Speter } 289251881Speter 290251881Speter /* We're done with the old history item, so we can clear its 291251881Speter pool, and then toggle our notion of "the old pool". */ 292251881Speter svn_pool_clear(oldpool); 293251881Speter tmppool = oldpool; 294251881Speter oldpool = newpool; 295251881Speter newpool = tmppool; 296251881Speter } 297251881Speter while (history); /* shouldn't hit this */ 298251881Speter 299251881Speter cleanup: 300251881Speter svn_pool_destroy(oldpool); 301251881Speter svn_pool_destroy(newpool); 302251881Speter return SVN_NO_ERROR; 303251881Speter} 304251881Speter 305251881Speter 306251881Spetersvn_error_t * 307251881Spetersvn_repos_deleted_rev(svn_fs_t *fs, 308251881Speter const char *path, 309251881Speter svn_revnum_t start, 310251881Speter svn_revnum_t end, 311251881Speter svn_revnum_t *deleted, 312251881Speter apr_pool_t *pool) 313251881Speter{ 314251881Speter apr_pool_t *subpool; 315251881Speter svn_fs_root_t *root, *copy_root; 316251881Speter const char *copy_path; 317251881Speter svn_revnum_t mid_rev; 318251881Speter const svn_fs_id_t *start_node_id, *curr_node_id; 319251881Speter svn_error_t *err; 320251881Speter 321251881Speter /* Validate the revision range. */ 322251881Speter if (! SVN_IS_VALID_REVNUM(start)) 323251881Speter return svn_error_createf 324251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, 0, 325251881Speter _("Invalid start revision %ld"), start); 326251881Speter if (! SVN_IS_VALID_REVNUM(end)) 327251881Speter return svn_error_createf 328251881Speter (SVN_ERR_FS_NO_SUCH_REVISION, 0, 329251881Speter _("Invalid end revision %ld"), end); 330251881Speter 331251881Speter /* Ensure that the input is ordered. */ 332251881Speter if (start > end) 333251881Speter { 334251881Speter svn_revnum_t tmprev = start; 335251881Speter start = end; 336251881Speter end = tmprev; 337251881Speter } 338251881Speter 339251881Speter /* Ensure path exists in fs at start revision. */ 340251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, start, pool)); 341251881Speter err = svn_fs_node_id(&start_node_id, root, path, pool); 342251881Speter if (err) 343251881Speter { 344251881Speter if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 345251881Speter { 346251881Speter /* Path must exist in fs at start rev. */ 347251881Speter *deleted = SVN_INVALID_REVNUM; 348251881Speter svn_error_clear(err); 349251881Speter return SVN_NO_ERROR; 350251881Speter } 351251881Speter return svn_error_trace(err); 352251881Speter } 353251881Speter 354251881Speter /* Ensure path was deleted at or before end revision. */ 355251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, end, pool)); 356251881Speter err = svn_fs_node_id(&curr_node_id, root, path, pool); 357251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 358251881Speter { 359251881Speter svn_error_clear(err); 360251881Speter } 361251881Speter else if (err) 362251881Speter { 363251881Speter return svn_error_trace(err); 364251881Speter } 365251881Speter else 366251881Speter { 367251881Speter /* path exists in the end node and the end node is equivalent 368251881Speter or otherwise equivalent to the start node. This can mean 369251881Speter a few things: 370251881Speter 371251881Speter 1) The end node *is* simply the start node, uncopied 372251881Speter and unmodified in the start to end range. 373251881Speter 374251881Speter 2) The start node was modified, but never copied. 375251881Speter 376251881Speter 3) The start node was copied, but this copy occurred at 377251881Speter start or some rev *previous* to start, this is 378251881Speter effectively the same situation as 1 if the node was 379251881Speter never modified or 2 if it was. 380251881Speter 381251881Speter In the first three cases the path was not deleted in 382251881Speter the specified range and we are done, but in the following 383251881Speter cases the start node must have been deleted at least once: 384251881Speter 385251881Speter 4) The start node was deleted and replaced by a copy of 386251881Speter itself at some rev between start and end. This copy 387251881Speter may itself have been replaced with copies of itself. 388251881Speter 389251881Speter 5) The start node was deleted and replaced by a node which 390251881Speter it does not share any history with. 391251881Speter */ 392251881Speter SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool)); 393251881Speter if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1) 394251881Speter { 395251881Speter SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, 396251881Speter path, pool)); 397251881Speter if (!copy_root || 398251881Speter (svn_fs_revision_root_revision(copy_root) <= start)) 399251881Speter { 400251881Speter /* Case 1,2 or 3, nothing more to do. */ 401251881Speter *deleted = SVN_INVALID_REVNUM; 402251881Speter return SVN_NO_ERROR; 403251881Speter } 404251881Speter } 405251881Speter } 406251881Speter 407251881Speter /* If we get here we know that path exists in rev start and was deleted 408251881Speter at least once before rev end. To find the revision path was first 409251881Speter deleted we use a binary search. The rules for the determining if 410251881Speter the deletion comes before or after a given median revision are 411251881Speter described by this matrix: 412251881Speter 413251881Speter | Most recent copy event that 414251881Speter | caused mid node to exist. 415251881Speter |----------------------------------------------------- 416251881Speter Compare path | | | | 417251881Speter at start and | Copied at | Copied at | Never copied | 418251881Speter mid nodes. | rev > start | rev <= start | | 419251881Speter | | | | 420251881Speter -------------------------------------------------------------------| 421251881Speter Mid node is | A) Start node | | 422251881Speter equivalent to | replaced with | E) Mid node == start node, | 423251881Speter start node | an unmodified | look HIGHER. | 424251881Speter | copy of | | 425251881Speter | itself, | | 426251881Speter | look LOWER. | | 427251881Speter -------------------------------------------------------------------| 428251881Speter Mid node is | B) Start node | | 429251881Speter otherwise | replaced with | F) Mid node is a modified | 430251881Speter related to | a modified | version of start node, | 431251881Speter start node | copy of | look HIGHER. | 432251881Speter | itself, | | 433251881Speter | look LOWER. | | 434251881Speter -------------------------------------------------------------------| 435251881Speter Mid node is | | 436251881Speter unrelated to | C) Start node replaced with unrelated mid node, | 437251881Speter start node | look LOWER. | 438251881Speter | | 439251881Speter -------------------------------------------------------------------| 440251881Speter Path doesn't | | 441251881Speter exist at mid | D) Start node deleted before mid node, | 442251881Speter node | look LOWER | 443251881Speter | | 444251881Speter -------------------------------------------------------------------- 445251881Speter */ 446251881Speter 447251881Speter mid_rev = (start + end) / 2; 448251881Speter subpool = svn_pool_create(pool); 449251881Speter 450251881Speter while (1) 451251881Speter { 452251881Speter svn_pool_clear(subpool); 453251881Speter 454251881Speter /* Get revision root and node id for mid_rev at that revision. */ 455251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool)); 456251881Speter err = svn_fs_node_id(&curr_node_id, root, path, subpool); 457251881Speter 458251881Speter if (err) 459251881Speter { 460251881Speter if (err->apr_err == SVN_ERR_FS_NOT_FOUND) 461251881Speter { 462251881Speter /* Case D: Look lower in the range. */ 463251881Speter svn_error_clear(err); 464251881Speter end = mid_rev; 465251881Speter mid_rev = (start + mid_rev) / 2; 466251881Speter } 467251881Speter else 468251881Speter return svn_error_trace(err); 469251881Speter } 470251881Speter else 471251881Speter { 472251881Speter /* Determine the relationship between the start node 473251881Speter and the current node. */ 474251881Speter int cmp = svn_fs_compare_ids(start_node_id, curr_node_id); 475251881Speter SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, 476251881Speter path, subpool)); 477251881Speter if (cmp == -1 || 478251881Speter (copy_root && 479251881Speter (svn_fs_revision_root_revision(copy_root) > start))) 480251881Speter { 481251881Speter /* Cases A, B, C: Look at lower revs. */ 482251881Speter end = mid_rev; 483251881Speter mid_rev = (start + mid_rev) / 2; 484251881Speter } 485251881Speter else if (end - mid_rev == 1) 486251881Speter { 487251881Speter /* Found the node path was deleted. */ 488251881Speter *deleted = end; 489251881Speter break; 490251881Speter } 491251881Speter else 492251881Speter { 493251881Speter /* Cases E, F: Look at higher revs. */ 494251881Speter start = mid_rev; 495251881Speter mid_rev = (start + end) / 2; 496251881Speter } 497251881Speter } 498251881Speter } 499251881Speter 500251881Speter svn_pool_destroy(subpool); 501251881Speter return SVN_NO_ERROR; 502251881Speter} 503251881Speter 504251881Speter 505251881Speter/* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is 506251881Speter unreadable. */ 507251881Speterstatic svn_error_t * 508251881Spetercheck_readability(svn_fs_root_t *root, 509251881Speter const char *path, 510251881Speter svn_repos_authz_func_t authz_read_func, 511251881Speter void *authz_read_baton, 512251881Speter apr_pool_t *pool) 513251881Speter{ 514251881Speter svn_boolean_t readable; 515251881Speter SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool)); 516251881Speter if (! readable) 517251881Speter return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, 518251881Speter _("Unreadable path encountered; access denied")); 519251881Speter return SVN_NO_ERROR; 520251881Speter} 521251881Speter 522251881Speter 523251881Speter/* The purpose of this function is to discover if fs_path@future_rev 524251881Speter * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */ 525251881Speter 526251881Speterstatic svn_error_t * 527251881Spetercheck_ancestry_of_peg_path(svn_boolean_t *is_ancestor, 528251881Speter svn_fs_t *fs, 529251881Speter const char *fs_path, 530251881Speter svn_revnum_t peg_revision, 531251881Speter svn_revnum_t future_revision, 532251881Speter apr_pool_t *pool) 533251881Speter{ 534251881Speter svn_fs_root_t *root; 535251881Speter svn_fs_history_t *history; 536251881Speter const char *path = NULL; 537251881Speter svn_revnum_t revision; 538251881Speter apr_pool_t *lastpool, *currpool; 539251881Speter 540251881Speter lastpool = svn_pool_create(pool); 541251881Speter currpool = svn_pool_create(pool); 542251881Speter 543251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool)); 544251881Speter 545251881Speter SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool)); 546251881Speter 547251881Speter /* Since paths that are different according to strcmp may still be 548251881Speter equivalent (due to number of consecutive slashes and the fact that 549251881Speter "" is the same as "/"), we get the "canonical" path in the first 550251881Speter iteration below so that the comparison after the loop will work 551251881Speter correctly. */ 552251881Speter fs_path = NULL; 553251881Speter 554251881Speter while (1) 555251881Speter { 556251881Speter apr_pool_t *tmppool; 557251881Speter 558251881Speter SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool)); 559251881Speter 560251881Speter if (!history) 561251881Speter break; 562251881Speter 563251881Speter SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool)); 564251881Speter 565251881Speter if (!fs_path) 566251881Speter fs_path = apr_pstrdup(pool, path); 567251881Speter 568251881Speter if (revision <= peg_revision) 569251881Speter break; 570251881Speter 571251881Speter /* Clear old pool and flip. */ 572251881Speter svn_pool_clear(lastpool); 573251881Speter tmppool = lastpool; 574251881Speter lastpool = currpool; 575251881Speter currpool = tmppool; 576251881Speter } 577251881Speter 578251881Speter /* We must have had at least one iteration above where we 579251881Speter reassigned fs_path. Else, the path wouldn't have existed at 580251881Speter future_revision and svn_fs_history would have thrown. */ 581251881Speter SVN_ERR_ASSERT(fs_path != NULL); 582251881Speter 583251881Speter *is_ancestor = (history && strcmp(path, fs_path) == 0); 584251881Speter 585251881Speter return SVN_NO_ERROR; 586251881Speter} 587251881Speter 588251881Speter 589251881Spetersvn_error_t * 590251881Spetersvn_repos__prev_location(svn_revnum_t *appeared_rev, 591251881Speter const char **prev_path, 592251881Speter svn_revnum_t *prev_rev, 593251881Speter svn_fs_t *fs, 594251881Speter svn_revnum_t revision, 595251881Speter const char *path, 596251881Speter apr_pool_t *pool) 597251881Speter{ 598251881Speter svn_fs_root_t *root, *copy_root; 599251881Speter const char *copy_path, *copy_src_path, *remainder; 600251881Speter svn_revnum_t copy_src_rev; 601251881Speter 602251881Speter /* Initialize return variables. */ 603251881Speter if (appeared_rev) 604251881Speter *appeared_rev = SVN_INVALID_REVNUM; 605251881Speter if (prev_rev) 606251881Speter *prev_rev = SVN_INVALID_REVNUM; 607251881Speter if (prev_path) 608251881Speter *prev_path = NULL; 609251881Speter 610251881Speter /* Ask about the most recent copy which affected PATH@REVISION. If 611251881Speter there was no such copy, we're done. */ 612251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); 613251881Speter SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool)); 614251881Speter if (! copy_root) 615251881Speter return SVN_NO_ERROR; 616251881Speter 617251881Speter /* Ultimately, it's not the path of the closest copy's source that 618251881Speter we care about -- it's our own path's location in the copy source 619251881Speter revision. So we'll tack the relative path that expresses the 620251881Speter difference between the copy destination and our path in the copy 621251881Speter revision onto the copy source path to determine this information. 622251881Speter 623251881Speter In other words, if our path is "/branches/my-branch/foo/bar", and 624251881Speter we know that the closest relevant copy was a copy of "/trunk" to 625251881Speter "/branches/my-branch", then that relative path under the copy 626251881Speter destination is "/foo/bar". Tacking that onto the copy source 627251881Speter path tells us that our path was located at "/trunk/foo/bar" 628251881Speter before the copy. 629251881Speter */ 630251881Speter SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path, 631251881Speter copy_root, copy_path, pool)); 632251881Speter remainder = svn_fspath__skip_ancestor(copy_path, path); 633251881Speter if (prev_path) 634251881Speter *prev_path = svn_fspath__join(copy_src_path, remainder, pool); 635251881Speter if (appeared_rev) 636251881Speter *appeared_rev = svn_fs_revision_root_revision(copy_root); 637251881Speter if (prev_rev) 638251881Speter *prev_rev = copy_src_rev; 639251881Speter return SVN_NO_ERROR; 640251881Speter} 641251881Speter 642251881Speter 643251881Spetersvn_error_t * 644251881Spetersvn_repos_trace_node_locations(svn_fs_t *fs, 645251881Speter apr_hash_t **locations, 646251881Speter const char *fs_path, 647251881Speter svn_revnum_t peg_revision, 648251881Speter const apr_array_header_t *location_revisions_orig, 649251881Speter svn_repos_authz_func_t authz_read_func, 650251881Speter void *authz_read_baton, 651251881Speter apr_pool_t *pool) 652251881Speter{ 653251881Speter apr_array_header_t *location_revisions; 654251881Speter svn_revnum_t *revision_ptr, *revision_ptr_end; 655251881Speter svn_fs_root_t *root; 656251881Speter const char *path; 657251881Speter svn_revnum_t revision; 658251881Speter svn_boolean_t is_ancestor; 659251881Speter apr_pool_t *lastpool, *currpool; 660251881Speter const svn_fs_id_t *id; 661251881Speter 662251881Speter SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t)); 663251881Speter 664251881Speter /* Ensure that FS_PATH is absolute, because our path-math below will 665251881Speter depend on that being the case. */ 666251881Speter if (*fs_path != '/') 667251881Speter fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL); 668251881Speter 669251881Speter /* Another sanity check. */ 670251881Speter if (authz_read_func) 671251881Speter { 672251881Speter svn_fs_root_t *peg_root; 673251881Speter SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); 674251881Speter SVN_ERR(check_readability(peg_root, fs_path, 675251881Speter authz_read_func, authz_read_baton, pool)); 676251881Speter } 677251881Speter 678251881Speter *locations = apr_hash_make(pool); 679251881Speter 680251881Speter /* We flip between two pools in the second loop below. */ 681251881Speter lastpool = svn_pool_create(pool); 682251881Speter currpool = svn_pool_create(pool); 683251881Speter 684251881Speter /* First - let's sort the array of the revisions from the greatest revision 685251881Speter * downward, so it will be easier to search on. */ 686251881Speter location_revisions = apr_array_copy(pool, location_revisions_orig); 687251881Speter qsort(location_revisions->elts, location_revisions->nelts, 688251881Speter sizeof(*revision_ptr), svn_sort_compare_revisions); 689251881Speter 690251881Speter revision_ptr = (svn_revnum_t *)location_revisions->elts; 691251881Speter revision_ptr_end = revision_ptr + location_revisions->nelts; 692251881Speter 693251881Speter /* Ignore revisions R that are younger than the peg_revisions where 694251881Speter path@peg_revision is not an ancestor of path@R. */ 695251881Speter is_ancestor = FALSE; 696251881Speter while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision) 697251881Speter { 698251881Speter svn_pool_clear(currpool); 699251881Speter SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path, 700251881Speter peg_revision, *revision_ptr, 701251881Speter currpool)); 702251881Speter if (is_ancestor) 703251881Speter break; 704251881Speter ++revision_ptr; 705251881Speter } 706251881Speter 707251881Speter revision = is_ancestor ? *revision_ptr : peg_revision; 708251881Speter path = fs_path; 709251881Speter if (authz_read_func) 710251881Speter { 711251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool)); 712251881Speter SVN_ERR(check_readability(root, fs_path, authz_read_func, 713251881Speter authz_read_baton, pool)); 714251881Speter } 715251881Speter 716251881Speter while (revision_ptr < revision_ptr_end) 717251881Speter { 718251881Speter apr_pool_t *tmppool; 719251881Speter svn_revnum_t appeared_rev, prev_rev; 720251881Speter const char *prev_path; 721251881Speter 722251881Speter /* Find the target of the innermost copy relevant to path@revision. 723251881Speter The copy may be of path itself, or of a parent directory. */ 724251881Speter SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 725251881Speter fs, revision, path, currpool)); 726251881Speter if (! prev_path) 727251881Speter break; 728251881Speter 729251881Speter if (authz_read_func) 730251881Speter { 731251881Speter svn_boolean_t readable; 732251881Speter svn_fs_root_t *tmp_root; 733251881Speter 734251881Speter SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool)); 735251881Speter SVN_ERR(authz_read_func(&readable, tmp_root, path, 736251881Speter authz_read_baton, currpool)); 737251881Speter if (! readable) 738251881Speter { 739251881Speter svn_pool_destroy(lastpool); 740251881Speter svn_pool_destroy(currpool); 741251881Speter 742251881Speter return SVN_NO_ERROR; 743251881Speter } 744251881Speter } 745251881Speter 746251881Speter /* Assign the current path to all younger revisions until we reach 747251881Speter the copy target rev. */ 748251881Speter while ((revision_ptr < revision_ptr_end) 749251881Speter && (*revision_ptr >= appeared_rev)) 750251881Speter { 751251881Speter /* *revision_ptr is allocated out of pool, so we can point 752251881Speter to in the hash table. */ 753251881Speter apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 754251881Speter apr_pstrdup(pool, path)); 755251881Speter revision_ptr++; 756251881Speter } 757251881Speter 758251881Speter /* Ignore all revs between the copy target rev and the copy 759251881Speter source rev (non-inclusive). */ 760251881Speter while ((revision_ptr < revision_ptr_end) 761251881Speter && (*revision_ptr > prev_rev)) 762251881Speter revision_ptr++; 763251881Speter 764251881Speter /* State update. */ 765251881Speter path = prev_path; 766251881Speter revision = prev_rev; 767251881Speter 768251881Speter /* Clear last pool and switch. */ 769251881Speter svn_pool_clear(lastpool); 770251881Speter tmppool = lastpool; 771251881Speter lastpool = currpool; 772251881Speter currpool = tmppool; 773251881Speter } 774251881Speter 775251881Speter /* There are no copies relevant to path@revision. So any remaining 776251881Speter revisions either predate the creation of path@revision or have 777251881Speter the node existing at the same path. We will look up path@lrev 778251881Speter for each remaining location-revision and make sure it is related 779251881Speter to path@revision. */ 780251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool)); 781251881Speter SVN_ERR(svn_fs_node_id(&id, root, path, pool)); 782251881Speter while (revision_ptr < revision_ptr_end) 783251881Speter { 784251881Speter svn_node_kind_t kind; 785251881Speter const svn_fs_id_t *lrev_id; 786251881Speter 787251881Speter svn_pool_clear(currpool); 788251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool)); 789251881Speter SVN_ERR(svn_fs_check_path(&kind, root, path, currpool)); 790251881Speter if (kind == svn_node_none) 791251881Speter break; 792251881Speter SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool)); 793251881Speter if (! svn_fs_check_related(id, lrev_id)) 794251881Speter break; 795251881Speter 796251881Speter /* The node exists at the same path; record that and advance. */ 797251881Speter apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr), 798251881Speter apr_pstrdup(pool, path)); 799251881Speter revision_ptr++; 800251881Speter } 801251881Speter 802251881Speter /* Ignore any remaining location-revisions; they predate the 803251881Speter creation of path@revision. */ 804251881Speter 805251881Speter svn_pool_destroy(lastpool); 806251881Speter svn_pool_destroy(currpool); 807251881Speter 808251881Speter return SVN_NO_ERROR; 809251881Speter} 810251881Speter 811251881Speter 812251881Speter/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of 813251881Speter its revision range fits between END_REV and START_REV, possibly 814251881Speter cropping the range so that it fits *entirely* in that range. */ 815251881Speterstatic svn_error_t * 816251881Spetermaybe_crop_and_send_segment(svn_location_segment_t *segment, 817251881Speter svn_revnum_t start_rev, 818251881Speter svn_revnum_t end_rev, 819251881Speter svn_location_segment_receiver_t receiver, 820251881Speter void *receiver_baton, 821251881Speter apr_pool_t *pool) 822251881Speter{ 823251881Speter /* We only want to transmit this segment if some portion of it 824251881Speter is between our END_REV and START_REV. */ 825251881Speter if (! ((segment->range_start > start_rev) 826251881Speter || (segment->range_end < end_rev))) 827251881Speter { 828251881Speter /* Correct our segment range when the range straddles one of 829251881Speter our requested revision boundaries. */ 830251881Speter if (segment->range_start < end_rev) 831251881Speter segment->range_start = end_rev; 832251881Speter if (segment->range_end > start_rev) 833251881Speter segment->range_end = start_rev; 834251881Speter SVN_ERR(receiver(segment, receiver_baton, pool)); 835251881Speter } 836251881Speter return SVN_NO_ERROR; 837251881Speter} 838251881Speter 839251881Speter 840251881Spetersvn_error_t * 841251881Spetersvn_repos_node_location_segments(svn_repos_t *repos, 842251881Speter const char *path, 843251881Speter svn_revnum_t peg_revision, 844251881Speter svn_revnum_t start_rev, 845251881Speter svn_revnum_t end_rev, 846251881Speter svn_location_segment_receiver_t receiver, 847251881Speter void *receiver_baton, 848251881Speter svn_repos_authz_func_t authz_read_func, 849251881Speter void *authz_read_baton, 850251881Speter apr_pool_t *pool) 851251881Speter{ 852251881Speter svn_fs_t *fs = svn_repos_fs(repos); 853251881Speter svn_stringbuf_t *current_path; 854251881Speter svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev; 855251881Speter apr_pool_t *subpool; 856251881Speter 857251881Speter /* No PEG_REVISION? We'll use HEAD. */ 858251881Speter if (! SVN_IS_VALID_REVNUM(peg_revision)) 859251881Speter { 860251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool)); 861251881Speter peg_revision = youngest_rev; 862251881Speter } 863251881Speter 864251881Speter /* No START_REV? We'll use HEAD (which we may have already fetched). */ 865251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 866251881Speter { 867251881Speter if (SVN_IS_VALID_REVNUM(youngest_rev)) 868251881Speter start_rev = youngest_rev; 869251881Speter else 870251881Speter SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool)); 871251881Speter } 872251881Speter 873251881Speter /* No END_REV? We'll use 0. */ 874251881Speter end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0; 875251881Speter 876251881Speter /* Are the revision properly ordered? They better be -- the API 877251881Speter demands it. */ 878251881Speter SVN_ERR_ASSERT(end_rev <= start_rev); 879251881Speter SVN_ERR_ASSERT(start_rev <= peg_revision); 880251881Speter 881251881Speter /* Ensure that PATH is absolute, because our path-math will depend 882251881Speter on that being the case. */ 883251881Speter if (*path != '/') 884251881Speter path = apr_pstrcat(pool, "/", path, (char *)NULL); 885251881Speter 886251881Speter /* Auth check. */ 887251881Speter if (authz_read_func) 888251881Speter { 889251881Speter svn_fs_root_t *peg_root; 890251881Speter SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool)); 891251881Speter SVN_ERR(check_readability(peg_root, path, 892251881Speter authz_read_func, authz_read_baton, pool)); 893251881Speter } 894251881Speter 895251881Speter /* Okay, let's get searching! */ 896251881Speter subpool = svn_pool_create(pool); 897251881Speter current_rev = peg_revision; 898251881Speter current_path = svn_stringbuf_create(path, pool); 899251881Speter while (current_rev >= end_rev) 900251881Speter { 901251881Speter svn_revnum_t appeared_rev, prev_rev; 902251881Speter const char *cur_path, *prev_path; 903251881Speter svn_location_segment_t *segment; 904251881Speter 905251881Speter svn_pool_clear(subpool); 906251881Speter 907251881Speter cur_path = apr_pstrmemdup(subpool, current_path->data, 908251881Speter current_path->len); 909251881Speter segment = apr_pcalloc(subpool, sizeof(*segment)); 910251881Speter segment->range_end = current_rev; 911251881Speter segment->range_start = end_rev; 912251881Speter /* segment path should be absolute without leading '/'. */ 913251881Speter segment->path = cur_path + 1; 914251881Speter 915251881Speter SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev, 916251881Speter fs, current_rev, cur_path, subpool)); 917251881Speter 918251881Speter /* If there are no previous locations for this thing (meaning, 919251881Speter it originated at the current path), then we simply need to 920251881Speter find its revision of origin to populate our final segment. 921251881Speter Otherwise, the APPEARED_REV is the start of current segment's 922251881Speter range. */ 923251881Speter if (! prev_path) 924251881Speter { 925251881Speter svn_fs_root_t *revroot; 926251881Speter SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool)); 927251881Speter SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot, 928251881Speter cur_path, subpool)); 929251881Speter if (segment->range_start < end_rev) 930251881Speter segment->range_start = end_rev; 931251881Speter current_rev = SVN_INVALID_REVNUM; 932251881Speter } 933251881Speter else 934251881Speter { 935251881Speter segment->range_start = appeared_rev; 936251881Speter svn_stringbuf_set(current_path, prev_path); 937251881Speter current_rev = prev_rev; 938251881Speter } 939251881Speter 940251881Speter /* Report our segment, providing it passes authz muster. */ 941251881Speter if (authz_read_func) 942251881Speter { 943251881Speter svn_boolean_t readable; 944251881Speter svn_fs_root_t *cur_rev_root; 945251881Speter 946251881Speter /* authz_read_func requires path to have a leading slash. */ 947251881Speter const char *abs_path = apr_pstrcat(subpool, "/", segment->path, 948251881Speter (char *)NULL); 949251881Speter 950251881Speter SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, 951251881Speter segment->range_end, subpool)); 952251881Speter SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path, 953251881Speter authz_read_baton, subpool)); 954251881Speter if (! readable) 955251881Speter return SVN_NO_ERROR; 956251881Speter } 957251881Speter 958251881Speter /* Transmit the segment (if it's within the scope of our concern). */ 959251881Speter SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev, 960251881Speter receiver, receiver_baton, subpool)); 961251881Speter 962251881Speter /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done 963251881Speter (and didn't ever reach END_REV). */ 964251881Speter if (! SVN_IS_VALID_REVNUM(current_rev)) 965251881Speter break; 966251881Speter 967251881Speter /* If there's a gap in the history, we need to report as much 968251881Speter (if the gap is within the scope of our concern). */ 969251881Speter if (segment->range_start - current_rev > 1) 970251881Speter { 971251881Speter svn_location_segment_t *gap_segment; 972251881Speter gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment)); 973251881Speter gap_segment->range_end = segment->range_start - 1; 974251881Speter gap_segment->range_start = current_rev + 1; 975251881Speter gap_segment->path = NULL; 976251881Speter SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev, 977251881Speter receiver, receiver_baton, 978251881Speter subpool)); 979251881Speter } 980251881Speter } 981251881Speter svn_pool_destroy(subpool); 982251881Speter return SVN_NO_ERROR; 983251881Speter} 984251881Speter 985251881Speter/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */ 986251881Speterstatic svn_error_t * 987251881Speterget_path_mergeinfo(apr_hash_t **mergeinfo, 988251881Speter svn_fs_t *fs, 989251881Speter const char *path, 990251881Speter svn_revnum_t revnum, 991251881Speter apr_pool_t *result_pool, 992251881Speter apr_pool_t *scratch_pool) 993251881Speter{ 994251881Speter svn_mergeinfo_catalog_t tmp_catalog; 995251881Speter svn_fs_root_t *root; 996251881Speter apr_array_header_t *paths = apr_array_make(scratch_pool, 1, 997251881Speter sizeof(const char *)); 998251881Speter 999251881Speter APR_ARRAY_PUSH(paths, const char *) = path; 1000251881Speter 1001251881Speter SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool)); 1002251881Speter /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz) 1003251881Speter because we will filter out unreadable revisions in 1004251881Speter find_interesting_revision(), above */ 1005251881Speter SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths, 1006251881Speter svn_mergeinfo_inherited, FALSE, TRUE, 1007251881Speter result_pool, scratch_pool)); 1008251881Speter 1009251881Speter *mergeinfo = svn_hash_gets(tmp_catalog, path); 1010251881Speter if (!*mergeinfo) 1011251881Speter *mergeinfo = apr_hash_make(result_pool); 1012251881Speter 1013251881Speter return SVN_NO_ERROR; 1014251881Speter} 1015251881Speter 1016251881Speterstatic APR_INLINE svn_boolean_t 1017251881Speteris_path_in_hash(apr_hash_t *duplicate_path_revs, 1018251881Speter const char *path, 1019251881Speter svn_revnum_t revision, 1020251881Speter apr_pool_t *pool) 1021251881Speter{ 1022251881Speter const char *key = apr_psprintf(pool, "%s:%ld", path, revision); 1023251881Speter void *ptr; 1024251881Speter 1025251881Speter ptr = svn_hash_gets(duplicate_path_revs, key); 1026251881Speter return ptr != NULL; 1027251881Speter} 1028251881Speter 1029251881Speterstruct path_revision 1030251881Speter{ 1031251881Speter svn_revnum_t revnum; 1032251881Speter const char *path; 1033251881Speter 1034251881Speter /* Does this path_rev have merges to also be included? */ 1035251881Speter apr_hash_t *merged_mergeinfo; 1036251881Speter 1037251881Speter /* Is this a merged revision? */ 1038251881Speter svn_boolean_t merged; 1039251881Speter}; 1040251881Speter 1041251881Speter/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store 1042251881Speter the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The 1043251881Speter returned *MERGED_MERGEINFO will be NULL if there are no changes. */ 1044251881Speterstatic svn_error_t * 1045251881Speterget_merged_mergeinfo(apr_hash_t **merged_mergeinfo, 1046251881Speter svn_repos_t *repos, 1047251881Speter struct path_revision *old_path_rev, 1048251881Speter apr_pool_t *result_pool, 1049251881Speter apr_pool_t *scratch_pool) 1050251881Speter{ 1051251881Speter apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed; 1052251881Speter svn_error_t *err; 1053251881Speter svn_fs_root_t *root; 1054251881Speter apr_hash_t *changed_paths; 1055251881Speter const char *path = old_path_rev->path; 1056251881Speter 1057251881Speter /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it 1058251881Speter if there is a property change. */ 1059251881Speter SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum, 1060251881Speter scratch_pool)); 1061251881Speter SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool)); 1062251881Speter while (1) 1063251881Speter { 1064251881Speter svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path); 1065251881Speter if (changed_path && changed_path->prop_mod) 1066251881Speter break; 1067251881Speter if (svn_fspath__is_root(path, strlen(path))) 1068251881Speter { 1069251881Speter *merged_mergeinfo = NULL; 1070251881Speter return SVN_NO_ERROR; 1071251881Speter } 1072251881Speter path = svn_fspath__dirname(path, scratch_pool); 1073251881Speter } 1074251881Speter 1075251881Speter /* First, find the mergeinfo difference for old_path_rev->revnum, and 1076251881Speter old_path_rev->revnum - 1. */ 1077251881Speter err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path, 1078251881Speter old_path_rev->revnum, scratch_pool, 1079251881Speter scratch_pool); 1080251881Speter if (err) 1081251881Speter { 1082251881Speter if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) 1083251881Speter { 1084251881Speter /* Issue #3896: If invalid mergeinfo is encountered the 1085251881Speter best we can do is ignore it and act is if there are 1086251881Speter no mergeinfo differences. */ 1087251881Speter svn_error_clear(err); 1088251881Speter *merged_mergeinfo = NULL; 1089251881Speter return SVN_NO_ERROR; 1090251881Speter } 1091251881Speter else 1092251881Speter { 1093251881Speter return svn_error_trace(err); 1094251881Speter } 1095251881Speter } 1096251881Speter 1097251881Speter err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path, 1098251881Speter old_path_rev->revnum - 1, scratch_pool, 1099251881Speter scratch_pool); 1100251881Speter if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND 1101251881Speter || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)) 1102251881Speter { 1103251881Speter /* If the path doesn't exist in the previous revision or it does exist 1104251881Speter but has invalid mergeinfo (Issue #3896), assume no merges. */ 1105251881Speter svn_error_clear(err); 1106251881Speter *merged_mergeinfo = NULL; 1107251881Speter return SVN_NO_ERROR; 1108251881Speter } 1109251881Speter else 1110251881Speter SVN_ERR(err); 1111251881Speter 1112251881Speter /* Then calculate and merge the differences. */ 1113251881Speter SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo, 1114251881Speter curr_mergeinfo, FALSE, result_pool, 1115251881Speter scratch_pool)); 1116251881Speter SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool)); 1117251881Speter 1118251881Speter /* Store the result. */ 1119251881Speter if (apr_hash_count(changed)) 1120251881Speter *merged_mergeinfo = changed; 1121251881Speter else 1122251881Speter *merged_mergeinfo = NULL; 1123251881Speter 1124251881Speter return SVN_NO_ERROR; 1125251881Speter} 1126251881Speter 1127251881Speterstatic svn_error_t * 1128251881Speterfind_interesting_revisions(apr_array_header_t *path_revisions, 1129251881Speter svn_repos_t *repos, 1130251881Speter const char *path, 1131251881Speter svn_revnum_t start, 1132251881Speter svn_revnum_t end, 1133251881Speter svn_boolean_t include_merged_revisions, 1134251881Speter svn_boolean_t mark_as_merged, 1135251881Speter apr_hash_t *duplicate_path_revs, 1136251881Speter svn_repos_authz_func_t authz_read_func, 1137251881Speter void *authz_read_baton, 1138251881Speter apr_pool_t *result_pool, 1139251881Speter apr_pool_t *scratch_pool) 1140251881Speter{ 1141251881Speter apr_pool_t *iterpool, *last_pool; 1142251881Speter svn_fs_history_t *history; 1143251881Speter svn_fs_root_t *root; 1144251881Speter svn_node_kind_t kind; 1145251881Speter 1146251881Speter /* We switch between two pools while looping, since we need information from 1147251881Speter the last iteration to be available. */ 1148251881Speter iterpool = svn_pool_create(scratch_pool); 1149251881Speter last_pool = svn_pool_create(scratch_pool); 1150251881Speter 1151251881Speter /* The path had better be a file in this revision. */ 1152251881Speter SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1153251881Speter SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1154251881Speter if (kind != svn_node_file) 1155251881Speter return svn_error_createf 1156251881Speter (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"), 1157251881Speter path, end); 1158251881Speter 1159251881Speter /* Open a history object. */ 1160251881Speter SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); 1161251881Speter while (1) 1162251881Speter { 1163251881Speter struct path_revision *path_rev; 1164251881Speter svn_revnum_t tmp_revnum; 1165251881Speter const char *tmp_path; 1166251881Speter apr_pool_t *tmp_pool; 1167251881Speter 1168251881Speter svn_pool_clear(iterpool); 1169251881Speter 1170251881Speter /* Fetch the history object to walk through. */ 1171251881Speter SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); 1172251881Speter if (!history) 1173251881Speter break; 1174251881Speter SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1175251881Speter history, iterpool)); 1176251881Speter 1177251881Speter /* Check to see if we already saw this path (and it's ancestors) */ 1178251881Speter if (include_merged_revisions 1179251881Speter && is_path_in_hash(duplicate_path_revs, tmp_path, 1180251881Speter tmp_revnum, iterpool)) 1181251881Speter break; 1182251881Speter 1183251881Speter /* Check authorization. */ 1184251881Speter if (authz_read_func) 1185251881Speter { 1186251881Speter svn_boolean_t readable; 1187251881Speter svn_fs_root_t *tmp_root; 1188251881Speter 1189251881Speter SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1190251881Speter iterpool)); 1191251881Speter SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1192251881Speter authz_read_baton, iterpool)); 1193251881Speter if (! readable) 1194251881Speter break; 1195251881Speter } 1196251881Speter 1197251881Speter /* We didn't break, so we must really want this path-rev. */ 1198251881Speter path_rev = apr_palloc(result_pool, sizeof(*path_rev)); 1199251881Speter path_rev->path = apr_pstrdup(result_pool, tmp_path); 1200251881Speter path_rev->revnum = tmp_revnum; 1201251881Speter path_rev->merged = mark_as_merged; 1202251881Speter APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev; 1203251881Speter 1204251881Speter if (include_merged_revisions) 1205251881Speter SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos, 1206251881Speter path_rev, result_pool, iterpool)); 1207251881Speter else 1208251881Speter path_rev->merged_mergeinfo = NULL; 1209251881Speter 1210251881Speter /* Add the path/rev pair to the hash, so we can filter out future 1211251881Speter occurrences of it. We only care about this if including merged 1212251881Speter revisions, 'cause that's the only time we can have duplicates. */ 1213251881Speter svn_hash_sets(duplicate_path_revs, 1214251881Speter apr_psprintf(result_pool, "%s:%ld", path_rev->path, 1215251881Speter path_rev->revnum), 1216251881Speter (void *)0xdeadbeef); 1217251881Speter 1218251881Speter if (path_rev->revnum <= start) 1219251881Speter break; 1220251881Speter 1221251881Speter /* Swap pools. */ 1222251881Speter tmp_pool = iterpool; 1223251881Speter iterpool = last_pool; 1224251881Speter last_pool = tmp_pool; 1225251881Speter } 1226251881Speter 1227251881Speter svn_pool_destroy(iterpool); 1228251881Speter svn_pool_destroy(last_pool); 1229251881Speter 1230251881Speter return SVN_NO_ERROR; 1231251881Speter} 1232251881Speter 1233251881Speter/* Comparison function to sort path/revisions in increasing order */ 1234251881Speterstatic int 1235251881Spetercompare_path_revisions(const void *a, const void *b) 1236251881Speter{ 1237251881Speter struct path_revision *a_pr = *(struct path_revision *const *)a; 1238251881Speter struct path_revision *b_pr = *(struct path_revision *const *)b; 1239251881Speter 1240251881Speter if (a_pr->revnum == b_pr->revnum) 1241251881Speter return 0; 1242251881Speter 1243251881Speter return a_pr->revnum < b_pr->revnum ? 1 : -1; 1244251881Speter} 1245251881Speter 1246251881Speterstatic svn_error_t * 1247251881Speterfind_merged_revisions(apr_array_header_t **merged_path_revisions_out, 1248251881Speter svn_revnum_t start, 1249251881Speter const apr_array_header_t *mainline_path_revisions, 1250251881Speter svn_repos_t *repos, 1251251881Speter apr_hash_t *duplicate_path_revs, 1252251881Speter svn_repos_authz_func_t authz_read_func, 1253251881Speter void *authz_read_baton, 1254251881Speter apr_pool_t *result_pool, 1255251881Speter apr_pool_t *scratch_pool) 1256251881Speter{ 1257251881Speter const apr_array_header_t *old; 1258251881Speter apr_array_header_t *new_merged_path_revs; 1259251881Speter apr_pool_t *iterpool, *last_pool; 1260251881Speter apr_array_header_t *merged_path_revisions = 1261251881Speter apr_array_make(scratch_pool, 0, sizeof(struct path_revision *)); 1262251881Speter 1263251881Speter old = mainline_path_revisions; 1264251881Speter iterpool = svn_pool_create(scratch_pool); 1265251881Speter last_pool = svn_pool_create(scratch_pool); 1266251881Speter 1267251881Speter do 1268251881Speter { 1269251881Speter int i; 1270251881Speter apr_pool_t *temp_pool; 1271251881Speter 1272251881Speter svn_pool_clear(iterpool); 1273251881Speter new_merged_path_revs = apr_array_make(iterpool, 0, 1274251881Speter sizeof(struct path_revision *)); 1275251881Speter 1276251881Speter /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather 1277251881Speter path_revisions for any merged revisions, and store those in NEW. */ 1278251881Speter for (i = 0; i < old->nelts; i++) 1279251881Speter { 1280251881Speter apr_pool_t *iterpool2; 1281251881Speter apr_hash_index_t *hi; 1282251881Speter struct path_revision *old_pr = APR_ARRAY_IDX(old, i, 1283251881Speter struct path_revision *); 1284251881Speter if (!old_pr->merged_mergeinfo) 1285251881Speter continue; 1286251881Speter 1287251881Speter iterpool2 = svn_pool_create(iterpool); 1288251881Speter 1289251881Speter /* Determine and trace the merge sources. */ 1290251881Speter for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi; 1291251881Speter hi = apr_hash_next(hi)) 1292251881Speter { 1293251881Speter apr_pool_t *iterpool3; 1294251881Speter svn_rangelist_t *rangelist; 1295251881Speter const char *path; 1296251881Speter int j; 1297251881Speter 1298251881Speter svn_pool_clear(iterpool2); 1299251881Speter iterpool3 = svn_pool_create(iterpool2); 1300251881Speter 1301251881Speter apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist); 1302251881Speter 1303251881Speter for (j = 0; j < rangelist->nelts; j++) 1304251881Speter { 1305251881Speter svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j, 1306251881Speter svn_merge_range_t *); 1307251881Speter svn_node_kind_t kind; 1308251881Speter svn_fs_root_t *root; 1309251881Speter 1310251881Speter if (range->end < start) 1311251881Speter continue; 1312251881Speter 1313251881Speter svn_pool_clear(iterpool3); 1314251881Speter 1315251881Speter SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end, 1316251881Speter iterpool3)); 1317251881Speter SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3)); 1318251881Speter if (kind != svn_node_file) 1319251881Speter continue; 1320251881Speter 1321251881Speter /* Search and find revisions to add to the NEW list. */ 1322251881Speter SVN_ERR(find_interesting_revisions(new_merged_path_revs, 1323251881Speter repos, path, 1324251881Speter range->start, range->end, 1325251881Speter TRUE, TRUE, 1326251881Speter duplicate_path_revs, 1327251881Speter authz_read_func, 1328251881Speter authz_read_baton, 1329251881Speter result_pool, iterpool3)); 1330251881Speter } 1331251881Speter svn_pool_destroy(iterpool3); 1332251881Speter } 1333251881Speter svn_pool_destroy(iterpool2); 1334251881Speter } 1335251881Speter 1336251881Speter /* Append the newly found path revisions with the old ones. */ 1337251881Speter merged_path_revisions = apr_array_append(iterpool, merged_path_revisions, 1338251881Speter new_merged_path_revs); 1339251881Speter 1340251881Speter /* Swap data structures */ 1341251881Speter old = new_merged_path_revs; 1342251881Speter temp_pool = last_pool; 1343251881Speter last_pool = iterpool; 1344251881Speter iterpool = temp_pool; 1345251881Speter } 1346251881Speter while (new_merged_path_revs->nelts > 0); 1347251881Speter 1348251881Speter /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */ 1349251881Speter qsort(merged_path_revisions->elts, merged_path_revisions->nelts, 1350251881Speter sizeof(struct path_revision *), compare_path_revisions); 1351251881Speter 1352251881Speter /* Copy to the output array. */ 1353251881Speter *merged_path_revisions_out = apr_array_copy(result_pool, 1354251881Speter merged_path_revisions); 1355251881Speter 1356251881Speter svn_pool_destroy(iterpool); 1357251881Speter svn_pool_destroy(last_pool); 1358251881Speter 1359251881Speter return SVN_NO_ERROR; 1360251881Speter} 1361251881Speter 1362251881Speterstruct send_baton 1363251881Speter{ 1364251881Speter apr_pool_t *iterpool; 1365251881Speter apr_pool_t *last_pool; 1366251881Speter apr_hash_t *last_props; 1367251881Speter const char *last_path; 1368251881Speter svn_fs_root_t *last_root; 1369251881Speter}; 1370251881Speter 1371251881Speter/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by 1372251881Speter SB. */ 1373251881Speterstatic svn_error_t * 1374251881Spetersend_path_revision(struct path_revision *path_rev, 1375251881Speter svn_repos_t *repos, 1376251881Speter struct send_baton *sb, 1377251881Speter svn_file_rev_handler_t handler, 1378251881Speter void *handler_baton) 1379251881Speter{ 1380251881Speter apr_hash_t *rev_props; 1381251881Speter apr_hash_t *props; 1382251881Speter apr_array_header_t *prop_diffs; 1383251881Speter svn_fs_root_t *root; 1384251881Speter svn_txdelta_stream_t *delta_stream; 1385251881Speter svn_txdelta_window_handler_t delta_handler = NULL; 1386251881Speter void *delta_baton = NULL; 1387251881Speter apr_pool_t *tmp_pool; /* For swapping */ 1388251881Speter svn_boolean_t contents_changed; 1389251881Speter 1390251881Speter svn_pool_clear(sb->iterpool); 1391251881Speter 1392251881Speter /* Get the revision properties. */ 1393251881Speter SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs, 1394251881Speter path_rev->revnum, sb->iterpool)); 1395251881Speter 1396251881Speter /* Open the revision root. */ 1397251881Speter SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum, 1398251881Speter sb->iterpool)); 1399251881Speter 1400251881Speter /* Get the file's properties for this revision and compute the diffs. */ 1401251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path, 1402251881Speter sb->iterpool)); 1403251881Speter SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props, 1404251881Speter sb->iterpool)); 1405251881Speter 1406251881Speter /* Check if the contents changed. */ 1407251881Speter /* Special case: In the first revision, we always provide a delta. */ 1408251881Speter if (sb->last_root) 1409251881Speter SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root, 1410251881Speter sb->last_path, root, path_rev->path, 1411251881Speter sb->iterpool)); 1412251881Speter else 1413251881Speter contents_changed = TRUE; 1414251881Speter 1415251881Speter /* We have all we need, give to the handler. */ 1416251881Speter SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum, 1417251881Speter rev_props, path_rev->merged, 1418251881Speter contents_changed ? &delta_handler : NULL, 1419251881Speter contents_changed ? &delta_baton : NULL, 1420251881Speter prop_diffs, sb->iterpool)); 1421251881Speter 1422251881Speter /* Compute and send delta if client asked for it. 1423251881Speter Note that this was initialized to NULL, so if !contents_changed, 1424251881Speter no deltas will be computed. */ 1425251881Speter if (delta_handler) 1426251881Speter { 1427251881Speter /* Get the content delta. */ 1428251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, 1429251881Speter sb->last_root, sb->last_path, 1430251881Speter root, path_rev->path, 1431251881Speter sb->iterpool)); 1432251881Speter /* And send. */ 1433251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, 1434251881Speter delta_handler, delta_baton, 1435251881Speter sb->iterpool)); 1436251881Speter } 1437251881Speter 1438251881Speter /* Remember root, path and props for next iteration. */ 1439251881Speter sb->last_root = root; 1440251881Speter sb->last_path = path_rev->path; 1441251881Speter sb->last_props = props; 1442251881Speter 1443251881Speter /* Swap the pools. */ 1444251881Speter tmp_pool = sb->iterpool; 1445251881Speter sb->iterpool = sb->last_pool; 1446251881Speter sb->last_pool = tmp_pool; 1447251881Speter 1448251881Speter return SVN_NO_ERROR; 1449251881Speter} 1450251881Speter 1451251881Speter/* Similar to svn_repos_get_file_revs2() but returns paths while walking 1452251881Speter history instead of after collecting all history. 1453251881Speter 1454251881Speter This allows implementing clients to immediately start processing and 1455251881Speter stop when they got the information they need. (E.g. all or a specific set 1456251881Speter of lines were modified) */ 1457251881Speterstatic svn_error_t * 1458251881Speterget_file_revs_backwards(svn_repos_t *repos, 1459251881Speter const char *path, 1460251881Speter svn_revnum_t start, 1461251881Speter svn_revnum_t end, 1462251881Speter svn_repos_authz_func_t authz_read_func, 1463251881Speter void *authz_read_baton, 1464251881Speter svn_file_rev_handler_t handler, 1465251881Speter void *handler_baton, 1466251881Speter apr_pool_t *scratch_pool) 1467251881Speter{ 1468251881Speter apr_pool_t *iterpool, *last_pool; 1469251881Speter svn_fs_history_t *history; 1470251881Speter svn_fs_root_t *root; 1471251881Speter svn_node_kind_t kind; 1472251881Speter struct send_baton sb; 1473251881Speter 1474251881Speter /* We switch between two pools while looping and so does the path-rev 1475251881Speter handler for actually reported revisions. We do this as we 1476251881Speter need just information from last iteration to be available. */ 1477251881Speter 1478251881Speter iterpool = svn_pool_create(scratch_pool); 1479251881Speter last_pool = svn_pool_create(scratch_pool); 1480251881Speter sb.iterpool = svn_pool_create(scratch_pool); 1481251881Speter sb.last_pool = svn_pool_create(scratch_pool); 1482251881Speter 1483251881Speter /* We want the first txdelta to be against the empty file. */ 1484251881Speter sb.last_root = NULL; 1485251881Speter sb.last_path = NULL; 1486251881Speter 1487251881Speter /* Create an empty hash table for the first property diff. */ 1488251881Speter sb.last_props = apr_hash_make(sb.last_pool); 1489251881Speter 1490251881Speter /* The path had better be a file in this revision. */ 1491251881Speter SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool)); 1492251881Speter SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool)); 1493251881Speter if (kind != svn_node_file) 1494251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FILE, 1495251881Speter NULL, _("'%s' is not a file in revision %ld"), 1496251881Speter path, end); 1497251881Speter 1498251881Speter /* Open a history object. */ 1499251881Speter SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool)); 1500251881Speter while (1) 1501251881Speter { 1502251881Speter struct path_revision *path_rev; 1503251881Speter svn_revnum_t tmp_revnum; 1504251881Speter const char *tmp_path; 1505251881Speter 1506251881Speter svn_pool_clear(iterpool); 1507251881Speter 1508251881Speter /* Fetch the history object to walk through. */ 1509251881Speter SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool)); 1510251881Speter if (!history) 1511251881Speter break; 1512251881Speter SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum, 1513251881Speter history, iterpool)); 1514251881Speter 1515251881Speter /* Check authorization. */ 1516251881Speter if (authz_read_func) 1517251881Speter { 1518251881Speter svn_boolean_t readable; 1519251881Speter svn_fs_root_t *tmp_root; 1520251881Speter 1521251881Speter SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum, 1522251881Speter iterpool)); 1523251881Speter SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path, 1524251881Speter authz_read_baton, iterpool)); 1525251881Speter if (! readable) 1526251881Speter break; 1527251881Speter } 1528251881Speter 1529251881Speter /* We didn't break, so we must really want this path-rev. */ 1530251881Speter path_rev = apr_palloc(iterpool, sizeof(*path_rev)); 1531251881Speter path_rev->path = tmp_path; 1532251881Speter path_rev->revnum = tmp_revnum; 1533251881Speter path_rev->merged = FALSE; 1534251881Speter 1535251881Speter SVN_ERR(send_path_revision(path_rev, repos, &sb, 1536251881Speter handler, handler_baton)); 1537251881Speter 1538251881Speter if (path_rev->revnum <= start) 1539251881Speter break; 1540251881Speter 1541251881Speter /* Swap pools. */ 1542251881Speter { 1543251881Speter apr_pool_t *tmp_pool = iterpool; 1544251881Speter iterpool = last_pool; 1545251881Speter last_pool = tmp_pool; 1546251881Speter } 1547251881Speter } 1548251881Speter 1549251881Speter svn_pool_destroy(iterpool); 1550251881Speter svn_pool_destroy(last_pool); 1551251881Speter svn_pool_destroy(sb.last_pool); 1552251881Speter svn_pool_destroy(sb.iterpool); 1553251881Speter 1554251881Speter return SVN_NO_ERROR; 1555251881Speter 1556251881Speter} 1557251881Speter 1558251881Speter 1559251881Speter/* We don't yet support sending revisions in reverse order; the caller wait 1560251881Speter * until we've traced back through the entire history, and then accept 1561251881Speter * them from oldest to youngest. Someday this may change, but in the meantime, 1562251881Speter * the general algorithm is thus: 1563251881Speter * 1564251881Speter * 1) Trace back through the history of an object, adding each revision 1565251881Speter * found to the MAINLINE_PATH_REVISIONS array, marking any which were 1566251881Speter * merges. 1567251881Speter * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the 1568251881Speter * merged revisions, including them in the MERGED_PATH_REVISIONS, and using 1569251881Speter * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple 1570251881Speter * times. 1571251881Speter * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from 1572251881Speter * oldest to youngest, interleaving as appropriate. This is implemented 1573251881Speter * similar to an insertion sort, but instead of inserting into another 1574251881Speter * array, we just call the appropriate handler. 1575251881Speter * 1576251881Speter * 2013-02: Added a very simple reverse for mainline only changes. Before this, 1577251881Speter * this would return an error (path not found) or just the first 1578251881Speter * revision before end. 1579251881Speter */ 1580251881Spetersvn_error_t * 1581251881Spetersvn_repos_get_file_revs2(svn_repos_t *repos, 1582251881Speter const char *path, 1583251881Speter svn_revnum_t start, 1584251881Speter svn_revnum_t end, 1585251881Speter svn_boolean_t include_merged_revisions, 1586251881Speter svn_repos_authz_func_t authz_read_func, 1587251881Speter void *authz_read_baton, 1588251881Speter svn_file_rev_handler_t handler, 1589251881Speter void *handler_baton, 1590251881Speter apr_pool_t *scratch_pool) 1591251881Speter{ 1592251881Speter apr_array_header_t *mainline_path_revisions, *merged_path_revisions; 1593251881Speter apr_hash_t *duplicate_path_revs; 1594251881Speter struct send_baton sb; 1595251881Speter int mainline_pos, merged_pos; 1596251881Speter 1597251881Speter if (end < start) 1598251881Speter { 1599251881Speter if (include_merged_revisions) 1600251881Speter return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); 1601251881Speter 1602251881Speter return svn_error_trace( 1603251881Speter get_file_revs_backwards(repos, path, 1604251881Speter end, start, 1605251881Speter authz_read_func, 1606251881Speter authz_read_baton, 1607251881Speter handler, 1608251881Speter handler_baton, 1609251881Speter scratch_pool)); 1610251881Speter } 1611251881Speter 1612251881Speter /* We switch between two pools while looping, since we need information from 1613251881Speter the last iteration to be available. */ 1614251881Speter sb.iterpool = svn_pool_create(scratch_pool); 1615251881Speter sb.last_pool = svn_pool_create(scratch_pool); 1616251881Speter 1617251881Speter /* We want the first txdelta to be against the empty file. */ 1618251881Speter sb.last_root = NULL; 1619251881Speter sb.last_path = NULL; 1620251881Speter 1621251881Speter /* Create an empty hash table for the first property diff. */ 1622251881Speter sb.last_props = apr_hash_make(sb.last_pool); 1623251881Speter 1624251881Speter 1625251881Speter /* Get the revisions we are interested in. */ 1626251881Speter duplicate_path_revs = apr_hash_make(scratch_pool); 1627251881Speter mainline_path_revisions = apr_array_make(scratch_pool, 100, 1628251881Speter sizeof(struct path_revision *)); 1629251881Speter SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path, 1630251881Speter start, end, include_merged_revisions, 1631251881Speter FALSE, duplicate_path_revs, 1632251881Speter authz_read_func, authz_read_baton, 1633251881Speter scratch_pool, sb.iterpool)); 1634251881Speter 1635251881Speter /* If we are including merged revisions, go get those, too. */ 1636251881Speter if (include_merged_revisions) 1637251881Speter SVN_ERR(find_merged_revisions(&merged_path_revisions, start, 1638251881Speter mainline_path_revisions, repos, 1639251881Speter duplicate_path_revs, authz_read_func, 1640251881Speter authz_read_baton, 1641251881Speter scratch_pool, sb.iterpool)); 1642251881Speter else 1643251881Speter merged_path_revisions = apr_array_make(scratch_pool, 0, 1644251881Speter sizeof(struct path_revision *)); 1645251881Speter 1646251881Speter /* We must have at least one revision to get. */ 1647251881Speter SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0); 1648251881Speter 1649251881Speter /* Walk through both mainline and merged revisions, and send them in 1650251881Speter reverse chronological order, interleaving as appropriate. */ 1651251881Speter mainline_pos = mainline_path_revisions->nelts - 1; 1652251881Speter merged_pos = merged_path_revisions->nelts - 1; 1653251881Speter while (mainline_pos >= 0 && merged_pos >= 0) 1654251881Speter { 1655251881Speter struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1656251881Speter mainline_pos, 1657251881Speter struct path_revision *); 1658251881Speter struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1659251881Speter merged_pos, 1660251881Speter struct path_revision *); 1661251881Speter 1662251881Speter if (main_pr->revnum <= merged_pr->revnum) 1663251881Speter { 1664251881Speter SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, 1665251881Speter handler_baton)); 1666251881Speter mainline_pos -= 1; 1667251881Speter } 1668251881Speter else 1669251881Speter { 1670251881Speter SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1671251881Speter handler_baton)); 1672251881Speter merged_pos -= 1; 1673251881Speter } 1674251881Speter } 1675251881Speter 1676251881Speter /* Send any remaining revisions from the mainline list. */ 1677251881Speter for (; mainline_pos >= 0; mainline_pos -= 1) 1678251881Speter { 1679251881Speter struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions, 1680251881Speter mainline_pos, 1681251881Speter struct path_revision *); 1682251881Speter SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton)); 1683251881Speter } 1684251881Speter 1685251881Speter /* Ditto for the merged list. */ 1686251881Speter for (; merged_pos >= 0; merged_pos -= 1) 1687251881Speter { 1688251881Speter struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions, 1689251881Speter merged_pos, 1690251881Speter struct path_revision *); 1691251881Speter SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler, 1692251881Speter handler_baton)); 1693251881Speter } 1694251881Speter 1695251881Speter svn_pool_destroy(sb.last_pool); 1696251881Speter svn_pool_destroy(sb.iterpool); 1697251881Speter 1698251881Speter return SVN_NO_ERROR; 1699251881Speter} 1700