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