2 * compat.c: compatibility compliance logic
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include <apr_pools.h>
27 #include "svn_error.h"
28 #include "svn_pools.h"
29 #include "svn_sorts.h"
30 #include "svn_dirent_uri.h"
34 #include "svn_compat.h"
35 #include "svn_props.h"
37 #include "private/svn_fspath.h"
38 #include "private/svn_sorts_private.h"
39 #include "ra_loader.h"
40 #include "svn_private_config.h"
44 /* This is just like svn_sort_compare_revisions, save that it sorts
45 the revisions in *ascending* order. */
47 compare_revisions(const void *a, const void *b)
49 svn_revnum_t a_rev = *(const svn_revnum_t *)a;
50 svn_revnum_t b_rev = *(const svn_revnum_t *)b;
53 return a_rev < b_rev ? -1 : 1;
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.
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.
71 prev_log_path(const char **prev_path_p,
73 svn_revnum_t *copyfrom_rev_p,
74 apr_hash_t *changed_paths,
77 svn_revnum_t revision,
80 svn_log_changed_path_t *change;
81 const char *prev_path = NULL;
83 /* It's impossible to find the predecessor path of a NULL path. */
86 /* Initialize our return values for the action and copyfrom_rev in
87 case we have an unhandled case later on. */
91 *copyfrom_rev_p = SVN_INVALID_REVNUM;
95 /* See if PATH was explicitly changed in this revision. */
96 change = svn_hash_gets(changed_paths, path);
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')
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);
116 *prev_path_p = prev_path;
118 *action_p = change->action;
120 *copyfrom_rev_p = change->copyfrom_rev;
125 if (apr_hash_count(changed_paths))
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. */
133 apr_array_header_t *paths;
135 /* Build a sorted list of the changed paths. */
136 paths = svn_sort__hash(changed_paths,
137 svn_sort_compare_items_as_paths, pool);
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--)
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);
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] == '/')))
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
160 if (change->copyfrom_path)
163 *action_p = change->action;
165 *copyfrom_rev_p = change->copyfrom_rev;
166 prev_path = svn_fspath__join(change->copyfrom_path,
167 path + len + 1, pool);
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.) */
179 if (kind == svn_node_dir)
180 prev_path = apr_pstrdup(pool, path);
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);
188 *prev_path_p = prev_path;
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. */
197 get_fs_path(const char **fs_path_p,
198 svn_ra_session_t *session,
199 const char *rel_path,
202 const char *url, *fs_path;
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,
214 /*** Fallback implementation of svn_ra_get_locations(). ***/
217 /* ### This is to support 1.0 servers. */
218 struct log_receiver_baton
220 /* The kind of the path we're tracing. */
221 svn_node_kind_t kind;
223 /* The path at which we are trying to find our versioned resource in
225 const char *last_path;
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;
233 /* A pool from which to allocate stuff stored in this baton. */
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
243 log_receiver(void *baton,
244 svn_log_entry_t *log_entry,
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;
252 /* No paths were changed in this revision. Nothing to do. */
253 if (! log_entry->changed_paths2)
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.) */
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);
270 /* Determine the paths for any of the revisions for which we haven't
271 gotten paths already. */
272 while (lrb->location_revisions->nelts)
274 svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
275 lrb->location_revisions->nelts - 1,
277 if (log_entry->revision <= next)
279 apr_hash_set(lrb->locations,
280 apr_pmemdup(hash_pool, &next, sizeof(next)),
282 apr_pstrdup(hash_pool, current_path));
283 apr_array_pop(lrb->location_revisions);
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));
294 /* Squirrel away our "next place to look" path (suffer the strcmp
295 hit to save on allocations). */
297 lrb->last_path = NULL;
298 else if (strcmp(prev_path, current_path) != 0)
299 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
306 svn_ra__locations_from_log(svn_ra_session_t *session,
307 apr_hash_t **locations_p,
309 svn_revnum_t peg_revision,
310 const apr_array_header_t *location_revisions,
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;
319 apr_array_header_t *sorted_location_revisions;
321 /* Fetch the absolute FS path associated with PATH. */
322 SVN_ERR(get_fs_path(&fs_path, session, path, pool));
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);
331 /* Easy out: no location revisions. */
332 if (! location_revisions->nelts)
334 *locations_p = locations;
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,
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;
354 /* Populate most of our log receiver baton structure. */
356 lrb.last_path = fs_path;
357 lrb.location_revisions = apr_array_copy(pool, sorted_location_revisions);
358 lrb.peg_revision = peg_revision;
360 lrb.locations = locations;
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,
370 apr_array_make(pool, 0, sizeof(const char *)),
371 log_receiver, &lrb, pool));
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.) */
379 lrb.peg_path = lrb.last_path;
383 for (i = 0; i < sorted_location_revisions->nelts; i++)
385 svn_revnum_t rev = APR_ARRAY_IDX(sorted_location_revisions, i,
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));
393 /* Check that we got the peg path. */
395 return svn_error_createf
397 _("Unable to find repository location for '%s' in revision %ld"),
398 fs_path, peg_revision);
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"),
408 *locations_p = locations;
415 /*** Fallback implementation of svn_ra_get_location_segments(). ***/
417 struct gls_log_receiver_baton {
418 /* The kind of the path we're tracing. */
419 svn_node_kind_t kind;
421 /* Are we finished (and just listening to log entries because our
422 caller won't shut up?). */
425 /* The path at which we are trying to find our versioned resource in
427 const char *last_path;
430 svn_revnum_t start_rev;
432 /* Output intermediate state and callback/baton. */
433 svn_revnum_t range_end;
434 svn_location_segment_receiver_t receiver;
435 void *receiver_baton;
437 /* A pool from which to allocate stuff stored in this baton. */
441 /* Build a node location segment object from PATH, RANGE_START, and
442 RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
444 maybe_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,
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)
458 if (segment->range_end > start_rev)
459 segment->range_end = start_rev;
460 return receiver(segment, receiver_baton, pool);
466 gls_log_receiver(void *baton,
467 svn_log_entry_t *log_entry,
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;
475 /* If we're done, ignore this invocation. */
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));
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). */
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,
497 /* If there was a copy operation of interest... */
498 if (SVN_IS_VALID_REVNUM(copyfrom_rev))
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,
506 lrb->range_end = log_entry->revision - 1;
508 /* And if there was a revision gap, we need to report that, too. */
509 if (log_entry->revision - copyfrom_rev > 1)
511 SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
512 copyfrom_rev + 1, lrb->range_end,
514 lrb->receiver_baton, pool));
515 lrb->range_end = copyfrom_rev;
518 /* Update our state variables. */
519 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
527 svn_ra__location_segments_from_log(svn_ra_session_t *session,
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,
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;
542 /* Fetch the absolute FS path associated with PATH. */
543 SVN_ERR(get_fs_path(&fs_path, session, path, pool));
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
548 if (! SVN_IS_VALID_REVNUM(peg_revision))
550 SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
551 peg_revision = youngest_rev;
553 if (! SVN_IS_VALID_REVNUM(start_rev))
555 if (SVN_IS_VALID_REVNUM(youngest_rev))
556 start_rev = youngest_rev;
558 SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
560 if (! SVN_IS_VALID_REVNUM(end_rev))
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));
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"),
575 /* Populate most of our log receiver baton structure. */
577 lrb.last_path = fs_path;
579 lrb.start_rev = start_rev;
580 lrb.range_end = start_rev;
581 lrb.receiver = receiver;
582 lrb.receiver_baton = receiver_baton;
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,
592 apr_array_make(pool, 0, sizeof(const char *)),
593 gls_log_receiver, &lrb, pool));
595 /* If we didn't finish, we need to do so with a final segment send. */
597 SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
598 end_rev, lrb.range_end,
599 receiver, receiver_baton, pool));
606 /*** Fallback implementation of svn_ra_get_file_revs(). ***/
608 /* The metadata associated with a particular revision. */
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 */
617 /* File revs log message baton. */
618 struct 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 */
626 /* Callback for log messages: implements svn_log_entry_receiver_t and
627 accumulates revision metadata into a chronologically ordered list stored in
630 fr_log_message_receiver(void *baton,
631 svn_log_entry_t *log_entry,
634 struct fr_log_message_baton *lmb = baton;
637 rev = apr_palloc(lmb->pool, sizeof(*rev));
638 rev->revision = log_entry->revision;
639 rev->path = lmb->path;
640 rev->next = lmb->eldest;
643 /* Duplicate log_entry revprops into rev->props */
644 rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);
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,
653 svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
657 svn_file_rev_handler_t handler,
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;
666 apr_hash_t *last_props;
667 svn_stream_t *last_stream;
668 apr_pool_t *currpool, *lastpool;
670 /* Fetch the absolute FS path associated with PATH. */
671 SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));
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);
679 condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
680 APR_ARRAY_PUSH(condensed_targets, const char *) = path;
686 /* Accumulate revision metadata by walking the revisions
687 backwards; this allows us to follow moves/copies
689 SVN_ERR(svn_ra_get_log2(ra_session,
691 end, start, 0, /* no limit */
693 NULL, fr_log_message_receiver, &lmb,
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));
701 currpool = svn_pool_create(pool);
702 lastpool = svn_pool_create(pool);
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);
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)
715 const char *temp_path;
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;
725 svn_pool_clear(currpool);
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));
736 /* Open up a stream to the local file. */
737 SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
739 stream = svn_stream_from_aprfile2(file, FALSE, currpool);
741 /* Calculate the property diff */
742 SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
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));
749 /* Compute and send delta if client asked for it. */
752 /* Get the content delta. Don't calculate checksums as we don't
754 svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);
757 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
758 delta_baton, lastpool));
761 /* Switch the pools and data for the next iteration */
766 SVN_ERR(svn_stream_close(last_stream));
767 last_stream = stream;
771 SVN_ERR(svn_stream_close(last_stream));
772 svn_pool_destroy(currpool);
773 svn_pool_destroy(lastpool);
775 /* Reparent the session back to the original URL. */
776 return svn_ra_reparent(ra_session, session_url, pool);
780 /*** Fallback implementation of svn_ra_get_deleted_rev(). ***/
782 /* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
783 typedef struct log_path_del_rev_t
785 /* Absolute repository path. */
788 /* Revision PATH was first deleted or replaced. */
789 svn_revnum_t revision_deleted;
790 } log_path_del_rev_t;
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.
797 log_path_del_receiver(void *baton,
798 svn_log_entry_t *log_entry,
801 log_path_del_rev_t *b = baton;
802 apr_hash_index_t *hi;
804 /* No paths were changed in this revision. Nothing to do. */
805 if (! log_entry->changed_paths2)
808 for (hi = apr_hash_first(pool, log_entry->changed_paths2);
810 hi = apr_hash_next(hi))
814 svn_log_changed_path_t *log_item;
816 apr_hash_this(hi, (void *) &path, NULL, &val);
818 if (svn_path_compare_paths(b->path, path) == 0
819 && (log_item->action == 'D' || log_item->action == 'R'))
821 /* Found the first deletion or replacement, we are done. */
822 b->revision_deleted = log_entry->revision;
830 svn_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,
838 log_path_del_rev_t log_path_deleted_baton;
840 /* Fetch the absolute FS path associated with PATH. */
841 SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));
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"));
853 log_path_deleted_baton.path = fs_path;
854 log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;
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,
860 apr_array_make(pool, 0, sizeof(char *)),
861 log_path_del_receiver, &log_path_deleted_baton,
863 *revision_deleted = log_path_deleted_baton.revision_deleted;
869 svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
871 svn_revnum_t revision,
872 apr_array_header_t **inherited_props,
873 apr_pool_t *result_pool,
874 apr_pool_t *scratch_pool)
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);
882 apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
884 /* Walk to the root of the repository getting inherited
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;
890 while (strcmp(repos_root_url, parent_url))
892 apr_hash_index_t *hi;
893 apr_hash_t *parent_props;
894 apr_hash_t *final_hash = apr_hash_make(result_pool);
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,
902 revision, SVN_DIRENT_ALL,
905 /* If the user doesn't have read access to a parent path then
906 skip, but allow them to inherit from further up. */
909 if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
910 || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
912 svn_error_clear(err);
917 return svn_error_trace(err);
921 for (hi = apr_hash_first(scratch_pool, parent_props);
923 hi = apr_hash_next(hi))
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);
929 if (svn_property_kind2(name) == svn_prop_regular_kind)
931 name = apr_pstrdup(result_pool, name);
932 value = svn_string_dup(value, result_pool);
933 apr_hash_set(final_hash, name, klen, value);
937 if (apr_hash_count(final_hash))
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,
944 new_iprop->prop_hash = final_hash;
945 svn_sort__array_insert(*inherited_props, &new_iprop, 0);
949 /* Reparent session back to original URL. */
950 SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
952 svn_pool_destroy(iterpool);