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 "ra_loader.h"
39 #include "svn_private_config.h"
43 /* This is just like svn_sort_compare_revisions, save that it sorts
44 the revisions in *ascending* order. */
46 compare_revisions(const void *a, const void *b)
48 svn_revnum_t a_rev = *(const svn_revnum_t *)a;
49 svn_revnum_t b_rev = *(const svn_revnum_t *)b;
52 return a_rev < b_rev ? -1 : 1;
55 /* Given the CHANGED_PATHS and REVISION from an instance of a
56 svn_log_message_receiver_t function, determine at which location
57 PATH may be expected in the next log message, and set *PREV_PATH_P
58 to that value. KIND is the node kind of PATH. Set *ACTION_P to a
59 character describing the change that caused this revision (as
60 listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
61 revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
62 copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case
63 they are not used. Perform all allocations in POOL.
65 This is useful for tracking the various changes in location a
66 particular resource has undergone when performing an RA->get_logs()
67 operation on that resource.
70 prev_log_path(const char **prev_path_p,
72 svn_revnum_t *copyfrom_rev_p,
73 apr_hash_t *changed_paths,
76 svn_revnum_t revision,
79 svn_log_changed_path_t *change;
80 const char *prev_path = NULL;
82 /* It's impossible to find the predecessor path of a NULL path. */
85 /* Initialize our return values for the action and copyfrom_rev in
86 case we have an unhandled case later on. */
90 *copyfrom_rev_p = SVN_INVALID_REVNUM;
94 /* See if PATH was explicitly changed in this revision. */
95 change = svn_hash_gets(changed_paths, path);
98 /* If PATH was not newly added in this revision, then it may or may
99 not have also been part of a moved subtree. In this case, set a
100 default previous path, but still look through the parents of this
101 path for a possible copy event. */
102 if (change->action != 'A' && change->action != 'R')
108 /* PATH is new in this revision. This means it cannot have been
109 part of a copied subtree. */
110 if (change->copyfrom_path)
111 prev_path = apr_pstrdup(pool, change->copyfrom_path);
115 *prev_path_p = prev_path;
117 *action_p = change->action;
119 *copyfrom_rev_p = change->copyfrom_rev;
124 if (apr_hash_count(changed_paths))
126 /* The path was not explicitly changed in this revision. The
127 fact that we're hearing about this revision implies, then,
128 that the path was a child of some copied directory. We need
129 to find that directory, and effectively "re-base" our path on
130 that directory's copyfrom_path. */
132 apr_array_header_t *paths;
134 /* Build a sorted list of the changed paths. */
135 paths = svn_sort__hash(changed_paths,
136 svn_sort_compare_items_as_paths, pool);
138 /* Now, walk the list of paths backwards, looking a parent of
139 our path that has copyfrom information. */
140 for (i = paths->nelts; i > 0; i--)
142 svn_sort__item_t item = APR_ARRAY_IDX(paths,
143 i - 1, svn_sort__item_t);
144 const char *ch_path = item.key;
145 size_t len = strlen(ch_path);
147 /* See if our path is the child of this change path. If
148 not, keep looking. */
149 if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
152 /* Okay, our path *is* a child of this change path. If
153 this change was copied, we just need to apply the
154 portion of our path that is relative to this change's
155 path, to the change's copyfrom path. Otherwise, this
156 change isn't really interesting to us, and our search
159 if (change->copyfrom_path)
162 *action_p = change->action;
164 *copyfrom_rev_p = change->copyfrom_rev;
165 prev_path = svn_fspath__join(change->copyfrom_path,
166 path + len + 1, pool);
173 /* If we didn't find what we expected to find, return an error.
174 (Because directories bubble-up, we get a bunch of logs we might
175 not want. Be forgiving in that case.) */
178 if (kind == svn_node_dir)
179 prev_path = apr_pstrdup(pool, path);
181 return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
182 _("Missing changed-path information for "
183 "'%s' in revision %ld"),
184 svn_dirent_local_style(path, pool), revision);
187 *prev_path_p = prev_path;
192 /* Set *FS_PATH_P to the absolute filesystem path associated with the
193 URL built from SESSION's URL and REL_PATH (which is relative to
194 session's URL. Use POOL for allocations. */
196 get_fs_path(const char **fs_path_p,
197 svn_ra_session_t *session,
198 const char *rel_path,
201 const char *url, *fs_path;
203 SVN_ERR(svn_ra_get_session_url(session, &url, pool));
204 SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool));
205 *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path,
213 /*** Fallback implementation of svn_ra_get_locations(). ***/
216 /* ### This is to support 1.0 servers. */
217 struct log_receiver_baton
219 /* The kind of the path we're tracing. */
220 svn_node_kind_t kind;
222 /* The path at which we are trying to find our versioned resource in
224 const char *last_path;
226 /* Input revisions and output hash; the whole point of this little game. */
227 svn_revnum_t peg_revision;
228 apr_array_header_t *location_revisions;
229 const char *peg_path;
230 apr_hash_t *locations;
232 /* A pool from which to allocate stuff stored in this baton. */
237 /* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
238 As input, takes log_receiver_baton (defined above) and attempts to
239 "fill in" locations in the baton over the course of many
242 log_receiver(void *baton,
243 svn_log_entry_t *log_entry,
246 struct log_receiver_baton *lrb = baton;
247 apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
248 const char *current_path = lrb->last_path;
249 const char *prev_path;
251 /* No paths were changed in this revision. Nothing to do. */
252 if (! log_entry->changed_paths2)
255 /* If we've run off the end of the path's history, there's nothing
256 to do. (This should never happen with a properly functioning
257 server, since we'd get no more log messages after the one where
258 path was created. But a malfunctioning server shouldn't cause us
259 to trigger an assertion failure.) */
263 /* If we haven't found our peg path yet, and we are now looking at a
264 revision equal to or older than the peg revision, then our
265 "current" path is our peg path. */
266 if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
267 lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
269 /* Determine the paths for any of the revisions for which we haven't
270 gotten paths already. */
271 while (lrb->location_revisions->nelts)
273 svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
274 lrb->location_revisions->nelts - 1,
276 if (log_entry->revision <= next)
278 apr_hash_set(lrb->locations,
279 apr_pmemdup(hash_pool, &next, sizeof(next)),
281 apr_pstrdup(hash_pool, current_path));
282 apr_array_pop(lrb->location_revisions);
288 /* Figure out at which repository path our object of interest lived
289 in the previous revision. */
290 SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2,
291 current_path, lrb->kind, log_entry->revision, pool));
293 /* Squirrel away our "next place to look" path (suffer the strcmp
294 hit to save on allocations). */
296 lrb->last_path = NULL;
297 else if (strcmp(prev_path, current_path) != 0)
298 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
305 svn_ra__locations_from_log(svn_ra_session_t *session,
306 apr_hash_t **locations_p,
308 svn_revnum_t peg_revision,
309 const apr_array_header_t *location_revisions,
312 apr_hash_t *locations = apr_hash_make(pool);
313 struct log_receiver_baton lrb = { 0 };
314 apr_array_header_t *targets;
315 svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
316 svn_node_kind_t kind;
319 /* Fetch the absolute FS path associated with PATH. */
320 SVN_ERR(get_fs_path(&fs_path, session, path, pool));
322 /* Sanity check: verify that the peg-object exists in repos. */
323 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
324 if (kind == svn_node_none)
325 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
326 _("Path '%s' doesn't exist in revision %ld"),
327 fs_path, peg_revision);
329 /* Easy out: no location revisions. */
330 if (! location_revisions->nelts)
332 *locations_p = locations;
336 /* Figure out the youngest and oldest revs (amongst the set of
337 requested revisions + the peg revision) so we can avoid
338 unnecessary log parsing. */
339 qsort(location_revisions->elts, location_revisions->nelts,
340 location_revisions->elt_size, compare_revisions);
341 oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t);
342 youngest_requested = APR_ARRAY_IDX(location_revisions,
343 location_revisions->nelts - 1,
345 youngest = peg_revision;
346 youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
347 youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
348 oldest = peg_revision;
349 oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
350 oldest = (youngest_requested < oldest) ? youngest_requested : oldest;
352 /* Populate most of our log receiver baton structure. */
354 lrb.last_path = fs_path;
355 lrb.location_revisions = apr_array_copy(pool, location_revisions);
356 lrb.peg_revision = peg_revision;
358 lrb.locations = locations;
361 /* Let the RA layer drive our log information handler, which will do
362 the work of finding the actual locations for our resource.
363 Notice that we always run on the youngest rev of the 3 inputs. */
364 targets = apr_array_make(pool, 1, sizeof(const char *));
365 APR_ARRAY_PUSH(targets, const char *) = path;
366 SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
368 apr_array_make(pool, 0, sizeof(const char *)),
369 log_receiver, &lrb, pool));
371 /* If the received log information did not cover any of the
372 requested revisions, use the last known path. (This normally
373 just means that FS_PATH was not modified between the requested
374 revision and OLDEST. If the file was created at some point after
375 OLDEST, then lrb.last_path should be NULL.) */
377 lrb.peg_path = lrb.last_path;
381 for (i = 0; i < location_revisions->nelts; i++)
383 svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i,
385 if (! apr_hash_get(locations, &rev, sizeof(rev)))
386 apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
387 sizeof(rev), apr_pstrdup(pool, lrb.last_path));
391 /* Check that we got the peg path. */
393 return svn_error_createf
395 _("Unable to find repository location for '%s' in revision %ld"),
396 fs_path, peg_revision);
398 /* Sanity check: make sure that our calculated peg path is the same
399 as what we expected it to be. */
400 if (strcmp(fs_path, lrb.peg_path) != 0)
401 return svn_error_createf
402 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
403 _("'%s' in revision %ld is an unrelated object"),
406 *locations_p = locations;
413 /*** Fallback implementation of svn_ra_get_location_segments(). ***/
415 struct gls_log_receiver_baton {
416 /* The kind of the path we're tracing. */
417 svn_node_kind_t kind;
419 /* Are we finished (and just listening to log entries because our
420 caller won't shut up?). */
423 /* The path at which we are trying to find our versioned resource in
425 const char *last_path;
428 svn_revnum_t start_rev;
430 /* Output intermediate state and callback/baton. */
431 svn_revnum_t range_end;
432 svn_location_segment_receiver_t receiver;
433 void *receiver_baton;
435 /* A pool from which to allocate stuff stored in this baton. */
439 /* Build a node location segment object from PATH, RANGE_START, and
440 RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
442 maybe_crop_and_send_segment(const char *path,
443 svn_revnum_t start_rev,
444 svn_revnum_t range_start,
445 svn_revnum_t range_end,
446 svn_location_segment_receiver_t receiver,
447 void *receiver_baton,
450 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
451 segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
452 segment->range_start = range_start;
453 segment->range_end = range_end;
454 if (segment->range_start <= start_rev)
456 if (segment->range_end > start_rev)
457 segment->range_end = start_rev;
458 return receiver(segment, receiver_baton, pool);
464 gls_log_receiver(void *baton,
465 svn_log_entry_t *log_entry,
468 struct gls_log_receiver_baton *lrb = baton;
469 const char *current_path = lrb->last_path;
470 const char *prev_path;
471 svn_revnum_t copyfrom_rev;
473 /* If we're done, ignore this invocation. */
477 /* Figure out at which repository path our object of interest lived
478 in the previous revision, and if its current location is the
479 result of copy since then. */
480 SVN_ERR(prev_log_path(&prev_path, NULL, ©from_rev,
481 log_entry->changed_paths2, current_path,
482 lrb->kind, log_entry->revision, pool));
484 /* If we've run off the end of the path's history, we need to report
485 our final segment (and then, we're done). */
489 return maybe_crop_and_send_segment(current_path, lrb->start_rev,
490 log_entry->revision, lrb->range_end,
491 lrb->receiver, lrb->receiver_baton,
495 /* If there was a copy operation of interest... */
496 if (SVN_IS_VALID_REVNUM(copyfrom_rev))
498 /* ...then report the segment between this revision and the
499 last-reported revision. */
500 SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
501 log_entry->revision, lrb->range_end,
502 lrb->receiver, lrb->receiver_baton,
504 lrb->range_end = log_entry->revision - 1;
506 /* And if there was a revision gap, we need to report that, too. */
507 if (log_entry->revision - copyfrom_rev > 1)
509 SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
510 copyfrom_rev + 1, lrb->range_end,
512 lrb->receiver_baton, pool));
513 lrb->range_end = copyfrom_rev;
516 /* Update our state variables. */
517 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
525 svn_ra__location_segments_from_log(svn_ra_session_t *session,
527 svn_revnum_t peg_revision,
528 svn_revnum_t start_rev,
529 svn_revnum_t end_rev,
530 svn_location_segment_receiver_t receiver,
531 void *receiver_baton,
534 struct gls_log_receiver_baton lrb = { 0 };
535 apr_array_header_t *targets;
536 svn_node_kind_t kind;
537 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
540 /* Fetch the absolute FS path associated with PATH. */
541 SVN_ERR(get_fs_path(&fs_path, session, path, pool));
543 /* If PEG_REVISION is invalid, it means HEAD. If START_REV is
544 invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll
546 if (! SVN_IS_VALID_REVNUM(peg_revision))
548 SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
549 peg_revision = youngest_rev;
551 if (! SVN_IS_VALID_REVNUM(start_rev))
553 if (SVN_IS_VALID_REVNUM(youngest_rev))
554 start_rev = youngest_rev;
556 SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
558 if (! SVN_IS_VALID_REVNUM(end_rev))
563 /* The API demands a certain ordering of our revision inputs. Enforce it. */
564 SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev));
566 /* Sanity check: verify that the peg-object exists in repos. */
567 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
568 if (kind == svn_node_none)
569 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
570 _("Path '%s' doesn't exist in revision %ld"),
573 /* Populate most of our log receiver baton structure. */
575 lrb.last_path = fs_path;
577 lrb.start_rev = start_rev;
578 lrb.range_end = start_rev;
579 lrb.receiver = receiver;
580 lrb.receiver_baton = receiver_baton;
583 /* Let the RA layer drive our log information handler, which will do
584 the work of finding the actual locations for our resource.
585 Notice that we always run on the youngest rev of the 3 inputs. */
586 targets = apr_array_make(pool, 1, sizeof(const char *));
587 APR_ARRAY_PUSH(targets, const char *) = path;
588 SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
590 apr_array_make(pool, 0, sizeof(const char *)),
591 gls_log_receiver, &lrb, pool));
593 /* If we didn't finish, we need to do so with a final segment send. */
595 SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
596 end_rev, lrb.range_end,
597 receiver, receiver_baton, pool));
604 /*** Fallback implementation of svn_ra_get_file_revs(). ***/
606 /* The metadata associated with a particular revision. */
609 svn_revnum_t revision; /* the revision number */
610 const char *path; /* the absolute repository path */
611 apr_hash_t *props; /* the revprops for this revision */
612 struct rev *next; /* the next revision */
615 /* File revs log message baton. */
616 struct fr_log_message_baton {
617 const char *path; /* The path to be processed */
618 struct rev *eldest; /* The eldest revision processed */
619 char action; /* The action associated with the eldest */
620 svn_revnum_t copyrev; /* The revision the eldest was copied from */
624 /* Callback for log messages: implements svn_log_entry_receiver_t and
625 accumulates revision metadata into a chronologically ordered list stored in
628 fr_log_message_receiver(void *baton,
629 svn_log_entry_t *log_entry,
632 struct fr_log_message_baton *lmb = baton;
635 rev = apr_palloc(lmb->pool, sizeof(*rev));
636 rev->revision = log_entry->revision;
637 rev->path = lmb->path;
638 rev->next = lmb->eldest;
641 /* Duplicate log_entry revprops into rev->props */
642 rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);
644 return prev_log_path(&lmb->path, &lmb->action,
645 &lmb->copyrev, log_entry->changed_paths2,
646 lmb->path, svn_node_file, log_entry->revision,
651 svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
655 svn_file_rev_handler_t handler,
659 svn_node_kind_t kind;
660 const char *repos_url, *session_url, *fs_path;
661 apr_array_header_t *condensed_targets;
662 struct fr_log_message_baton lmb;
664 apr_hash_t *last_props;
665 svn_stream_t *last_stream;
666 apr_pool_t *currpool, *lastpool;
668 /* Fetch the absolute FS path associated with PATH. */
669 SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));
671 /* Check to make sure we're dealing with a file. */
672 SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool));
673 if (kind == svn_node_dir)
674 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
675 _("'%s' is not a file"), fs_path);
677 condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
678 APR_ARRAY_PUSH(condensed_targets, const char *) = path;
684 /* Accumulate revision metadata by walking the revisions
685 backwards; this allows us to follow moves/copies
687 SVN_ERR(svn_ra_get_log2(ra_session,
689 end, start, 0, /* no limit */
691 NULL, fr_log_message_receiver, &lmb,
694 /* Reparent the session while we go back through the history. */
695 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
696 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
697 SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));
699 currpool = svn_pool_create(pool);
700 lastpool = svn_pool_create(pool);
702 /* We want the first txdelta to be against the empty file. */
703 last_props = apr_hash_make(lastpool);
704 last_stream = svn_stream_empty(lastpool);
706 /* Walk the revision list in chronological order, downloading each fulltext,
707 diffing it with its predecessor, and calling the file_revs handler for
708 each one. Use two iteration pools rather than one, because the diff
709 routines need to look at a sliding window of revisions. Two pools gives
710 us a ring buffer of sorts. */
711 for (rev = lmb.eldest; rev; rev = rev->next)
713 const char *temp_path;
717 svn_stream_t *stream;
718 apr_array_header_t *prop_diffs;
719 svn_txdelta_stream_t *delta_stream;
720 svn_txdelta_window_handler_t delta_handler = NULL;
721 void *delta_baton = NULL;
723 svn_pool_clear(currpool);
725 /* Get the contents of the file from the repository, and put them in
726 a temporary local file. */
727 SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL,
728 svn_io_file_del_on_pool_cleanup,
729 currpool, currpool));
730 SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
731 stream, NULL, &props, currpool));
732 SVN_ERR(svn_stream_close(stream));
734 /* Open up a stream to the local file. */
735 SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
737 stream = svn_stream_from_aprfile2(file, FALSE, currpool);
739 /* Calculate the property diff */
740 SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
742 /* Call the file_rev handler */
743 SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
744 FALSE, /* merged revision */
745 &delta_handler, &delta_baton, prop_diffs, lastpool));
747 /* Compute and send delta if client asked for it. */
750 /* Get the content delta. Don't calculate checksums as we don't
752 svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);
755 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
756 delta_baton, lastpool));
759 /* Switch the pools and data for the next iteration */
764 SVN_ERR(svn_stream_close(last_stream));
765 last_stream = stream;
769 SVN_ERR(svn_stream_close(last_stream));
770 svn_pool_destroy(currpool);
771 svn_pool_destroy(lastpool);
773 /* Reparent the session back to the original URL. */
774 return svn_ra_reparent(ra_session, session_url, pool);
778 /*** Fallback implementation of svn_ra_get_deleted_rev(). ***/
780 /* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
781 typedef struct log_path_del_rev_t
783 /* Absolute repository path. */
786 /* Revision PATH was first deleted or replaced. */
787 svn_revnum_t revision_deleted;
788 } log_path_del_rev_t;
790 /* A svn_log_entry_receiver_t callback for finding the revision
791 ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced.
792 Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED.
795 log_path_del_receiver(void *baton,
796 svn_log_entry_t *log_entry,
799 log_path_del_rev_t *b = baton;
800 apr_hash_index_t *hi;
802 /* No paths were changed in this revision. Nothing to do. */
803 if (! log_entry->changed_paths2)
806 for (hi = apr_hash_first(pool, log_entry->changed_paths2);
808 hi = apr_hash_next(hi))
812 svn_log_changed_path_t *log_item;
814 apr_hash_this(hi, (void *) &path, NULL, &val);
816 if (svn_path_compare_paths(b->path, path) == 0
817 && (log_item->action == 'D' || log_item->action == 'R'))
819 /* Found the first deletion or replacement, we are done. */
820 b->revision_deleted = log_entry->revision;
828 svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
829 const char *rel_deleted_path,
830 svn_revnum_t peg_revision,
831 svn_revnum_t end_revision,
832 svn_revnum_t *revision_deleted,
836 log_path_del_rev_t log_path_deleted_baton;
838 /* Fetch the absolute FS path associated with PATH. */
839 SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));
841 if (!SVN_IS_VALID_REVNUM(peg_revision))
842 return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
843 _("Invalid peg revision %ld"), peg_revision);
844 if (!SVN_IS_VALID_REVNUM(end_revision))
845 return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
846 _("Invalid end revision %ld"), end_revision);
847 if (end_revision <= peg_revision)
848 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
849 _("Peg revision must precede end revision"));
851 log_path_deleted_baton.path = fs_path;
852 log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;
854 /* Examine the logs of SESSION's URL to find when DELETED_PATH was first
855 deleted or replaced. */
856 SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0,
858 apr_array_make(pool, 0, sizeof(char *)),
859 log_path_del_receiver, &log_path_deleted_baton,
861 *revision_deleted = log_path_deleted_baton.revision_deleted;
867 svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
869 svn_revnum_t revision,
870 apr_array_header_t **inherited_props,
871 apr_pool_t *result_pool,
872 apr_pool_t *scratch_pool)
874 const char *repos_root_url;
875 const char *session_url;
876 const char *parent_url;
877 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
880 apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
882 /* Walk to the root of the repository getting inherited
884 SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
885 SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
886 parent_url = session_url;
888 while (strcmp(repos_root_url, parent_url))
890 apr_hash_index_t *hi;
891 apr_hash_t *parent_props;
892 apr_hash_t *final_hash = apr_hash_make(result_pool);
895 svn_pool_clear(iterpool);
896 parent_url = svn_uri_dirname(parent_url, scratch_pool);
897 SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
898 err = session->vtable->get_dir(session, NULL, NULL,
900 revision, SVN_DIRENT_ALL,
903 /* If the user doesn't have read access to a parent path then
904 skip, but allow them to inherit from further up. */
907 if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
908 || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
910 svn_error_clear(err);
915 return svn_error_trace(err);
919 for (hi = apr_hash_first(scratch_pool, parent_props);
921 hi = apr_hash_next(hi))
923 const char *name = svn__apr_hash_index_key(hi);
924 apr_ssize_t klen = svn__apr_hash_index_klen(hi);
925 svn_string_t *value = svn__apr_hash_index_val(hi);
927 if (svn_property_kind2(name) == svn_prop_regular_kind)
929 name = apr_pstrdup(result_pool, name);
930 value = svn_string_dup(value, result_pool);
931 apr_hash_set(final_hash, name, klen, value);
935 if (apr_hash_count(final_hash))
937 svn_prop_inherited_item_t *new_iprop =
938 apr_palloc(result_pool, sizeof(*new_iprop));
939 new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url,
942 new_iprop->prop_hash = final_hash;
943 svn_sort__array_insert(&new_iprop, *inherited_props, 0);
947 /* Reparent session back to original URL. */
948 SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
950 svn_pool_destroy(iterpool);