diff options
Diffstat (limited to 'subversion/libsvn_ra/compat.c')
-rw-r--r-- | subversion/libsvn_ra/compat.c | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/subversion/libsvn_ra/compat.c b/subversion/libsvn_ra/compat.c new file mode 100644 index 0000000..b16bbef --- /dev/null +++ b/subversion/libsvn_ra/compat.c @@ -0,0 +1,952 @@ +/* + * compat.c: compatibility compliance logic + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_hash.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_sorts.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_ra.h" +#include "svn_io.h" +#include "svn_compat.h" +#include "svn_props.h" + +#include "private/svn_fspath.h" +#include "ra_loader.h" +#include "svn_private_config.h" + + + +/* This is just like svn_sort_compare_revisions, save that it sorts + the revisions in *ascending* order. */ +static int +compare_revisions(const void *a, const void *b) +{ + svn_revnum_t a_rev = *(const svn_revnum_t *)a; + svn_revnum_t b_rev = *(const svn_revnum_t *)b; + if (a_rev == b_rev) + return 0; + return a_rev < b_rev ? -1 : 1; +} + +/* Given the CHANGED_PATHS and REVISION from an instance of a + svn_log_message_receiver_t function, determine at which location + PATH may be expected in the next log message, and set *PREV_PATH_P + to that value. KIND is the node kind of PATH. Set *ACTION_P to a + character describing the change that caused this revision (as + listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the + revision PATH was copied from, or SVN_INVALID_REVNUM if it was not + copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case + they are not used. Perform all allocations in POOL. + + This is useful for tracking the various changes in location a + particular resource has undergone when performing an RA->get_logs() + operation on that resource. +*/ +static svn_error_t * +prev_log_path(const char **prev_path_p, + char *action_p, + svn_revnum_t *copyfrom_rev_p, + apr_hash_t *changed_paths, + const char *path, + svn_node_kind_t kind, + svn_revnum_t revision, + apr_pool_t *pool) +{ + svn_log_changed_path_t *change; + const char *prev_path = NULL; + + /* It's impossible to find the predecessor path of a NULL path. */ + SVN_ERR_ASSERT(path); + + /* Initialize our return values for the action and copyfrom_rev in + case we have an unhandled case later on. */ + if (action_p) + *action_p = 'M'; + if (copyfrom_rev_p) + *copyfrom_rev_p = SVN_INVALID_REVNUM; + + if (changed_paths) + { + /* See if PATH was explicitly changed in this revision. */ + change = svn_hash_gets(changed_paths, path); + if (change) + { + /* If PATH was not newly added in this revision, then it may or may + not have also been part of a moved subtree. In this case, set a + default previous path, but still look through the parents of this + path for a possible copy event. */ + if (change->action != 'A' && change->action != 'R') + { + prev_path = path; + } + else + { + /* PATH is new in this revision. This means it cannot have been + part of a copied subtree. */ + if (change->copyfrom_path) + prev_path = apr_pstrdup(pool, change->copyfrom_path); + else + prev_path = NULL; + + *prev_path_p = prev_path; + if (action_p) + *action_p = change->action; + if (copyfrom_rev_p) + *copyfrom_rev_p = change->copyfrom_rev; + return SVN_NO_ERROR; + } + } + + if (apr_hash_count(changed_paths)) + { + /* The path was not explicitly changed in this revision. The + fact that we're hearing about this revision implies, then, + that the path was a child of some copied directory. We need + to find that directory, and effectively "re-base" our path on + that directory's copyfrom_path. */ + int i; + apr_array_header_t *paths; + + /* Build a sorted list of the changed paths. */ + paths = svn_sort__hash(changed_paths, + svn_sort_compare_items_as_paths, pool); + + /* Now, walk the list of paths backwards, looking a parent of + our path that has copyfrom information. */ + for (i = paths->nelts; i > 0; i--) + { + svn_sort__item_t item = APR_ARRAY_IDX(paths, + i - 1, svn_sort__item_t); + const char *ch_path = item.key; + size_t len = strlen(ch_path); + + /* See if our path is the child of this change path. If + not, keep looking. */ + if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/'))) + continue; + + /* Okay, our path *is* a child of this change path. If + this change was copied, we just need to apply the + portion of our path that is relative to this change's + path, to the change's copyfrom path. Otherwise, this + change isn't really interesting to us, and our search + continues. */ + change = item.value; + if (change->copyfrom_path) + { + if (action_p) + *action_p = change->action; + if (copyfrom_rev_p) + *copyfrom_rev_p = change->copyfrom_rev; + prev_path = svn_fspath__join(change->copyfrom_path, + path + len + 1, pool); + break; + } + } + } + } + + /* If we didn't find what we expected to find, return an error. + (Because directories bubble-up, we get a bunch of logs we might + not want. Be forgiving in that case.) */ + if (! prev_path) + { + if (kind == svn_node_dir) + prev_path = apr_pstrdup(pool, path); + else + return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, + _("Missing changed-path information for " + "'%s' in revision %ld"), + svn_dirent_local_style(path, pool), revision); + } + + *prev_path_p = prev_path; + return SVN_NO_ERROR; +} + + +/* Set *FS_PATH_P to the absolute filesystem path associated with the + URL built from SESSION's URL and REL_PATH (which is relative to + session's URL. Use POOL for allocations. */ +static svn_error_t * +get_fs_path(const char **fs_path_p, + svn_ra_session_t *session, + const char *rel_path, + apr_pool_t *pool) +{ + const char *url, *fs_path; + + SVN_ERR(svn_ra_get_session_url(session, &url, pool)); + SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool)); + *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path, + rel_path, pool), + pool); + return SVN_NO_ERROR; +} + + + +/*** Fallback implementation of svn_ra_get_locations(). ***/ + + +/* ### This is to support 1.0 servers. */ +struct log_receiver_baton +{ + /* The kind of the path we're tracing. */ + svn_node_kind_t kind; + + /* The path at which we are trying to find our versioned resource in + the log output. */ + const char *last_path; + + /* Input revisions and output hash; the whole point of this little game. */ + svn_revnum_t peg_revision; + apr_array_header_t *location_revisions; + const char *peg_path; + apr_hash_t *locations; + + /* A pool from which to allocate stuff stored in this baton. */ + apr_pool_t *pool; +}; + + +/* Implements svn_log_entry_receiver_t; helper for slow_get_locations. + As input, takes log_receiver_baton (defined above) and attempts to + "fill in" locations in the baton over the course of many + iterations. */ +static svn_error_t * +log_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct log_receiver_baton *lrb = baton; + apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations); + const char *current_path = lrb->last_path; + const char *prev_path; + + /* No paths were changed in this revision. Nothing to do. */ + if (! log_entry->changed_paths2) + return SVN_NO_ERROR; + + /* If we've run off the end of the path's history, there's nothing + to do. (This should never happen with a properly functioning + server, since we'd get no more log messages after the one where + path was created. But a malfunctioning server shouldn't cause us + to trigger an assertion failure.) */ + if (! current_path) + return SVN_NO_ERROR; + + /* If we haven't found our peg path yet, and we are now looking at a + revision equal to or older than the peg revision, then our + "current" path is our peg path. */ + if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision)) + lrb->peg_path = apr_pstrdup(lrb->pool, current_path); + + /* Determine the paths for any of the revisions for which we haven't + gotten paths already. */ + while (lrb->location_revisions->nelts) + { + svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions, + lrb->location_revisions->nelts - 1, + svn_revnum_t); + if (log_entry->revision <= next) + { + apr_hash_set(lrb->locations, + apr_pmemdup(hash_pool, &next, sizeof(next)), + sizeof(next), + apr_pstrdup(hash_pool, current_path)); + apr_array_pop(lrb->location_revisions); + } + else + break; + } + + /* Figure out at which repository path our object of interest lived + in the previous revision. */ + SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2, + current_path, lrb->kind, log_entry->revision, pool)); + + /* Squirrel away our "next place to look" path (suffer the strcmp + hit to save on allocations). */ + if (! prev_path) + lrb->last_path = NULL; + else if (strcmp(prev_path, current_path) != 0) + lrb->last_path = apr_pstrdup(lrb->pool, prev_path); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_ra__locations_from_log(svn_ra_session_t *session, + apr_hash_t **locations_p, + const char *path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool) +{ + apr_hash_t *locations = apr_hash_make(pool); + struct log_receiver_baton lrb = { 0 }; + apr_array_header_t *targets; + svn_revnum_t youngest_requested, oldest_requested, youngest, oldest; + svn_node_kind_t kind; + const char *fs_path; + + /* Fetch the absolute FS path associated with PATH. */ + SVN_ERR(get_fs_path(&fs_path, session, path, pool)); + + /* Sanity check: verify that the peg-object exists in repos. */ + SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Path '%s' doesn't exist in revision %ld"), + fs_path, peg_revision); + + /* Easy out: no location revisions. */ + if (! location_revisions->nelts) + { + *locations_p = locations; + return SVN_NO_ERROR; + } + + /* Figure out the youngest and oldest revs (amongst the set of + requested revisions + the peg revision) so we can avoid + unnecessary log parsing. */ + qsort(location_revisions->elts, location_revisions->nelts, + location_revisions->elt_size, compare_revisions); + oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t); + youngest_requested = APR_ARRAY_IDX(location_revisions, + location_revisions->nelts - 1, + svn_revnum_t); + youngest = peg_revision; + youngest = (oldest_requested > youngest) ? oldest_requested : youngest; + youngest = (youngest_requested > youngest) ? youngest_requested : youngest; + oldest = peg_revision; + oldest = (oldest_requested < oldest) ? oldest_requested : oldest; + oldest = (youngest_requested < oldest) ? youngest_requested : oldest; + + /* Populate most of our log receiver baton structure. */ + lrb.kind = kind; + lrb.last_path = fs_path; + lrb.location_revisions = apr_array_copy(pool, location_revisions); + lrb.peg_revision = peg_revision; + lrb.peg_path = NULL; + lrb.locations = locations; + lrb.pool = pool; + + /* Let the RA layer drive our log information handler, which will do + the work of finding the actual locations for our resource. + Notice that we always run on the youngest rev of the 3 inputs. */ + targets = apr_array_make(pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(targets, const char *) = path; + SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0, + TRUE, FALSE, FALSE, + apr_array_make(pool, 0, sizeof(const char *)), + log_receiver, &lrb, pool)); + + /* If the received log information did not cover any of the + requested revisions, use the last known path. (This normally + just means that FS_PATH was not modified between the requested + revision and OLDEST. If the file was created at some point after + OLDEST, then lrb.last_path should be NULL.) */ + if (! lrb.peg_path) + lrb.peg_path = lrb.last_path; + if (lrb.last_path) + { + int i; + for (i = 0; i < location_revisions->nelts; i++) + { + svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i, + svn_revnum_t); + if (! apr_hash_get(locations, &rev, sizeof(rev))) + apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)), + sizeof(rev), apr_pstrdup(pool, lrb.last_path)); + } + } + + /* Check that we got the peg path. */ + if (! lrb.peg_path) + return svn_error_createf + (APR_EGENERAL, NULL, + _("Unable to find repository location for '%s' in revision %ld"), + fs_path, peg_revision); + + /* Sanity check: make sure that our calculated peg path is the same + as what we expected it to be. */ + if (strcmp(fs_path, lrb.peg_path) != 0) + return svn_error_createf + (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, + _("'%s' in revision %ld is an unrelated object"), + fs_path, youngest); + + *locations_p = locations; + return SVN_NO_ERROR; +} + + + + +/*** Fallback implementation of svn_ra_get_location_segments(). ***/ + +struct gls_log_receiver_baton { + /* The kind of the path we're tracing. */ + svn_node_kind_t kind; + + /* Are we finished (and just listening to log entries because our + caller won't shut up?). */ + svn_boolean_t done; + + /* The path at which we are trying to find our versioned resource in + the log output. */ + const char *last_path; + + /* Input data. */ + svn_revnum_t start_rev; + + /* Output intermediate state and callback/baton. */ + svn_revnum_t range_end; + svn_location_segment_receiver_t receiver; + void *receiver_baton; + + /* A pool from which to allocate stuff stored in this baton. */ + apr_pool_t *pool; +}; + +/* Build a node location segment object from PATH, RANGE_START, and + RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */ +static svn_error_t * +maybe_crop_and_send_segment(const char *path, + svn_revnum_t start_rev, + svn_revnum_t range_start, + svn_revnum_t range_end, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment)); + segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL; + segment->range_start = range_start; + segment->range_end = range_end; + if (segment->range_start <= start_rev) + { + if (segment->range_end > start_rev) + segment->range_end = start_rev; + return receiver(segment, receiver_baton, pool); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +gls_log_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct gls_log_receiver_baton *lrb = baton; + const char *current_path = lrb->last_path; + const char *prev_path; + svn_revnum_t copyfrom_rev; + + /* If we're done, ignore this invocation. */ + if (lrb->done) + return SVN_NO_ERROR; + + /* Figure out at which repository path our object of interest lived + in the previous revision, and if its current location is the + result of copy since then. */ + SVN_ERR(prev_log_path(&prev_path, NULL, ©from_rev, + log_entry->changed_paths2, current_path, + lrb->kind, log_entry->revision, pool)); + + /* If we've run off the end of the path's history, we need to report + our final segment (and then, we're done). */ + if (! prev_path) + { + lrb->done = TRUE; + return maybe_crop_and_send_segment(current_path, lrb->start_rev, + log_entry->revision, lrb->range_end, + lrb->receiver, lrb->receiver_baton, + pool); + } + + /* If there was a copy operation of interest... */ + if (SVN_IS_VALID_REVNUM(copyfrom_rev)) + { + /* ...then report the segment between this revision and the + last-reported revision. */ + SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev, + log_entry->revision, lrb->range_end, + lrb->receiver, lrb->receiver_baton, + pool)); + lrb->range_end = log_entry->revision - 1; + + /* And if there was a revision gap, we need to report that, too. */ + if (log_entry->revision - copyfrom_rev > 1) + { + SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev, + copyfrom_rev + 1, lrb->range_end, + lrb->receiver, + lrb->receiver_baton, pool)); + lrb->range_end = copyfrom_rev; + } + + /* Update our state variables. */ + lrb->last_path = apr_pstrdup(lrb->pool, prev_path); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_ra__location_segments_from_log(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_location_segment_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + struct gls_log_receiver_baton lrb = { 0 }; + apr_array_header_t *targets; + svn_node_kind_t kind; + svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; + const char *fs_path; + + /* Fetch the absolute FS path associated with PATH. */ + SVN_ERR(get_fs_path(&fs_path, session, path, pool)); + + /* If PEG_REVISION is invalid, it means HEAD. If START_REV is + invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll + use 0. */ + if (! SVN_IS_VALID_REVNUM(peg_revision)) + { + SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool)); + peg_revision = youngest_rev; + } + if (! SVN_IS_VALID_REVNUM(start_rev)) + { + if (SVN_IS_VALID_REVNUM(youngest_rev)) + start_rev = youngest_rev; + else + SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool)); + } + if (! SVN_IS_VALID_REVNUM(end_rev)) + { + end_rev = 0; + } + + /* The API demands a certain ordering of our revision inputs. Enforce it. */ + SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev)); + + /* Sanity check: verify that the peg-object exists in repos. */ + SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool)); + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Path '%s' doesn't exist in revision %ld"), + fs_path, start_rev); + + /* Populate most of our log receiver baton structure. */ + lrb.kind = kind; + lrb.last_path = fs_path; + lrb.done = FALSE; + lrb.start_rev = start_rev; + lrb.range_end = start_rev; + lrb.receiver = receiver; + lrb.receiver_baton = receiver_baton; + lrb.pool = pool; + + /* Let the RA layer drive our log information handler, which will do + the work of finding the actual locations for our resource. + Notice that we always run on the youngest rev of the 3 inputs. */ + targets = apr_array_make(pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(targets, const char *) = path; + SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0, + TRUE, FALSE, FALSE, + apr_array_make(pool, 0, sizeof(const char *)), + gls_log_receiver, &lrb, pool)); + + /* If we didn't finish, we need to do so with a final segment send. */ + if (! lrb.done) + SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev, + end_rev, lrb.range_end, + receiver, receiver_baton, pool)); + + return SVN_NO_ERROR; +} + + + +/*** Fallback implementation of svn_ra_get_file_revs(). ***/ + +/* The metadata associated with a particular revision. */ +struct rev +{ + svn_revnum_t revision; /* the revision number */ + const char *path; /* the absolute repository path */ + apr_hash_t *props; /* the revprops for this revision */ + struct rev *next; /* the next revision */ +}; + +/* File revs log message baton. */ +struct fr_log_message_baton { + const char *path; /* The path to be processed */ + struct rev *eldest; /* The eldest revision processed */ + char action; /* The action associated with the eldest */ + svn_revnum_t copyrev; /* The revision the eldest was copied from */ + apr_pool_t *pool; +}; + +/* Callback for log messages: implements svn_log_entry_receiver_t and + accumulates revision metadata into a chronologically ordered list stored in + the baton. */ +static svn_error_t * +fr_log_message_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct fr_log_message_baton *lmb = baton; + struct rev *rev; + + rev = apr_palloc(lmb->pool, sizeof(*rev)); + rev->revision = log_entry->revision; + rev->path = lmb->path; + rev->next = lmb->eldest; + lmb->eldest = rev; + + /* Duplicate log_entry revprops into rev->props */ + rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool); + + return prev_log_path(&lmb->path, &lmb->action, + &lmb->copyrev, log_entry->changed_paths2, + lmb->path, svn_node_file, log_entry->revision, + lmb->pool); +} + +svn_error_t * +svn_ra__file_revs_from_log(svn_ra_session_t *ra_session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + const char *repos_url, *session_url, *fs_path; + apr_array_header_t *condensed_targets; + struct fr_log_message_baton lmb; + struct rev *rev; + apr_hash_t *last_props; + svn_stream_t *last_stream; + apr_pool_t *currpool, *lastpool; + + /* Fetch the absolute FS path associated with PATH. */ + SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); + + /* Check to make sure we're dealing with a file. */ + SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); + if (kind == svn_node_dir) + return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, + _("'%s' is not a file"), fs_path); + + condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(condensed_targets, const char *) = path; + + lmb.path = fs_path; + lmb.eldest = NULL; + lmb.pool = pool; + + /* Accumulate revision metadata by walking the revisions + backwards; this allows us to follow moves/copies + correctly. */ + SVN_ERR(svn_ra_get_log2(ra_session, + condensed_targets, + end, start, 0, /* no limit */ + TRUE, FALSE, FALSE, + NULL, fr_log_message_receiver, &lmb, + pool)); + + /* Reparent the session while we go back through the history. */ + SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); + SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); + SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); + + currpool = svn_pool_create(pool); + lastpool = svn_pool_create(pool); + + /* We want the first txdelta to be against the empty file. */ + last_props = apr_hash_make(lastpool); + last_stream = svn_stream_empty(lastpool); + + /* Walk the revision list in chronological order, downloading each fulltext, + diffing it with its predecessor, and calling the file_revs handler for + each one. Use two iteration pools rather than one, because the diff + routines need to look at a sliding window of revisions. Two pools gives + us a ring buffer of sorts. */ + for (rev = lmb.eldest; rev; rev = rev->next) + { + const char *temp_path; + apr_pool_t *tmppool; + apr_hash_t *props; + apr_file_t *file; + svn_stream_t *stream; + apr_array_header_t *prop_diffs; + svn_txdelta_stream_t *delta_stream; + svn_txdelta_window_handler_t delta_handler = NULL; + void *delta_baton = NULL; + + svn_pool_clear(currpool); + + /* Get the contents of the file from the repository, and put them in + a temporary local file. */ + SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, + svn_io_file_del_on_pool_cleanup, + currpool, currpool)); + SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, + stream, NULL, &props, currpool)); + SVN_ERR(svn_stream_close(stream)); + + /* Open up a stream to the local file. */ + SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, + currpool)); + stream = svn_stream_from_aprfile2(file, FALSE, currpool); + + /* Calculate the property diff */ + SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); + + /* Call the file_rev handler */ + SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, + FALSE, /* merged revision */ + &delta_handler, &delta_baton, prop_diffs, lastpool)); + + /* Compute and send delta if client asked for it. */ + if (delta_handler) + { + /* Get the content delta. Don't calculate checksums as we don't + * use them. */ + svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); + + /* And send. */ + SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, + delta_baton, lastpool)); + } + + /* Switch the pools and data for the next iteration */ + tmppool = currpool; + currpool = lastpool; + lastpool = tmppool; + + SVN_ERR(svn_stream_close(last_stream)); + last_stream = stream; + last_props = props; + } + + SVN_ERR(svn_stream_close(last_stream)); + svn_pool_destroy(currpool); + svn_pool_destroy(lastpool); + + /* Reparent the session back to the original URL. */ + return svn_ra_reparent(ra_session, session_url, pool); +} + + +/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/ + +/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */ +typedef struct log_path_del_rev_t +{ + /* Absolute repository path. */ + const char *path; + + /* Revision PATH was first deleted or replaced. */ + svn_revnum_t revision_deleted; +} log_path_del_rev_t; + +/* A svn_log_entry_receiver_t callback for finding the revision + ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced. + Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED. + */ +static svn_error_t * +log_path_del_receiver(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + log_path_del_rev_t *b = baton; + apr_hash_index_t *hi; + + /* No paths were changed in this revision. Nothing to do. */ + if (! log_entry->changed_paths2) + return SVN_NO_ERROR; + + for (hi = apr_hash_first(pool, log_entry->changed_paths2); + hi != NULL; + hi = apr_hash_next(hi)) + { + void *val; + char *path; + svn_log_changed_path_t *log_item; + + apr_hash_this(hi, (void *) &path, NULL, &val); + log_item = val; + if (svn_path_compare_paths(b->path, path) == 0 + && (log_item->action == 'D' || log_item->action == 'R')) + { + /* Found the first deletion or replacement, we are done. */ + b->revision_deleted = log_entry->revision; + break; + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session, + const char *rel_deleted_path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + const char *fs_path; + log_path_del_rev_t log_path_deleted_baton; + + /* Fetch the absolute FS path associated with PATH. */ + SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool)); + + if (!SVN_IS_VALID_REVNUM(peg_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid peg revision %ld"), peg_revision); + if (!SVN_IS_VALID_REVNUM(end_revision)) + return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Invalid end revision %ld"), end_revision); + if (end_revision <= peg_revision) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Peg revision must precede end revision")); + + log_path_deleted_baton.path = fs_path; + log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM; + + /* Examine the logs of SESSION's URL to find when DELETED_PATH was first + deleted or replaced. */ + SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0, + TRUE, TRUE, FALSE, + apr_array_make(pool, 0, sizeof(char *)), + log_path_del_receiver, &log_path_deleted_baton, + pool)); + *revision_deleted = log_path_deleted_baton.revision_deleted; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_ra__get_inherited_props_walk(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + apr_array_header_t **inherited_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_root_url; + const char *session_url; + const char *parent_url; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + *inherited_props = + apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); + + /* Walk to the root of the repository getting inherited + props for PATH. */ + SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool)); + SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool)); + parent_url = session_url; + + while (strcmp(repos_root_url, parent_url)) + { + apr_hash_index_t *hi; + apr_hash_t *parent_props; + apr_hash_t *final_hash = apr_hash_make(result_pool); + svn_error_t *err; + + svn_pool_clear(iterpool); + parent_url = svn_uri_dirname(parent_url, scratch_pool); + SVN_ERR(svn_ra_reparent(session, parent_url, iterpool)); + err = session->vtable->get_dir(session, NULL, NULL, + &parent_props, "", + revision, SVN_DIRENT_ALL, + iterpool); + + /* If the user doesn't have read access to a parent path then + skip, but allow them to inherit from further up. */ + if (err) + { + if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) + || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)) + { + svn_error_clear(err); + continue; + } + else + { + return svn_error_trace(err); + } + } + + for (hi = apr_hash_first(scratch_pool, parent_props); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + apr_ssize_t klen = svn__apr_hash_index_klen(hi); + svn_string_t *value = svn__apr_hash_index_val(hi); + + if (svn_property_kind2(name) == svn_prop_regular_kind) + { + name = apr_pstrdup(result_pool, name); + value = svn_string_dup(value, result_pool); + apr_hash_set(final_hash, name, klen, value); + } + } + + if (apr_hash_count(final_hash)) + { + svn_prop_inherited_item_t *new_iprop = + apr_palloc(result_pool, sizeof(*new_iprop)); + new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url, + parent_url, + result_pool); + new_iprop->prop_hash = final_hash; + svn_sort__array_insert(&new_iprop, *inherited_props, 0); + } + } + + /* Reparent session back to original URL. */ + SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool)); + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} |