compat.c revision 362181
1/* 2 * compat.c: compatibility compliance logic 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <apr_pools.h> 25 26#include "svn_hash.h" 27#include "svn_error.h" 28#include "svn_pools.h" 29#include "svn_sorts.h" 30#include "svn_dirent_uri.h" 31#include "svn_path.h" 32#include "svn_ra.h" 33#include "svn_io.h" 34#include "svn_compat.h" 35#include "svn_props.h" 36 37#include "private/svn_fspath.h" 38#include "private/svn_sorts_private.h" 39#include "ra_loader.h" 40#include "svn_private_config.h" 41 42 43 44/* This is just like svn_sort_compare_revisions, save that it sorts 45 the revisions in *ascending* order. */ 46static int 47compare_revisions(const void *a, const void *b) 48{ 49 svn_revnum_t a_rev = *(const svn_revnum_t *)a; 50 svn_revnum_t b_rev = *(const svn_revnum_t *)b; 51 if (a_rev == b_rev) 52 return 0; 53 return a_rev < b_rev ? -1 : 1; 54} 55 56/* Given the CHANGED_PATHS and REVISION from an instance of a 57 svn_log_message_receiver_t function, determine at which location 58 PATH may be expected in the next log message, and set *PREV_PATH_P 59 to that value. KIND is the node kind of PATH. Set *ACTION_P to a 60 character describing the change that caused this revision (as 61 listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the 62 revision PATH was copied from, or SVN_INVALID_REVNUM if it was not 63 copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case 64 they are not used. Perform all allocations in POOL. 65 66 This is useful for tracking the various changes in location a 67 particular resource has undergone when performing an RA->get_logs() 68 operation on that resource. 69*/ 70static svn_error_t * 71prev_log_path(const char **prev_path_p, 72 char *action_p, 73 svn_revnum_t *copyfrom_rev_p, 74 apr_hash_t *changed_paths, 75 const char *path, 76 svn_node_kind_t kind, 77 svn_revnum_t revision, 78 apr_pool_t *pool) 79{ 80 svn_log_changed_path_t *change; 81 const char *prev_path = NULL; 82 83 /* It's impossible to find the predecessor path of a NULL path. */ 84 SVN_ERR_ASSERT(path); 85 86 /* Initialize our return values for the action and copyfrom_rev in 87 case we have an unhandled case later on. */ 88 if (action_p) 89 *action_p = 'M'; 90 if (copyfrom_rev_p) 91 *copyfrom_rev_p = SVN_INVALID_REVNUM; 92 93 if (changed_paths) 94 { 95 /* See if PATH was explicitly changed in this revision. */ 96 change = svn_hash_gets(changed_paths, path); 97 if (change) 98 { 99 /* If PATH was not newly added in this revision, then it may or may 100 not have also been part of a moved subtree. In this case, set a 101 default previous path, but still look through the parents of this 102 path for a possible copy event. */ 103 if (change->action != 'A' && change->action != 'R') 104 { 105 prev_path = path; 106 } 107 else 108 { 109 /* PATH is new in this revision. This means it cannot have been 110 part of a copied subtree. */ 111 if (change->copyfrom_path) 112 prev_path = apr_pstrdup(pool, change->copyfrom_path); 113 else 114 prev_path = NULL; 115 116 *prev_path_p = prev_path; 117 if (action_p) 118 *action_p = change->action; 119 if (copyfrom_rev_p) 120 *copyfrom_rev_p = change->copyfrom_rev; 121 return SVN_NO_ERROR; 122 } 123 } 124 125 if (apr_hash_count(changed_paths)) 126 { 127 /* The path was not explicitly changed in this revision. The 128 fact that we're hearing about this revision implies, then, 129 that the path was a child of some copied directory. We need 130 to find that directory, and effectively "re-base" our path on 131 that directory's copyfrom_path. */ 132 int i; 133 apr_array_header_t *paths; 134 135 /* Build a sorted list of the changed paths. */ 136 paths = svn_sort__hash(changed_paths, 137 svn_sort_compare_items_as_paths, pool); 138 139 /* Now, walk the list of paths backwards, looking a parent of 140 our path that has copyfrom information. */ 141 for (i = paths->nelts; i > 0; i--) 142 { 143 svn_sort__item_t item = APR_ARRAY_IDX(paths, 144 i - 1, svn_sort__item_t); 145 const char *ch_path = item.key; 146 size_t len = strlen(ch_path); 147 148 /* See if our path is the child of this change path. If 149 not, keep looking. */ 150 if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/'))) 151 continue; 152 153 /* Okay, our path *is* a child of this change path. If 154 this change was copied, we just need to apply the 155 portion of our path that is relative to this change's 156 path, to the change's copyfrom path. Otherwise, this 157 change isn't really interesting to us, and our search 158 continues. */ 159 change = item.value; 160 if (change->copyfrom_path) 161 { 162 if (action_p) 163 *action_p = change->action; 164 if (copyfrom_rev_p) 165 *copyfrom_rev_p = change->copyfrom_rev; 166 prev_path = svn_fspath__join(change->copyfrom_path, 167 path + len + 1, pool); 168 break; 169 } 170 } 171 } 172 } 173 174 /* If we didn't find what we expected to find, return an error. 175 (Because directories bubble-up, we get a bunch of logs we might 176 not want. Be forgiving in that case.) */ 177 if (! prev_path) 178 { 179 if (kind == svn_node_dir) 180 prev_path = apr_pstrdup(pool, path); 181 else 182 return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 183 _("Missing changed-path information for " 184 "'%s' in revision %ld"), 185 svn_dirent_local_style(path, pool), revision); 186 } 187 188 *prev_path_p = prev_path; 189 return SVN_NO_ERROR; 190} 191 192 193/* Set *FS_PATH_P to the absolute filesystem path associated with the 194 URL built from SESSION's URL and REL_PATH (which is relative to 195 session's URL. Use POOL for allocations. */ 196static svn_error_t * 197get_fs_path(const char **fs_path_p, 198 svn_ra_session_t *session, 199 const char *rel_path, 200 apr_pool_t *pool) 201{ 202 const char *url, *fs_path; 203 204 SVN_ERR(svn_ra_get_session_url(session, &url, pool)); 205 SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool)); 206 *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path, 207 rel_path, pool), 208 pool); 209 return SVN_NO_ERROR; 210} 211 212 213 214/*** Fallback implementation of svn_ra_get_locations(). ***/ 215 216 217/* ### This is to support 1.0 servers. */ 218struct log_receiver_baton 219{ 220 /* The kind of the path we're tracing. */ 221 svn_node_kind_t kind; 222 223 /* The path at which we are trying to find our versioned resource in 224 the log output. */ 225 const char *last_path; 226 227 /* Input revisions and output hash; the whole point of this little game. */ 228 svn_revnum_t peg_revision; 229 apr_array_header_t *location_revisions; 230 const char *peg_path; 231 apr_hash_t *locations; 232 233 /* A pool from which to allocate stuff stored in this baton. */ 234 apr_pool_t *pool; 235}; 236 237 238/* Implements svn_log_entry_receiver_t; helper for slow_get_locations. 239 As input, takes log_receiver_baton (defined above) and attempts to 240 "fill in" locations in the baton over the course of many 241 iterations. */ 242static svn_error_t * 243log_receiver(void *baton, 244 svn_log_entry_t *log_entry, 245 apr_pool_t *pool) 246{ 247 struct log_receiver_baton *lrb = baton; 248 apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations); 249 const char *current_path = lrb->last_path; 250 const char *prev_path; 251 252 /* No paths were changed in this revision. Nothing to do. */ 253 if (! log_entry->changed_paths2) 254 return SVN_NO_ERROR; 255 256 /* If we've run off the end of the path's history, there's nothing 257 to do. (This should never happen with a properly functioning 258 server, since we'd get no more log messages after the one where 259 path was created. But a malfunctioning server shouldn't cause us 260 to trigger an assertion failure.) */ 261 if (! current_path) 262 return SVN_NO_ERROR; 263 264 /* If we haven't found our peg path yet, and we are now looking at a 265 revision equal to or older than the peg revision, then our 266 "current" path is our peg path. */ 267 if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision)) 268 lrb->peg_path = apr_pstrdup(lrb->pool, current_path); 269 270 /* Determine the paths for any of the revisions for which we haven't 271 gotten paths already. */ 272 while (lrb->location_revisions->nelts) 273 { 274 svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions, 275 lrb->location_revisions->nelts - 1, 276 svn_revnum_t); 277 if (log_entry->revision <= next) 278 { 279 apr_hash_set(lrb->locations, 280 apr_pmemdup(hash_pool, &next, sizeof(next)), 281 sizeof(next), 282 apr_pstrdup(hash_pool, current_path)); 283 apr_array_pop(lrb->location_revisions); 284 } 285 else 286 break; 287 } 288 289 /* Figure out at which repository path our object of interest lived 290 in the previous revision. */ 291 SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2, 292 current_path, lrb->kind, log_entry->revision, pool)); 293 294 /* Squirrel away our "next place to look" path (suffer the strcmp 295 hit to save on allocations). */ 296 if (! prev_path) 297 lrb->last_path = NULL; 298 else if (strcmp(prev_path, current_path) != 0) 299 lrb->last_path = apr_pstrdup(lrb->pool, prev_path); 300 301 return SVN_NO_ERROR; 302} 303 304 305svn_error_t * 306svn_ra__locations_from_log(svn_ra_session_t *session, 307 apr_hash_t **locations_p, 308 const char *path, 309 svn_revnum_t peg_revision, 310 const apr_array_header_t *location_revisions, 311 apr_pool_t *pool) 312{ 313 apr_hash_t *locations = apr_hash_make(pool); 314 struct log_receiver_baton lrb = { 0 }; 315 apr_array_header_t *targets; 316 svn_revnum_t youngest_requested, oldest_requested, youngest, oldest; 317 svn_node_kind_t kind; 318 const char *fs_path; 319 apr_array_header_t *sorted_location_revisions; 320 321 /* Fetch the absolute FS path associated with PATH. */ 322 SVN_ERR(get_fs_path(&fs_path, session, path, pool)); 323 324 /* Sanity check: verify that the peg-object exists in repos. */ 325 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); 326 if (kind == svn_node_none) 327 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 328 _("Path '%s' doesn't exist in revision %ld"), 329 fs_path, peg_revision); 330 331 /* Easy out: no location revisions. */ 332 if (! location_revisions->nelts) 333 { 334 *locations_p = locations; 335 return SVN_NO_ERROR; 336 } 337 338 /* Figure out the youngest and oldest revs (amongst the set of 339 requested revisions + the peg revision) so we can avoid 340 unnecessary log parsing. */ 341 sorted_location_revisions = apr_array_copy(pool, location_revisions); 342 svn_sort__array(sorted_location_revisions, compare_revisions); 343 oldest_requested = APR_ARRAY_IDX(sorted_location_revisions, 0, svn_revnum_t); 344 youngest_requested = APR_ARRAY_IDX(sorted_location_revisions, 345 sorted_location_revisions->nelts - 1, 346 svn_revnum_t); 347 youngest = peg_revision; 348 youngest = (oldest_requested > youngest) ? oldest_requested : youngest; 349 youngest = (youngest_requested > youngest) ? youngest_requested : youngest; 350 oldest = peg_revision; 351 oldest = (oldest_requested < oldest) ? oldest_requested : oldest; 352 oldest = (youngest_requested < oldest) ? youngest_requested : oldest; 353 354 /* Populate most of our log receiver baton structure. */ 355 lrb.kind = kind; 356 lrb.last_path = fs_path; 357 lrb.location_revisions = apr_array_copy(pool, sorted_location_revisions); 358 lrb.peg_revision = peg_revision; 359 lrb.peg_path = NULL; 360 lrb.locations = locations; 361 lrb.pool = pool; 362 363 /* Let the RA layer drive our log information handler, which will do 364 the work of finding the actual locations for our resource. 365 Notice that we always run on the youngest rev of the 3 inputs. */ 366 targets = apr_array_make(pool, 1, sizeof(const char *)); 367 APR_ARRAY_PUSH(targets, const char *) = path; 368 SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0, 369 TRUE, FALSE, FALSE, 370 apr_array_make(pool, 0, sizeof(const char *)), 371 log_receiver, &lrb, pool)); 372 373 /* If the received log information did not cover any of the 374 requested revisions, use the last known path. (This normally 375 just means that FS_PATH was not modified between the requested 376 revision and OLDEST. If the file was created at some point after 377 OLDEST, then lrb.last_path should be NULL.) */ 378 if (! lrb.peg_path) 379 lrb.peg_path = lrb.last_path; 380 if (lrb.last_path) 381 { 382 int i; 383 for (i = 0; i < sorted_location_revisions->nelts; i++) 384 { 385 svn_revnum_t rev = APR_ARRAY_IDX(sorted_location_revisions, i, 386 svn_revnum_t); 387 if (! apr_hash_get(locations, &rev, sizeof(rev))) 388 apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)), 389 sizeof(rev), apr_pstrdup(pool, lrb.last_path)); 390 } 391 } 392 393 /* Check that we got the peg path. */ 394 if (! lrb.peg_path) 395 return svn_error_createf 396 (APR_EGENERAL, NULL, 397 _("Unable to find repository location for '%s' in revision %ld"), 398 fs_path, peg_revision); 399 400 /* Sanity check: make sure that our calculated peg path is the same 401 as what we expected it to be. */ 402 if (strcmp(fs_path, lrb.peg_path) != 0) 403 return svn_error_createf 404 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 405 _("'%s' in revision %ld is an unrelated object"), 406 fs_path, youngest); 407 408 *locations_p = locations; 409 return SVN_NO_ERROR; 410} 411 412 413 414 415/*** Fallback implementation of svn_ra_get_location_segments(). ***/ 416 417struct gls_log_receiver_baton { 418 /* The kind of the path we're tracing. */ 419 svn_node_kind_t kind; 420 421 /* Are we finished (and just listening to log entries because our 422 caller won't shut up?). */ 423 svn_boolean_t done; 424 425 /* The path at which we are trying to find our versioned resource in 426 the log output. */ 427 const char *last_path; 428 429 /* Input data. */ 430 svn_revnum_t start_rev; 431 432 /* Output intermediate state and callback/baton. */ 433 svn_revnum_t range_end; 434 svn_location_segment_receiver_t receiver; 435 void *receiver_baton; 436 437 /* A pool from which to allocate stuff stored in this baton. */ 438 apr_pool_t *pool; 439}; 440 441/* Build a node location segment object from PATH, RANGE_START, and 442 RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */ 443static svn_error_t * 444maybe_crop_and_send_segment(const char *path, 445 svn_revnum_t start_rev, 446 svn_revnum_t range_start, 447 svn_revnum_t range_end, 448 svn_location_segment_receiver_t receiver, 449 void *receiver_baton, 450 apr_pool_t *pool) 451{ 452 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment)); 453 segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL; 454 segment->range_start = range_start; 455 segment->range_end = range_end; 456 if (segment->range_start <= start_rev) 457 { 458 if (segment->range_end > start_rev) 459 segment->range_end = start_rev; 460 return receiver(segment, receiver_baton, pool); 461 } 462 return SVN_NO_ERROR; 463} 464 465static svn_error_t * 466gls_log_receiver(void *baton, 467 svn_log_entry_t *log_entry, 468 apr_pool_t *pool) 469{ 470 struct gls_log_receiver_baton *lrb = baton; 471 const char *current_path = lrb->last_path; 472 const char *prev_path; 473 svn_revnum_t copyfrom_rev; 474 475 /* If we're done, ignore this invocation. */ 476 if (lrb->done) 477 return SVN_NO_ERROR; 478 479 /* Figure out at which repository path our object of interest lived 480 in the previous revision, and if its current location is the 481 result of copy since then. */ 482 SVN_ERR(prev_log_path(&prev_path, NULL, ©from_rev, 483 log_entry->changed_paths2, current_path, 484 lrb->kind, log_entry->revision, pool)); 485 486 /* If we've run off the end of the path's history, we need to report 487 our final segment (and then, we're done). */ 488 if (! prev_path) 489 { 490 lrb->done = TRUE; 491 return maybe_crop_and_send_segment(current_path, lrb->start_rev, 492 log_entry->revision, lrb->range_end, 493 lrb->receiver, lrb->receiver_baton, 494 pool); 495 } 496 497 /* If there was a copy operation of interest... */ 498 if (SVN_IS_VALID_REVNUM(copyfrom_rev)) 499 { 500 /* ...then report the segment between this revision and the 501 last-reported revision. */ 502 SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev, 503 log_entry->revision, lrb->range_end, 504 lrb->receiver, lrb->receiver_baton, 505 pool)); 506 lrb->range_end = log_entry->revision - 1; 507 508 /* And if there was a revision gap, we need to report that, too. */ 509 if (log_entry->revision - copyfrom_rev > 1) 510 { 511 SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev, 512 copyfrom_rev + 1, lrb->range_end, 513 lrb->receiver, 514 lrb->receiver_baton, pool)); 515 lrb->range_end = copyfrom_rev; 516 } 517 518 /* Update our state variables. */ 519 lrb->last_path = apr_pstrdup(lrb->pool, prev_path); 520 } 521 522 return SVN_NO_ERROR; 523} 524 525 526svn_error_t * 527svn_ra__location_segments_from_log(svn_ra_session_t *session, 528 const char *path, 529 svn_revnum_t peg_revision, 530 svn_revnum_t start_rev, 531 svn_revnum_t end_rev, 532 svn_location_segment_receiver_t receiver, 533 void *receiver_baton, 534 apr_pool_t *pool) 535{ 536 struct gls_log_receiver_baton lrb = { 0 }; 537 apr_array_header_t *targets; 538 svn_node_kind_t kind; 539 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; 540 const char *fs_path; 541 542 /* Fetch the absolute FS path associated with PATH. */ 543 SVN_ERR(get_fs_path(&fs_path, session, path, pool)); 544 545 /* If PEG_REVISION is invalid, it means HEAD. If START_REV is 546 invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll 547 use 0. */ 548 if (! SVN_IS_VALID_REVNUM(peg_revision)) 549 { 550 SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool)); 551 peg_revision = youngest_rev; 552 } 553 if (! SVN_IS_VALID_REVNUM(start_rev)) 554 { 555 if (SVN_IS_VALID_REVNUM(youngest_rev)) 556 start_rev = youngest_rev; 557 else 558 SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool)); 559 } 560 if (! SVN_IS_VALID_REVNUM(end_rev)) 561 { 562 end_rev = 0; 563 } 564 565 /* The API demands a certain ordering of our revision inputs. Enforce it. */ 566 SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev)); 567 568 /* Sanity check: verify that the peg-object exists in repos. */ 569 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); 570 if (kind == svn_node_none) 571 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 572 _("Path '%s' doesn't exist in revision %ld"), 573 fs_path, start_rev); 574 575 /* Populate most of our log receiver baton structure. */ 576 lrb.kind = kind; 577 lrb.last_path = fs_path; 578 lrb.done = FALSE; 579 lrb.start_rev = start_rev; 580 lrb.range_end = start_rev; 581 lrb.receiver = receiver; 582 lrb.receiver_baton = receiver_baton; 583 lrb.pool = pool; 584 585 /* Let the RA layer drive our log information handler, which will do 586 the work of finding the actual locations for our resource. 587 Notice that we always run on the youngest rev of the 3 inputs. */ 588 targets = apr_array_make(pool, 1, sizeof(const char *)); 589 APR_ARRAY_PUSH(targets, const char *) = path; 590 SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0, 591 TRUE, FALSE, FALSE, 592 apr_array_make(pool, 0, sizeof(const char *)), 593 gls_log_receiver, &lrb, pool)); 594 595 /* If we didn't finish, we need to do so with a final segment send. */ 596 if (! lrb.done) 597 SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev, 598 end_rev, lrb.range_end, 599 receiver, receiver_baton, pool)); 600 601 return SVN_NO_ERROR; 602} 603 604 605 606/*** Fallback implementation of svn_ra_get_file_revs(). ***/ 607 608/* The metadata associated with a particular revision. */ 609struct rev 610{ 611 svn_revnum_t revision; /* the revision number */ 612 const char *path; /* the absolute repository path */ 613 apr_hash_t *props; /* the revprops for this revision */ 614 struct rev *next; /* the next revision */ 615}; 616 617/* File revs log message baton. */ 618struct fr_log_message_baton { 619 const char *path; /* The path to be processed */ 620 struct rev *eldest; /* The eldest revision processed */ 621 char action; /* The action associated with the eldest */ 622 svn_revnum_t copyrev; /* The revision the eldest was copied from */ 623 apr_pool_t *pool; 624}; 625 626/* Callback for log messages: implements svn_log_entry_receiver_t and 627 accumulates revision metadata into a chronologically ordered list stored in 628 the baton. */ 629static svn_error_t * 630fr_log_message_receiver(void *baton, 631 svn_log_entry_t *log_entry, 632 apr_pool_t *pool) 633{ 634 struct fr_log_message_baton *lmb = baton; 635 struct rev *rev; 636 637 rev = apr_palloc(lmb->pool, sizeof(*rev)); 638 rev->revision = log_entry->revision; 639 rev->path = lmb->path; 640 rev->next = lmb->eldest; 641 lmb->eldest = rev; 642 643 /* Duplicate log_entry revprops into rev->props */ 644 rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool); 645 646 return prev_log_path(&lmb->path, &lmb->action, 647 &lmb->copyrev, log_entry->changed_paths2, 648 lmb->path, svn_node_file, log_entry->revision, 649 lmb->pool); 650} 651 652svn_error_t * 653svn_ra__file_revs_from_log(svn_ra_session_t *ra_session, 654 const char *path, 655 svn_revnum_t start, 656 svn_revnum_t end, 657 svn_file_rev_handler_t handler, 658 void *handler_baton, 659 apr_pool_t *pool) 660{ 661 svn_node_kind_t kind; 662 const char *repos_url, *session_url, *fs_path; 663 apr_array_header_t *condensed_targets; 664 struct fr_log_message_baton lmb; 665 struct rev *rev; 666 apr_hash_t *last_props; 667 svn_stream_t *last_stream; 668 apr_pool_t *currpool, *lastpool; 669 670 /* Fetch the absolute FS path associated with PATH. */ 671 SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); 672 673 /* Check to make sure we're dealing with a file. */ 674 SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); 675 if (kind == svn_node_dir) 676 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 677 _("'%s' is not a file"), fs_path); 678 679 condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); 680 APR_ARRAY_PUSH(condensed_targets, const char *) = path; 681 682 lmb.path = fs_path; 683 lmb.eldest = NULL; 684 lmb.pool = pool; 685 686 /* Accumulate revision metadata by walking the revisions 687 backwards; this allows us to follow moves/copies 688 correctly. */ 689 SVN_ERR(svn_ra_get_log2(ra_session, 690 condensed_targets, 691 end, start, 0, /* no limit */ 692 TRUE, FALSE, FALSE, 693 NULL, fr_log_message_receiver, &lmb, 694 pool)); 695 696 /* Reparent the session while we go back through the history. */ 697 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); 698 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); 699 SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); 700 701 currpool = svn_pool_create(pool); 702 lastpool = svn_pool_create(pool); 703 704 /* We want the first txdelta to be against the empty file. */ 705 last_props = apr_hash_make(lastpool); 706 last_stream = svn_stream_empty(lastpool); 707 708 /* Walk the revision list in chronological order, downloading each fulltext, 709 diffing it with its predecessor, and calling the file_revs handler for 710 each one. Use two iteration pools rather than one, because the diff 711 routines need to look at a sliding window of revisions. Two pools gives 712 us a ring buffer of sorts. */ 713 for (rev = lmb.eldest; rev; rev = rev->next) 714 { 715 const char *temp_path; 716 apr_pool_t *tmppool; 717 apr_hash_t *props; 718 apr_file_t *file; 719 svn_stream_t *stream; 720 apr_array_header_t *prop_diffs; 721 svn_txdelta_stream_t *delta_stream; 722 svn_txdelta_window_handler_t delta_handler = NULL; 723 void *delta_baton = NULL; 724 725 svn_pool_clear(currpool); 726 727 /* Get the contents of the file from the repository, and put them in 728 a temporary local file. */ 729 SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, 730 svn_io_file_del_on_pool_cleanup, 731 currpool, currpool)); 732 SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, 733 stream, NULL, &props, currpool)); 734 SVN_ERR(svn_stream_close(stream)); 735 736 /* Open up a stream to the local file. */ 737 SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, 738 currpool)); 739 stream = svn_stream_from_aprfile2(file, FALSE, currpool); 740 741 /* Calculate the property diff */ 742 SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); 743 744 /* Call the file_rev handler */ 745 SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, 746 FALSE, /* merged revision */ 747 &delta_handler, &delta_baton, prop_diffs, lastpool)); 748 749 /* Compute and send delta if client asked for it. */ 750 if (delta_handler) 751 { 752 /* Get the content delta. Don't calculate checksums as we don't 753 * use them. */ 754 svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); 755 756 /* And send. */ 757 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, 758 delta_baton, lastpool)); 759 } 760 761 /* Switch the pools and data for the next iteration */ 762 tmppool = currpool; 763 currpool = lastpool; 764 lastpool = tmppool; 765 766 SVN_ERR(svn_stream_close(last_stream)); 767 last_stream = stream; 768 last_props = props; 769 } 770 771 SVN_ERR(svn_stream_close(last_stream)); 772 svn_pool_destroy(currpool); 773 svn_pool_destroy(lastpool); 774 775 /* Reparent the session back to the original URL. */ 776 return svn_ra_reparent(ra_session, session_url, pool); 777} 778 779 780/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/ 781 782/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */ 783typedef struct log_path_del_rev_t 784{ 785 /* Absolute repository path. */ 786 const char *path; 787 788 /* Revision PATH was first deleted or replaced. */ 789 svn_revnum_t revision_deleted; 790} log_path_del_rev_t; 791 792/* A svn_log_entry_receiver_t callback for finding the revision 793 ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced. 794 Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED. 795 */ 796static svn_error_t * 797log_path_del_receiver(void *baton, 798 svn_log_entry_t *log_entry, 799 apr_pool_t *pool) 800{ 801 log_path_del_rev_t *b = baton; 802 apr_hash_index_t *hi; 803 804 /* No paths were changed in this revision. Nothing to do. */ 805 if (! log_entry->changed_paths2) 806 return SVN_NO_ERROR; 807 808 for (hi = apr_hash_first(pool, log_entry->changed_paths2); 809 hi != NULL; 810 hi = apr_hash_next(hi)) 811 { 812 void *val; 813 char *path; 814 svn_log_changed_path_t *log_item; 815 816 apr_hash_this(hi, (void *) &path, NULL, &val); 817 log_item = val; 818 if (svn_path_compare_paths(b->path, path) == 0 819 && (log_item->action == 'D' || log_item->action == 'R')) 820 { 821 /* Found the first deletion or replacement, we are done. */ 822 b->revision_deleted = log_entry->revision; 823 break; 824 } 825 } 826 return SVN_NO_ERROR; 827} 828 829svn_error_t * 830svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session, 831 const char *rel_deleted_path, 832 svn_revnum_t peg_revision, 833 svn_revnum_t end_revision, 834 svn_revnum_t *revision_deleted, 835 apr_pool_t *pool) 836{ 837 const char *fs_path; 838 log_path_del_rev_t log_path_deleted_baton; 839 840 /* Fetch the absolute FS path associated with PATH. */ 841 SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool)); 842 843 if (!SVN_IS_VALID_REVNUM(peg_revision)) 844 return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, 845 _("Invalid peg revision %ld"), peg_revision); 846 if (!SVN_IS_VALID_REVNUM(end_revision)) 847 return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, 848 _("Invalid end revision %ld"), end_revision); 849 if (end_revision <= peg_revision) 850 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 851 _("Peg revision must precede end revision")); 852 853 log_path_deleted_baton.path = fs_path; 854 log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM; 855 856 /* Examine the logs of SESSION's URL to find when DELETED_PATH was first 857 deleted or replaced. */ 858 SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0, 859 TRUE, TRUE, FALSE, 860 apr_array_make(pool, 0, sizeof(char *)), 861 log_path_del_receiver, &log_path_deleted_baton, 862 pool)); 863 *revision_deleted = log_path_deleted_baton.revision_deleted; 864 return SVN_NO_ERROR; 865} 866 867 868svn_error_t * 869svn_ra__get_inherited_props_walk(svn_ra_session_t *session, 870 const char *path, 871 svn_revnum_t revision, 872 apr_array_header_t **inherited_props, 873 apr_pool_t *result_pool, 874 apr_pool_t *scratch_pool) 875{ 876 const char *repos_root_url; 877 const char *session_url; 878 const char *parent_url; 879 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 880 881 *inherited_props = 882 apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); 883 884 /* Walk to the root of the repository getting inherited 885 props for PATH. */ 886 SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool)); 887 SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool)); 888 parent_url = session_url; 889 890 while (strcmp(repos_root_url, parent_url)) 891 { 892 apr_hash_index_t *hi; 893 apr_hash_t *parent_props; 894 apr_hash_t *final_hash = apr_hash_make(result_pool); 895 svn_error_t *err; 896 897 svn_pool_clear(iterpool); 898 parent_url = svn_uri_dirname(parent_url, scratch_pool); 899 SVN_ERR(svn_ra_reparent(session, parent_url, iterpool)); 900 err = session->vtable->get_dir(session, NULL, NULL, 901 &parent_props, "", 902 revision, SVN_DIRENT_ALL, 903 iterpool); 904 905 /* If the user doesn't have read access to a parent path then 906 skip, but allow them to inherit from further up. */ 907 if (err) 908 { 909 if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) 910 || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)) 911 { 912 svn_error_clear(err); 913 continue; 914 } 915 else 916 { 917 return svn_error_trace(err); 918 } 919 } 920 921 for (hi = apr_hash_first(scratch_pool, parent_props); 922 hi; 923 hi = apr_hash_next(hi)) 924 { 925 const char *name = apr_hash_this_key(hi); 926 apr_ssize_t klen = apr_hash_this_key_len(hi); 927 svn_string_t *value = apr_hash_this_val(hi); 928 929 if (svn_property_kind2(name) == svn_prop_regular_kind) 930 { 931 name = apr_pstrdup(result_pool, name); 932 value = svn_string_dup(value, result_pool); 933 apr_hash_set(final_hash, name, klen, value); 934 } 935 } 936 937 if (apr_hash_count(final_hash)) 938 { 939 svn_prop_inherited_item_t *new_iprop = 940 apr_palloc(result_pool, sizeof(*new_iprop)); 941 new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url, 942 parent_url, 943 result_pool); 944 new_iprop->prop_hash = final_hash; 945 SVN_ERR(svn_sort__array_insert2(*inherited_props, &new_iprop, 0)); 946 } 947 } 948 949 /* Reparent session back to original URL. */ 950 SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool)); 951 952 svn_pool_destroy(iterpool); 953 return SVN_NO_ERROR; 954} 955