1251881Speter/* 2251881Speter * compat.c: compatibility compliance logic 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#include <apr_pools.h> 25251881Speter 26251881Speter#include "svn_hash.h" 27251881Speter#include "svn_error.h" 28251881Speter#include "svn_pools.h" 29251881Speter#include "svn_sorts.h" 30251881Speter#include "svn_dirent_uri.h" 31251881Speter#include "svn_path.h" 32251881Speter#include "svn_ra.h" 33251881Speter#include "svn_io.h" 34251881Speter#include "svn_compat.h" 35251881Speter#include "svn_props.h" 36251881Speter 37251881Speter#include "private/svn_fspath.h" 38251881Speter#include "ra_loader.h" 39251881Speter#include "svn_private_config.h" 40251881Speter 41251881Speter 42251881Speter 43251881Speter/* This is just like svn_sort_compare_revisions, save that it sorts 44251881Speter the revisions in *ascending* order. */ 45251881Speterstatic int 46251881Spetercompare_revisions(const void *a, const void *b) 47251881Speter{ 48251881Speter svn_revnum_t a_rev = *(const svn_revnum_t *)a; 49251881Speter svn_revnum_t b_rev = *(const svn_revnum_t *)b; 50251881Speter if (a_rev == b_rev) 51251881Speter return 0; 52251881Speter return a_rev < b_rev ? -1 : 1; 53251881Speter} 54251881Speter 55251881Speter/* Given the CHANGED_PATHS and REVISION from an instance of a 56251881Speter svn_log_message_receiver_t function, determine at which location 57251881Speter PATH may be expected in the next log message, and set *PREV_PATH_P 58251881Speter to that value. KIND is the node kind of PATH. Set *ACTION_P to a 59251881Speter character describing the change that caused this revision (as 60251881Speter listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the 61251881Speter revision PATH was copied from, or SVN_INVALID_REVNUM if it was not 62251881Speter copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case 63251881Speter they are not used. Perform all allocations in POOL. 64251881Speter 65251881Speter This is useful for tracking the various changes in location a 66251881Speter particular resource has undergone when performing an RA->get_logs() 67251881Speter operation on that resource. 68251881Speter*/ 69251881Speterstatic svn_error_t * 70251881Speterprev_log_path(const char **prev_path_p, 71251881Speter char *action_p, 72251881Speter svn_revnum_t *copyfrom_rev_p, 73251881Speter apr_hash_t *changed_paths, 74251881Speter const char *path, 75251881Speter svn_node_kind_t kind, 76251881Speter svn_revnum_t revision, 77251881Speter apr_pool_t *pool) 78251881Speter{ 79251881Speter svn_log_changed_path_t *change; 80251881Speter const char *prev_path = NULL; 81251881Speter 82251881Speter /* It's impossible to find the predecessor path of a NULL path. */ 83251881Speter SVN_ERR_ASSERT(path); 84251881Speter 85251881Speter /* Initialize our return values for the action and copyfrom_rev in 86251881Speter case we have an unhandled case later on. */ 87251881Speter if (action_p) 88251881Speter *action_p = 'M'; 89251881Speter if (copyfrom_rev_p) 90251881Speter *copyfrom_rev_p = SVN_INVALID_REVNUM; 91251881Speter 92251881Speter if (changed_paths) 93251881Speter { 94251881Speter /* See if PATH was explicitly changed in this revision. */ 95251881Speter change = svn_hash_gets(changed_paths, path); 96251881Speter if (change) 97251881Speter { 98251881Speter /* If PATH was not newly added in this revision, then it may or may 99251881Speter not have also been part of a moved subtree. In this case, set a 100251881Speter default previous path, but still look through the parents of this 101251881Speter path for a possible copy event. */ 102251881Speter if (change->action != 'A' && change->action != 'R') 103251881Speter { 104251881Speter prev_path = path; 105251881Speter } 106251881Speter else 107251881Speter { 108251881Speter /* PATH is new in this revision. This means it cannot have been 109251881Speter part of a copied subtree. */ 110251881Speter if (change->copyfrom_path) 111251881Speter prev_path = apr_pstrdup(pool, change->copyfrom_path); 112251881Speter else 113251881Speter prev_path = NULL; 114251881Speter 115251881Speter *prev_path_p = prev_path; 116251881Speter if (action_p) 117251881Speter *action_p = change->action; 118251881Speter if (copyfrom_rev_p) 119251881Speter *copyfrom_rev_p = change->copyfrom_rev; 120251881Speter return SVN_NO_ERROR; 121251881Speter } 122251881Speter } 123251881Speter 124251881Speter if (apr_hash_count(changed_paths)) 125251881Speter { 126251881Speter /* The path was not explicitly changed in this revision. The 127251881Speter fact that we're hearing about this revision implies, then, 128251881Speter that the path was a child of some copied directory. We need 129251881Speter to find that directory, and effectively "re-base" our path on 130251881Speter that directory's copyfrom_path. */ 131251881Speter int i; 132251881Speter apr_array_header_t *paths; 133251881Speter 134251881Speter /* Build a sorted list of the changed paths. */ 135251881Speter paths = svn_sort__hash(changed_paths, 136251881Speter svn_sort_compare_items_as_paths, pool); 137251881Speter 138251881Speter /* Now, walk the list of paths backwards, looking a parent of 139251881Speter our path that has copyfrom information. */ 140251881Speter for (i = paths->nelts; i > 0; i--) 141251881Speter { 142251881Speter svn_sort__item_t item = APR_ARRAY_IDX(paths, 143251881Speter i - 1, svn_sort__item_t); 144251881Speter const char *ch_path = item.key; 145251881Speter size_t len = strlen(ch_path); 146251881Speter 147251881Speter /* See if our path is the child of this change path. If 148251881Speter not, keep looking. */ 149251881Speter if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/'))) 150251881Speter continue; 151251881Speter 152251881Speter /* Okay, our path *is* a child of this change path. If 153251881Speter this change was copied, we just need to apply the 154251881Speter portion of our path that is relative to this change's 155251881Speter path, to the change's copyfrom path. Otherwise, this 156251881Speter change isn't really interesting to us, and our search 157251881Speter continues. */ 158251881Speter change = item.value; 159251881Speter if (change->copyfrom_path) 160251881Speter { 161251881Speter if (action_p) 162251881Speter *action_p = change->action; 163251881Speter if (copyfrom_rev_p) 164251881Speter *copyfrom_rev_p = change->copyfrom_rev; 165251881Speter prev_path = svn_fspath__join(change->copyfrom_path, 166251881Speter path + len + 1, pool); 167251881Speter break; 168251881Speter } 169251881Speter } 170251881Speter } 171251881Speter } 172251881Speter 173251881Speter /* If we didn't find what we expected to find, return an error. 174251881Speter (Because directories bubble-up, we get a bunch of logs we might 175251881Speter not want. Be forgiving in that case.) */ 176251881Speter if (! prev_path) 177251881Speter { 178251881Speter if (kind == svn_node_dir) 179251881Speter prev_path = apr_pstrdup(pool, path); 180251881Speter else 181251881Speter return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 182251881Speter _("Missing changed-path information for " 183251881Speter "'%s' in revision %ld"), 184251881Speter svn_dirent_local_style(path, pool), revision); 185251881Speter } 186251881Speter 187251881Speter *prev_path_p = prev_path; 188251881Speter return SVN_NO_ERROR; 189251881Speter} 190251881Speter 191251881Speter 192251881Speter/* Set *FS_PATH_P to the absolute filesystem path associated with the 193251881Speter URL built from SESSION's URL and REL_PATH (which is relative to 194251881Speter session's URL. Use POOL for allocations. */ 195251881Speterstatic svn_error_t * 196251881Speterget_fs_path(const char **fs_path_p, 197251881Speter svn_ra_session_t *session, 198251881Speter const char *rel_path, 199251881Speter apr_pool_t *pool) 200251881Speter{ 201251881Speter const char *url, *fs_path; 202251881Speter 203251881Speter SVN_ERR(svn_ra_get_session_url(session, &url, pool)); 204251881Speter SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool)); 205251881Speter *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path, 206251881Speter rel_path, pool), 207251881Speter pool); 208251881Speter return SVN_NO_ERROR; 209251881Speter} 210251881Speter 211251881Speter 212251881Speter 213251881Speter/*** Fallback implementation of svn_ra_get_locations(). ***/ 214251881Speter 215251881Speter 216251881Speter/* ### This is to support 1.0 servers. */ 217251881Speterstruct log_receiver_baton 218251881Speter{ 219251881Speter /* The kind of the path we're tracing. */ 220251881Speter svn_node_kind_t kind; 221251881Speter 222251881Speter /* The path at which we are trying to find our versioned resource in 223251881Speter the log output. */ 224251881Speter const char *last_path; 225251881Speter 226251881Speter /* Input revisions and output hash; the whole point of this little game. */ 227251881Speter svn_revnum_t peg_revision; 228251881Speter apr_array_header_t *location_revisions; 229251881Speter const char *peg_path; 230251881Speter apr_hash_t *locations; 231251881Speter 232251881Speter /* A pool from which to allocate stuff stored in this baton. */ 233251881Speter apr_pool_t *pool; 234251881Speter}; 235251881Speter 236251881Speter 237251881Speter/* Implements svn_log_entry_receiver_t; helper for slow_get_locations. 238251881Speter As input, takes log_receiver_baton (defined above) and attempts to 239251881Speter "fill in" locations in the baton over the course of many 240251881Speter iterations. */ 241251881Speterstatic svn_error_t * 242251881Speterlog_receiver(void *baton, 243251881Speter svn_log_entry_t *log_entry, 244251881Speter apr_pool_t *pool) 245251881Speter{ 246251881Speter struct log_receiver_baton *lrb = baton; 247251881Speter apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations); 248251881Speter const char *current_path = lrb->last_path; 249251881Speter const char *prev_path; 250251881Speter 251251881Speter /* No paths were changed in this revision. Nothing to do. */ 252251881Speter if (! log_entry->changed_paths2) 253251881Speter return SVN_NO_ERROR; 254251881Speter 255251881Speter /* If we've run off the end of the path's history, there's nothing 256251881Speter to do. (This should never happen with a properly functioning 257251881Speter server, since we'd get no more log messages after the one where 258251881Speter path was created. But a malfunctioning server shouldn't cause us 259251881Speter to trigger an assertion failure.) */ 260251881Speter if (! current_path) 261251881Speter return SVN_NO_ERROR; 262251881Speter 263251881Speter /* If we haven't found our peg path yet, and we are now looking at a 264251881Speter revision equal to or older than the peg revision, then our 265251881Speter "current" path is our peg path. */ 266251881Speter if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision)) 267251881Speter lrb->peg_path = apr_pstrdup(lrb->pool, current_path); 268251881Speter 269251881Speter /* Determine the paths for any of the revisions for which we haven't 270251881Speter gotten paths already. */ 271251881Speter while (lrb->location_revisions->nelts) 272251881Speter { 273251881Speter svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions, 274251881Speter lrb->location_revisions->nelts - 1, 275251881Speter svn_revnum_t); 276251881Speter if (log_entry->revision <= next) 277251881Speter { 278251881Speter apr_hash_set(lrb->locations, 279251881Speter apr_pmemdup(hash_pool, &next, sizeof(next)), 280251881Speter sizeof(next), 281251881Speter apr_pstrdup(hash_pool, current_path)); 282251881Speter apr_array_pop(lrb->location_revisions); 283251881Speter } 284251881Speter else 285251881Speter break; 286251881Speter } 287251881Speter 288251881Speter /* Figure out at which repository path our object of interest lived 289251881Speter in the previous revision. */ 290251881Speter SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2, 291251881Speter current_path, lrb->kind, log_entry->revision, pool)); 292251881Speter 293251881Speter /* Squirrel away our "next place to look" path (suffer the strcmp 294251881Speter hit to save on allocations). */ 295251881Speter if (! prev_path) 296251881Speter lrb->last_path = NULL; 297251881Speter else if (strcmp(prev_path, current_path) != 0) 298251881Speter lrb->last_path = apr_pstrdup(lrb->pool, prev_path); 299251881Speter 300251881Speter return SVN_NO_ERROR; 301251881Speter} 302251881Speter 303251881Speter 304251881Spetersvn_error_t * 305251881Spetersvn_ra__locations_from_log(svn_ra_session_t *session, 306251881Speter apr_hash_t **locations_p, 307251881Speter const char *path, 308251881Speter svn_revnum_t peg_revision, 309251881Speter const apr_array_header_t *location_revisions, 310251881Speter apr_pool_t *pool) 311251881Speter{ 312251881Speter apr_hash_t *locations = apr_hash_make(pool); 313251881Speter struct log_receiver_baton lrb = { 0 }; 314251881Speter apr_array_header_t *targets; 315251881Speter svn_revnum_t youngest_requested, oldest_requested, youngest, oldest; 316251881Speter svn_node_kind_t kind; 317251881Speter const char *fs_path; 318251881Speter 319251881Speter /* Fetch the absolute FS path associated with PATH. */ 320251881Speter SVN_ERR(get_fs_path(&fs_path, session, path, pool)); 321251881Speter 322251881Speter /* Sanity check: verify that the peg-object exists in repos. */ 323251881Speter SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); 324251881Speter if (kind == svn_node_none) 325251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 326251881Speter _("Path '%s' doesn't exist in revision %ld"), 327251881Speter fs_path, peg_revision); 328251881Speter 329251881Speter /* Easy out: no location revisions. */ 330251881Speter if (! location_revisions->nelts) 331251881Speter { 332251881Speter *locations_p = locations; 333251881Speter return SVN_NO_ERROR; 334251881Speter } 335251881Speter 336251881Speter /* Figure out the youngest and oldest revs (amongst the set of 337251881Speter requested revisions + the peg revision) so we can avoid 338251881Speter unnecessary log parsing. */ 339251881Speter qsort(location_revisions->elts, location_revisions->nelts, 340251881Speter location_revisions->elt_size, compare_revisions); 341251881Speter oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t); 342251881Speter youngest_requested = APR_ARRAY_IDX(location_revisions, 343251881Speter location_revisions->nelts - 1, 344251881Speter svn_revnum_t); 345251881Speter youngest = peg_revision; 346251881Speter youngest = (oldest_requested > youngest) ? oldest_requested : youngest; 347251881Speter youngest = (youngest_requested > youngest) ? youngest_requested : youngest; 348251881Speter oldest = peg_revision; 349251881Speter oldest = (oldest_requested < oldest) ? oldest_requested : oldest; 350251881Speter oldest = (youngest_requested < oldest) ? youngest_requested : oldest; 351251881Speter 352251881Speter /* Populate most of our log receiver baton structure. */ 353251881Speter lrb.kind = kind; 354251881Speter lrb.last_path = fs_path; 355251881Speter lrb.location_revisions = apr_array_copy(pool, location_revisions); 356251881Speter lrb.peg_revision = peg_revision; 357251881Speter lrb.peg_path = NULL; 358251881Speter lrb.locations = locations; 359251881Speter lrb.pool = pool; 360251881Speter 361251881Speter /* Let the RA layer drive our log information handler, which will do 362251881Speter the work of finding the actual locations for our resource. 363251881Speter Notice that we always run on the youngest rev of the 3 inputs. */ 364251881Speter targets = apr_array_make(pool, 1, sizeof(const char *)); 365251881Speter APR_ARRAY_PUSH(targets, const char *) = path; 366251881Speter SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0, 367251881Speter TRUE, FALSE, FALSE, 368251881Speter apr_array_make(pool, 0, sizeof(const char *)), 369251881Speter log_receiver, &lrb, pool)); 370251881Speter 371251881Speter /* If the received log information did not cover any of the 372251881Speter requested revisions, use the last known path. (This normally 373251881Speter just means that FS_PATH was not modified between the requested 374251881Speter revision and OLDEST. If the file was created at some point after 375251881Speter OLDEST, then lrb.last_path should be NULL.) */ 376251881Speter if (! lrb.peg_path) 377251881Speter lrb.peg_path = lrb.last_path; 378251881Speter if (lrb.last_path) 379251881Speter { 380251881Speter int i; 381251881Speter for (i = 0; i < location_revisions->nelts; i++) 382251881Speter { 383251881Speter svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i, 384251881Speter svn_revnum_t); 385251881Speter if (! apr_hash_get(locations, &rev, sizeof(rev))) 386251881Speter apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)), 387251881Speter sizeof(rev), apr_pstrdup(pool, lrb.last_path)); 388251881Speter } 389251881Speter } 390251881Speter 391251881Speter /* Check that we got the peg path. */ 392251881Speter if (! lrb.peg_path) 393251881Speter return svn_error_createf 394251881Speter (APR_EGENERAL, NULL, 395251881Speter _("Unable to find repository location for '%s' in revision %ld"), 396251881Speter fs_path, peg_revision); 397251881Speter 398251881Speter /* Sanity check: make sure that our calculated peg path is the same 399251881Speter as what we expected it to be. */ 400251881Speter if (strcmp(fs_path, lrb.peg_path) != 0) 401251881Speter return svn_error_createf 402251881Speter (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 403251881Speter _("'%s' in revision %ld is an unrelated object"), 404251881Speter fs_path, youngest); 405251881Speter 406251881Speter *locations_p = locations; 407251881Speter return SVN_NO_ERROR; 408251881Speter} 409251881Speter 410251881Speter 411251881Speter 412251881Speter 413251881Speter/*** Fallback implementation of svn_ra_get_location_segments(). ***/ 414251881Speter 415251881Speterstruct gls_log_receiver_baton { 416251881Speter /* The kind of the path we're tracing. */ 417251881Speter svn_node_kind_t kind; 418251881Speter 419251881Speter /* Are we finished (and just listening to log entries because our 420251881Speter caller won't shut up?). */ 421251881Speter svn_boolean_t done; 422251881Speter 423251881Speter /* The path at which we are trying to find our versioned resource in 424251881Speter the log output. */ 425251881Speter const char *last_path; 426251881Speter 427251881Speter /* Input data. */ 428251881Speter svn_revnum_t start_rev; 429251881Speter 430251881Speter /* Output intermediate state and callback/baton. */ 431251881Speter svn_revnum_t range_end; 432251881Speter svn_location_segment_receiver_t receiver; 433251881Speter void *receiver_baton; 434251881Speter 435251881Speter /* A pool from which to allocate stuff stored in this baton. */ 436251881Speter apr_pool_t *pool; 437251881Speter}; 438251881Speter 439251881Speter/* Build a node location segment object from PATH, RANGE_START, and 440251881Speter RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */ 441251881Speterstatic svn_error_t * 442251881Spetermaybe_crop_and_send_segment(const char *path, 443251881Speter svn_revnum_t start_rev, 444251881Speter svn_revnum_t range_start, 445251881Speter svn_revnum_t range_end, 446251881Speter svn_location_segment_receiver_t receiver, 447251881Speter void *receiver_baton, 448251881Speter apr_pool_t *pool) 449251881Speter{ 450251881Speter svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment)); 451251881Speter segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL; 452251881Speter segment->range_start = range_start; 453251881Speter segment->range_end = range_end; 454251881Speter if (segment->range_start <= start_rev) 455251881Speter { 456251881Speter if (segment->range_end > start_rev) 457251881Speter segment->range_end = start_rev; 458251881Speter return receiver(segment, receiver_baton, pool); 459251881Speter } 460251881Speter return SVN_NO_ERROR; 461251881Speter} 462251881Speter 463251881Speterstatic svn_error_t * 464251881Spetergls_log_receiver(void *baton, 465251881Speter svn_log_entry_t *log_entry, 466251881Speter apr_pool_t *pool) 467251881Speter{ 468251881Speter struct gls_log_receiver_baton *lrb = baton; 469251881Speter const char *current_path = lrb->last_path; 470251881Speter const char *prev_path; 471251881Speter svn_revnum_t copyfrom_rev; 472251881Speter 473251881Speter /* If we're done, ignore this invocation. */ 474251881Speter if (lrb->done) 475251881Speter return SVN_NO_ERROR; 476251881Speter 477251881Speter /* Figure out at which repository path our object of interest lived 478251881Speter in the previous revision, and if its current location is the 479251881Speter result of copy since then. */ 480251881Speter SVN_ERR(prev_log_path(&prev_path, NULL, ©from_rev, 481251881Speter log_entry->changed_paths2, current_path, 482251881Speter lrb->kind, log_entry->revision, pool)); 483251881Speter 484251881Speter /* If we've run off the end of the path's history, we need to report 485251881Speter our final segment (and then, we're done). */ 486251881Speter if (! prev_path) 487251881Speter { 488251881Speter lrb->done = TRUE; 489251881Speter return maybe_crop_and_send_segment(current_path, lrb->start_rev, 490251881Speter log_entry->revision, lrb->range_end, 491251881Speter lrb->receiver, lrb->receiver_baton, 492251881Speter pool); 493251881Speter } 494251881Speter 495251881Speter /* If there was a copy operation of interest... */ 496251881Speter if (SVN_IS_VALID_REVNUM(copyfrom_rev)) 497251881Speter { 498251881Speter /* ...then report the segment between this revision and the 499251881Speter last-reported revision. */ 500251881Speter SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev, 501251881Speter log_entry->revision, lrb->range_end, 502251881Speter lrb->receiver, lrb->receiver_baton, 503251881Speter pool)); 504251881Speter lrb->range_end = log_entry->revision - 1; 505251881Speter 506251881Speter /* And if there was a revision gap, we need to report that, too. */ 507251881Speter if (log_entry->revision - copyfrom_rev > 1) 508251881Speter { 509251881Speter SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev, 510251881Speter copyfrom_rev + 1, lrb->range_end, 511251881Speter lrb->receiver, 512251881Speter lrb->receiver_baton, pool)); 513251881Speter lrb->range_end = copyfrom_rev; 514251881Speter } 515251881Speter 516251881Speter /* Update our state variables. */ 517251881Speter lrb->last_path = apr_pstrdup(lrb->pool, prev_path); 518251881Speter } 519251881Speter 520251881Speter return SVN_NO_ERROR; 521251881Speter} 522251881Speter 523251881Speter 524251881Spetersvn_error_t * 525251881Spetersvn_ra__location_segments_from_log(svn_ra_session_t *session, 526251881Speter const char *path, 527251881Speter svn_revnum_t peg_revision, 528251881Speter svn_revnum_t start_rev, 529251881Speter svn_revnum_t end_rev, 530251881Speter svn_location_segment_receiver_t receiver, 531251881Speter void *receiver_baton, 532251881Speter apr_pool_t *pool) 533251881Speter{ 534251881Speter struct gls_log_receiver_baton lrb = { 0 }; 535251881Speter apr_array_header_t *targets; 536251881Speter svn_node_kind_t kind; 537251881Speter svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; 538251881Speter const char *fs_path; 539251881Speter 540251881Speter /* Fetch the absolute FS path associated with PATH. */ 541251881Speter SVN_ERR(get_fs_path(&fs_path, session, path, pool)); 542251881Speter 543251881Speter /* If PEG_REVISION is invalid, it means HEAD. If START_REV is 544251881Speter invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll 545251881Speter use 0. */ 546251881Speter if (! SVN_IS_VALID_REVNUM(peg_revision)) 547251881Speter { 548251881Speter SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool)); 549251881Speter peg_revision = youngest_rev; 550251881Speter } 551251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 552251881Speter { 553251881Speter if (SVN_IS_VALID_REVNUM(youngest_rev)) 554251881Speter start_rev = youngest_rev; 555251881Speter else 556251881Speter SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool)); 557251881Speter } 558251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 559251881Speter { 560251881Speter end_rev = 0; 561251881Speter } 562251881Speter 563251881Speter /* The API demands a certain ordering of our revision inputs. Enforce it. */ 564251881Speter SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev)); 565251881Speter 566251881Speter /* Sanity check: verify that the peg-object exists in repos. */ 567251881Speter SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); 568251881Speter if (kind == svn_node_none) 569251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 570251881Speter _("Path '%s' doesn't exist in revision %ld"), 571251881Speter fs_path, start_rev); 572251881Speter 573251881Speter /* Populate most of our log receiver baton structure. */ 574251881Speter lrb.kind = kind; 575251881Speter lrb.last_path = fs_path; 576251881Speter lrb.done = FALSE; 577251881Speter lrb.start_rev = start_rev; 578251881Speter lrb.range_end = start_rev; 579251881Speter lrb.receiver = receiver; 580251881Speter lrb.receiver_baton = receiver_baton; 581251881Speter lrb.pool = pool; 582251881Speter 583251881Speter /* Let the RA layer drive our log information handler, which will do 584251881Speter the work of finding the actual locations for our resource. 585251881Speter Notice that we always run on the youngest rev of the 3 inputs. */ 586251881Speter targets = apr_array_make(pool, 1, sizeof(const char *)); 587251881Speter APR_ARRAY_PUSH(targets, const char *) = path; 588251881Speter SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0, 589251881Speter TRUE, FALSE, FALSE, 590251881Speter apr_array_make(pool, 0, sizeof(const char *)), 591251881Speter gls_log_receiver, &lrb, pool)); 592251881Speter 593251881Speter /* If we didn't finish, we need to do so with a final segment send. */ 594251881Speter if (! lrb.done) 595251881Speter SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev, 596251881Speter end_rev, lrb.range_end, 597251881Speter receiver, receiver_baton, pool)); 598251881Speter 599251881Speter return SVN_NO_ERROR; 600251881Speter} 601251881Speter 602251881Speter 603251881Speter 604251881Speter/*** Fallback implementation of svn_ra_get_file_revs(). ***/ 605251881Speter 606251881Speter/* The metadata associated with a particular revision. */ 607251881Speterstruct rev 608251881Speter{ 609251881Speter svn_revnum_t revision; /* the revision number */ 610251881Speter const char *path; /* the absolute repository path */ 611251881Speter apr_hash_t *props; /* the revprops for this revision */ 612251881Speter struct rev *next; /* the next revision */ 613251881Speter}; 614251881Speter 615251881Speter/* File revs log message baton. */ 616251881Speterstruct fr_log_message_baton { 617251881Speter const char *path; /* The path to be processed */ 618251881Speter struct rev *eldest; /* The eldest revision processed */ 619251881Speter char action; /* The action associated with the eldest */ 620251881Speter svn_revnum_t copyrev; /* The revision the eldest was copied from */ 621251881Speter apr_pool_t *pool; 622251881Speter}; 623251881Speter 624251881Speter/* Callback for log messages: implements svn_log_entry_receiver_t and 625251881Speter accumulates revision metadata into a chronologically ordered list stored in 626251881Speter the baton. */ 627251881Speterstatic svn_error_t * 628251881Speterfr_log_message_receiver(void *baton, 629251881Speter svn_log_entry_t *log_entry, 630251881Speter apr_pool_t *pool) 631251881Speter{ 632251881Speter struct fr_log_message_baton *lmb = baton; 633251881Speter struct rev *rev; 634251881Speter 635251881Speter rev = apr_palloc(lmb->pool, sizeof(*rev)); 636251881Speter rev->revision = log_entry->revision; 637251881Speter rev->path = lmb->path; 638251881Speter rev->next = lmb->eldest; 639251881Speter lmb->eldest = rev; 640251881Speter 641251881Speter /* Duplicate log_entry revprops into rev->props */ 642251881Speter rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool); 643251881Speter 644251881Speter return prev_log_path(&lmb->path, &lmb->action, 645251881Speter &lmb->copyrev, log_entry->changed_paths2, 646251881Speter lmb->path, svn_node_file, log_entry->revision, 647251881Speter lmb->pool); 648251881Speter} 649251881Speter 650251881Spetersvn_error_t * 651251881Spetersvn_ra__file_revs_from_log(svn_ra_session_t *ra_session, 652251881Speter const char *path, 653251881Speter svn_revnum_t start, 654251881Speter svn_revnum_t end, 655251881Speter svn_file_rev_handler_t handler, 656251881Speter void *handler_baton, 657251881Speter apr_pool_t *pool) 658251881Speter{ 659251881Speter svn_node_kind_t kind; 660251881Speter const char *repos_url, *session_url, *fs_path; 661251881Speter apr_array_header_t *condensed_targets; 662251881Speter struct fr_log_message_baton lmb; 663251881Speter struct rev *rev; 664251881Speter apr_hash_t *last_props; 665251881Speter svn_stream_t *last_stream; 666251881Speter apr_pool_t *currpool, *lastpool; 667251881Speter 668251881Speter /* Fetch the absolute FS path associated with PATH. */ 669251881Speter SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); 670251881Speter 671251881Speter /* Check to make sure we're dealing with a file. */ 672251881Speter SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); 673251881Speter if (kind == svn_node_dir) 674251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 675251881Speter _("'%s' is not a file"), fs_path); 676251881Speter 677251881Speter condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); 678251881Speter APR_ARRAY_PUSH(condensed_targets, const char *) = path; 679251881Speter 680251881Speter lmb.path = fs_path; 681251881Speter lmb.eldest = NULL; 682251881Speter lmb.pool = pool; 683251881Speter 684251881Speter /* Accumulate revision metadata by walking the revisions 685251881Speter backwards; this allows us to follow moves/copies 686251881Speter correctly. */ 687251881Speter SVN_ERR(svn_ra_get_log2(ra_session, 688251881Speter condensed_targets, 689251881Speter end, start, 0, /* no limit */ 690251881Speter TRUE, FALSE, FALSE, 691251881Speter NULL, fr_log_message_receiver, &lmb, 692251881Speter pool)); 693251881Speter 694251881Speter /* Reparent the session while we go back through the history. */ 695251881Speter SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); 696251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); 697251881Speter SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); 698251881Speter 699251881Speter currpool = svn_pool_create(pool); 700251881Speter lastpool = svn_pool_create(pool); 701251881Speter 702251881Speter /* We want the first txdelta to be against the empty file. */ 703251881Speter last_props = apr_hash_make(lastpool); 704251881Speter last_stream = svn_stream_empty(lastpool); 705251881Speter 706251881Speter /* Walk the revision list in chronological order, downloading each fulltext, 707251881Speter diffing it with its predecessor, and calling the file_revs handler for 708251881Speter each one. Use two iteration pools rather than one, because the diff 709251881Speter routines need to look at a sliding window of revisions. Two pools gives 710251881Speter us a ring buffer of sorts. */ 711251881Speter for (rev = lmb.eldest; rev; rev = rev->next) 712251881Speter { 713251881Speter const char *temp_path; 714251881Speter apr_pool_t *tmppool; 715251881Speter apr_hash_t *props; 716251881Speter apr_file_t *file; 717251881Speter svn_stream_t *stream; 718251881Speter apr_array_header_t *prop_diffs; 719251881Speter svn_txdelta_stream_t *delta_stream; 720251881Speter svn_txdelta_window_handler_t delta_handler = NULL; 721251881Speter void *delta_baton = NULL; 722251881Speter 723251881Speter svn_pool_clear(currpool); 724251881Speter 725251881Speter /* Get the contents of the file from the repository, and put them in 726251881Speter a temporary local file. */ 727251881Speter SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, 728251881Speter svn_io_file_del_on_pool_cleanup, 729251881Speter currpool, currpool)); 730251881Speter SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, 731251881Speter stream, NULL, &props, currpool)); 732251881Speter SVN_ERR(svn_stream_close(stream)); 733251881Speter 734251881Speter /* Open up a stream to the local file. */ 735251881Speter SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, 736251881Speter currpool)); 737251881Speter stream = svn_stream_from_aprfile2(file, FALSE, currpool); 738251881Speter 739251881Speter /* Calculate the property diff */ 740251881Speter SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); 741251881Speter 742251881Speter /* Call the file_rev handler */ 743251881Speter SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, 744251881Speter FALSE, /* merged revision */ 745251881Speter &delta_handler, &delta_baton, prop_diffs, lastpool)); 746251881Speter 747251881Speter /* Compute and send delta if client asked for it. */ 748251881Speter if (delta_handler) 749251881Speter { 750251881Speter /* Get the content delta. Don't calculate checksums as we don't 751251881Speter * use them. */ 752251881Speter svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); 753251881Speter 754251881Speter /* And send. */ 755251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, 756251881Speter delta_baton, lastpool)); 757251881Speter } 758251881Speter 759251881Speter /* Switch the pools and data for the next iteration */ 760251881Speter tmppool = currpool; 761251881Speter currpool = lastpool; 762251881Speter lastpool = tmppool; 763251881Speter 764251881Speter SVN_ERR(svn_stream_close(last_stream)); 765251881Speter last_stream = stream; 766251881Speter last_props = props; 767251881Speter } 768251881Speter 769251881Speter SVN_ERR(svn_stream_close(last_stream)); 770251881Speter svn_pool_destroy(currpool); 771251881Speter svn_pool_destroy(lastpool); 772251881Speter 773251881Speter /* Reparent the session back to the original URL. */ 774251881Speter return svn_ra_reparent(ra_session, session_url, pool); 775251881Speter} 776251881Speter 777251881Speter 778251881Speter/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/ 779251881Speter 780251881Speter/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */ 781251881Spetertypedef struct log_path_del_rev_t 782251881Speter{ 783251881Speter /* Absolute repository path. */ 784251881Speter const char *path; 785251881Speter 786251881Speter /* Revision PATH was first deleted or replaced. */ 787251881Speter svn_revnum_t revision_deleted; 788251881Speter} log_path_del_rev_t; 789251881Speter 790251881Speter/* A svn_log_entry_receiver_t callback for finding the revision 791251881Speter ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced. 792251881Speter Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED. 793251881Speter */ 794251881Speterstatic svn_error_t * 795251881Speterlog_path_del_receiver(void *baton, 796251881Speter svn_log_entry_t *log_entry, 797251881Speter apr_pool_t *pool) 798251881Speter{ 799251881Speter log_path_del_rev_t *b = baton; 800251881Speter apr_hash_index_t *hi; 801251881Speter 802251881Speter /* No paths were changed in this revision. Nothing to do. */ 803251881Speter if (! log_entry->changed_paths2) 804251881Speter return SVN_NO_ERROR; 805251881Speter 806251881Speter for (hi = apr_hash_first(pool, log_entry->changed_paths2); 807251881Speter hi != NULL; 808251881Speter hi = apr_hash_next(hi)) 809251881Speter { 810251881Speter void *val; 811251881Speter char *path; 812251881Speter svn_log_changed_path_t *log_item; 813251881Speter 814251881Speter apr_hash_this(hi, (void *) &path, NULL, &val); 815251881Speter log_item = val; 816251881Speter if (svn_path_compare_paths(b->path, path) == 0 817251881Speter && (log_item->action == 'D' || log_item->action == 'R')) 818251881Speter { 819251881Speter /* Found the first deletion or replacement, we are done. */ 820251881Speter b->revision_deleted = log_entry->revision; 821251881Speter break; 822251881Speter } 823251881Speter } 824251881Speter return SVN_NO_ERROR; 825251881Speter} 826251881Speter 827251881Spetersvn_error_t * 828251881Spetersvn_ra__get_deleted_rev_from_log(svn_ra_session_t *session, 829251881Speter const char *rel_deleted_path, 830251881Speter svn_revnum_t peg_revision, 831251881Speter svn_revnum_t end_revision, 832251881Speter svn_revnum_t *revision_deleted, 833251881Speter apr_pool_t *pool) 834251881Speter{ 835251881Speter const char *fs_path; 836251881Speter log_path_del_rev_t log_path_deleted_baton; 837251881Speter 838251881Speter /* Fetch the absolute FS path associated with PATH. */ 839251881Speter SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool)); 840251881Speter 841251881Speter if (!SVN_IS_VALID_REVNUM(peg_revision)) 842251881Speter return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, 843251881Speter _("Invalid peg revision %ld"), peg_revision); 844251881Speter if (!SVN_IS_VALID_REVNUM(end_revision)) 845251881Speter return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, 846251881Speter _("Invalid end revision %ld"), end_revision); 847251881Speter if (end_revision <= peg_revision) 848251881Speter return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 849251881Speter _("Peg revision must precede end revision")); 850251881Speter 851251881Speter log_path_deleted_baton.path = fs_path; 852251881Speter log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM; 853251881Speter 854251881Speter /* Examine the logs of SESSION's URL to find when DELETED_PATH was first 855251881Speter deleted or replaced. */ 856251881Speter SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0, 857251881Speter TRUE, TRUE, FALSE, 858251881Speter apr_array_make(pool, 0, sizeof(char *)), 859251881Speter log_path_del_receiver, &log_path_deleted_baton, 860251881Speter pool)); 861251881Speter *revision_deleted = log_path_deleted_baton.revision_deleted; 862251881Speter return SVN_NO_ERROR; 863251881Speter} 864251881Speter 865251881Speter 866251881Spetersvn_error_t * 867251881Spetersvn_ra__get_inherited_props_walk(svn_ra_session_t *session, 868251881Speter const char *path, 869251881Speter svn_revnum_t revision, 870251881Speter apr_array_header_t **inherited_props, 871251881Speter apr_pool_t *result_pool, 872251881Speter apr_pool_t *scratch_pool) 873251881Speter{ 874251881Speter const char *repos_root_url; 875251881Speter const char *session_url; 876251881Speter const char *parent_url; 877251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 878251881Speter 879251881Speter *inherited_props = 880251881Speter apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); 881251881Speter 882251881Speter /* Walk to the root of the repository getting inherited 883251881Speter props for PATH. */ 884251881Speter SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool)); 885251881Speter SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool)); 886251881Speter parent_url = session_url; 887251881Speter 888251881Speter while (strcmp(repos_root_url, parent_url)) 889251881Speter { 890251881Speter apr_hash_index_t *hi; 891251881Speter apr_hash_t *parent_props; 892251881Speter apr_hash_t *final_hash = apr_hash_make(result_pool); 893251881Speter svn_error_t *err; 894251881Speter 895251881Speter svn_pool_clear(iterpool); 896251881Speter parent_url = svn_uri_dirname(parent_url, scratch_pool); 897251881Speter SVN_ERR(svn_ra_reparent(session, parent_url, iterpool)); 898251881Speter err = session->vtable->get_dir(session, NULL, NULL, 899251881Speter &parent_props, "", 900251881Speter revision, SVN_DIRENT_ALL, 901251881Speter iterpool); 902251881Speter 903251881Speter /* If the user doesn't have read access to a parent path then 904251881Speter skip, but allow them to inherit from further up. */ 905251881Speter if (err) 906251881Speter { 907251881Speter if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) 908251881Speter || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)) 909251881Speter { 910251881Speter svn_error_clear(err); 911251881Speter continue; 912251881Speter } 913251881Speter else 914251881Speter { 915251881Speter return svn_error_trace(err); 916251881Speter } 917251881Speter } 918251881Speter 919251881Speter for (hi = apr_hash_first(scratch_pool, parent_props); 920251881Speter hi; 921251881Speter hi = apr_hash_next(hi)) 922251881Speter { 923251881Speter const char *name = svn__apr_hash_index_key(hi); 924251881Speter apr_ssize_t klen = svn__apr_hash_index_klen(hi); 925251881Speter svn_string_t *value = svn__apr_hash_index_val(hi); 926251881Speter 927251881Speter if (svn_property_kind2(name) == svn_prop_regular_kind) 928251881Speter { 929251881Speter name = apr_pstrdup(result_pool, name); 930251881Speter value = svn_string_dup(value, result_pool); 931251881Speter apr_hash_set(final_hash, name, klen, value); 932251881Speter } 933251881Speter } 934251881Speter 935251881Speter if (apr_hash_count(final_hash)) 936251881Speter { 937251881Speter svn_prop_inherited_item_t *new_iprop = 938251881Speter apr_palloc(result_pool, sizeof(*new_iprop)); 939251881Speter new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url, 940251881Speter parent_url, 941251881Speter result_pool); 942251881Speter new_iprop->prop_hash = final_hash; 943251881Speter svn_sort__array_insert(&new_iprop, *inherited_props, 0); 944251881Speter } 945251881Speter } 946251881Speter 947251881Speter /* Reparent session back to original URL. */ 948251881Speter SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool)); 949251881Speter 950251881Speter svn_pool_destroy(iterpool); 951251881Speter return SVN_NO_ERROR; 952251881Speter} 953