diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
commit | f1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch) | |
tree | 4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_ra | |
download | subversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz |
Tarball conversion
Diffstat (limited to 'subversion/libsvn_ra')
-rw-r--r-- | subversion/libsvn_ra/compat.c | 872 | ||||
-rw-r--r-- | subversion/libsvn_ra/debug_reporter.c | 151 | ||||
-rw-r--r-- | subversion/libsvn_ra/debug_reporter.h | 49 | ||||
-rw-r--r-- | subversion/libsvn_ra/deprecated.c | 419 | ||||
-rw-r--r-- | subversion/libsvn_ra/ra_loader.c | 1406 | ||||
-rw-r--r-- | subversion/libsvn_ra/ra_loader.h | 458 | ||||
-rw-r--r-- | subversion/libsvn_ra/util.c | 242 | ||||
-rw-r--r-- | subversion/libsvn_ra/wrapper_template.h | 506 |
8 files changed, 4103 insertions, 0 deletions
diff --git a/subversion/libsvn_ra/compat.c b/subversion/libsvn_ra/compat.c new file mode 100644 index 0000000..ec3ff9d --- /dev/null +++ b/subversion/libsvn_ra/compat.c @@ -0,0 +1,872 @@ +/* + * 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_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 = apr_hash_get(changed_paths, path, APR_HASH_KEY_STRING); + 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; + int 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 = apr_hash_get(changed_paths, ch_path, len); + 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; + apr_hash_index_t *hi; + + 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 = apr_hash_make(lmb->pool); + for (hi = apr_hash_first(pool, log_entry->revprops); hi; + hi = apr_hash_next(hi)) + { + void *val; + const void *key; + + apr_hash_this(hi, &key, NULL, &val); + apr_hash_set(rev->props, apr_pstrdup(lmb->pool, key), APR_HASH_KEY_STRING, + svn_string_dup(val, 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. */ + svn_txdelta(&delta_stream, last_stream, stream, 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; +} diff --git a/subversion/libsvn_ra/debug_reporter.c b/subversion/libsvn_ra/debug_reporter.c new file mode 100644 index 0000000..56248ac --- /dev/null +++ b/subversion/libsvn_ra/debug_reporter.c @@ -0,0 +1,151 @@ +/* + * debug_reporter.c : An reporter that writes the operations it does to stderr. + * + * ==================================================================== + * 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 "debug_reporter.h" + +struct report_baton +{ + const svn_ra_reporter3_t *wrapped_reporter; + void *wrapped_report_baton; + + svn_stream_t *out; +}; + +#define BOOLEAN_TO_WORD(condition) ((condition) ? "True" : "False") + + +/*** Wrappers. ***/ + +static svn_error_t * +set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct report_baton *rb = report_baton; + SVN_ERR(svn_stream_printf(rb->out, pool, "set_path(%s, %ld, %s, %s, %s)\n", + path, revision, svn_depth_to_word(depth), + BOOLEAN_TO_WORD(start_empty), lock_token)); + SVN_ERR(rb->wrapped_reporter->set_path(rb->wrapped_report_baton, path, + revision, depth, + start_empty, lock_token, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct report_baton *rb = report_baton; + SVN_ERR(svn_stream_printf(rb->out, pool, "delete_path(%s)\n", path)); + SVN_ERR(rb->wrapped_reporter->delete_path(rb->wrapped_report_baton, + path, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct report_baton *rb = report_baton; + SVN_ERR(svn_stream_printf(rb->out, pool, + "link_path(%s, %s, %ld, %s, %s, %s)\n", + path, url, revision, svn_depth_to_word(depth), + BOOLEAN_TO_WORD(start_empty), lock_token)); + SVN_ERR(rb->wrapped_reporter->link_path(rb->wrapped_report_baton, path, url, + revision, depth, start_empty, + lock_token, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +finish_report(void *report_baton, + apr_pool_t *pool) +{ + struct report_baton *rb = report_baton; + SVN_ERR(svn_stream_printf(rb->out, pool, "finish_report()\n")); + SVN_ERR(rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +abort_report(void *report_baton, + apr_pool_t *pool) +{ + struct report_baton *rb = report_baton; + SVN_ERR(svn_stream_printf(rb->out, pool, "abort_report()\n")); + SVN_ERR(rb->wrapped_reporter->abort_report(rb->wrapped_report_baton, pool)); + return SVN_NO_ERROR; +} + + +/*** Public interfaces. ***/ +svn_error_t * +svn_ra__get_debug_reporter(const svn_ra_reporter3_t **reporter, + void **report_baton, + const svn_ra_reporter3_t *wrapped_reporter, + void *wrapped_report_baton, + apr_pool_t *pool) +{ + svn_ra_reporter3_t *tree_reporter; + struct report_baton *rb; + apr_file_t *errfp; + svn_stream_t *out; + + apr_status_t apr_err = apr_file_open_stderr(&errfp, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Problem opening stderr"); + + out = svn_stream_from_aprfile2(errfp, TRUE, pool); + + /* ### svn_delta_default_editor() */ + tree_reporter = apr_palloc(pool, sizeof(*tree_reporter)); + rb = apr_palloc(pool, sizeof(*rb)); + + tree_reporter->set_path = set_path; + tree_reporter->delete_path = delete_path; + tree_reporter->link_path = link_path; + tree_reporter->finish_report = finish_report; + tree_reporter->abort_report = abort_report; + + rb->wrapped_reporter = wrapped_reporter; + rb->wrapped_report_baton = wrapped_report_baton; + rb->out = out; + + *reporter = tree_reporter; + *report_baton = rb; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra/debug_reporter.h b/subversion/libsvn_ra/debug_reporter.h new file mode 100644 index 0000000..1728139 --- /dev/null +++ b/subversion/libsvn_ra/debug_reporter.h @@ -0,0 +1,49 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + */ + + +#ifndef SVN_DEBUG_REPORTER_H +#define SVN_DEBUG_REPORTER_H + +#include "svn_ra.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Return a debug reporter that wraps @a wrapped_reporter. + * + * The debug reporter simply prints an indication of what callbacks are being + * called to @c stderr, and is only intended for use in debugging subversion + * reporters. + */ +svn_error_t * +svn_ra__get_debug_reporter(const svn_ra_reporter3_t **reporter, + void **report_baton, + const svn_ra_reporter3_t *wrapped_reporter, + void *wrapped_report_baton, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_DEBUG_REPORTER_H */ diff --git a/subversion/libsvn_ra/deprecated.c b/subversion/libsvn_ra/deprecated.c new file mode 100644 index 0000000..6fd2442 --- /dev/null +++ b/subversion/libsvn_ra/deprecated.c @@ -0,0 +1,419 @@ +/* + * deprecated.c: holding file for all deprecated APIs. + * "we can't lose 'em, but we can shun 'em!" + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* We define this here to remove any further warnings about the usage of + deprecated functions in this file. */ +#define SVN_DEPRECATED + +#include "svn_ra.h" +#include "svn_path.h" +#include "svn_compat.h" +#include "svn_props.h" +#include "svn_pools.h" + +#include "ra_loader.h" + +#include "svn_private_config.h" + + + + +/*** From ra_loader.c ***/ +/*** Compatibility Wrappers ***/ + +/* Wrap @c svn_ra_reporter3_t in an interface that looks like + @c svn_ra_reporter2_t, for compatibility with functions that take + the latter. This shields the ra-specific implementations from + worrying about what kind of reporter they're dealing with. + + This code does not live in wrapper_template.h because that file is + about the big changeover from a vtable-style to function-style + interface, and does not contain the post-changeover interfaces + that we are compatiblizing here. + + This code looks like it duplicates code in libsvn_wc/adm_crawler.c, + but in fact it does not. That code makes old things look like new + things; this code makes a new thing look like an old thing. */ + +/* Baton for abovementioned wrapping. */ +struct reporter_3in2_baton { + const svn_ra_reporter3_t *reporter3; + void *reporter3_baton; +}; + +/* Wrap the corresponding svn_ra_reporter3_t field in an + svn_ra_reporter2_t interface. @a report_baton is a + @c reporter_3in2_baton_t *. */ +static svn_error_t * +set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = report_baton; + return b->reporter3->set_path(b->reporter3_baton, + path, revision, svn_depth_infinity, + start_empty, lock_token, pool); +} + +/* Wrap the corresponding svn_ra_reporter3_t field in an + svn_ra_reporter2_t interface. @a report_baton is a + @c reporter_3in2_baton_t *. */ +static svn_error_t * +delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = report_baton; + return b->reporter3->delete_path(b->reporter3_baton, path, pool); +} + +/* Wrap the corresponding svn_ra_reporter3_t field in an + svn_ra_reporter2_t interface. @a report_baton is a + @c reporter_3in2_baton_t *. */ +static svn_error_t * +link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = report_baton; + return b->reporter3->link_path(b->reporter3_baton, + path, url, revision, svn_depth_infinity, + start_empty, lock_token, pool); + +} + +/* Wrap the corresponding svn_ra_reporter3_t field in an + svn_ra_reporter2_t interface. @a report_baton is a + @c reporter_3in2_baton_t *. */ +static svn_error_t * +finish_report(void *report_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = report_baton; + return b->reporter3->finish_report(b->reporter3_baton, pool); +} + +/* Wrap the corresponding svn_ra_reporter3_t field in an + svn_ra_reporter2_t interface. @a report_baton is a + @c reporter_3in2_baton_t *. */ +static svn_error_t * +abort_report(void *report_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = report_baton; + return b->reporter3->abort_report(b->reporter3_baton, pool); +} + +/* Wrap svn_ra_reporter3_t calls in an svn_ra_reporter2_t interface. + + Note: For calls where the prototypes are exactly the same, we could + avoid the pass-through overhead by using the function in the + reporter returned from session->vtable->do_foo. But the code would + get a lot less readable, and the only benefit would be to shave a + few instructions in a network-bound operation anyway. So in + delete_path(), finish_report(), and abort_report(), we cheerfully + pass through to identical functions. */ +static svn_ra_reporter2_t reporter_3in2_wrapper = { + set_path, + delete_path, + link_path, + finish_report, + abort_report +}; + +svn_error_t *svn_ra_open3(svn_ra_session_t **session_p, + const char *repos_URL, + const char *uuid, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + return svn_ra_open4(session_p, NULL, repos_URL, uuid, + callbacks, callback_baton, config, pool); +} + +svn_error_t *svn_ra_open2(svn_ra_session_t **session_p, + const char *repos_URL, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + return svn_ra_open3(session_p, repos_URL, NULL, + callbacks, callback_baton, config, pool); +} + +svn_error_t *svn_ra_open(svn_ra_session_t **session_p, + const char *repos_URL, + const svn_ra_callbacks_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + /* Deprecated function. Copy the contents of the svn_ra_callbacks_t + to a new svn_ra_callbacks2_t and call svn_ra_open2(). */ + svn_ra_callbacks2_t *callbacks2; + SVN_ERR(svn_ra_create_callbacks(&callbacks2, pool)); + callbacks2->open_tmp_file = callbacks->open_tmp_file; + callbacks2->auth_baton = callbacks->auth_baton; + callbacks2->get_wc_prop = callbacks->get_wc_prop; + callbacks2->set_wc_prop = callbacks->set_wc_prop; + callbacks2->push_wc_prop = callbacks->push_wc_prop; + callbacks2->invalidate_wc_props = callbacks->invalidate_wc_props; + callbacks2->progress_func = NULL; + callbacks2->progress_baton = NULL; + return svn_ra_open2(session_p, repos_URL, + callbacks2, callback_baton, + config, pool); +} + +svn_error_t *svn_ra_change_rev_prop(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + return svn_ra_change_rev_prop2(session, rev, name, NULL, value, pool); +} + +svn_error_t *svn_ra_get_commit_editor2(svn_ra_session_t *session, + const svn_delta_editor_t **editor, + void **edit_baton, + const char *log_msg, + svn_commit_callback2_t callback, + void *callback_baton, + apr_hash_t *lock_tokens, + svn_boolean_t keep_locks, + apr_pool_t *pool) +{ + apr_hash_t *revprop_table = apr_hash_make(pool); + if (log_msg) + apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, + APR_HASH_KEY_STRING, + svn_string_create(log_msg, pool)); + return svn_ra_get_commit_editor3(session, editor, edit_baton, revprop_table, + callback, callback_baton, + lock_tokens, keep_locks, pool); +} + +svn_error_t *svn_ra_get_commit_editor(svn_ra_session_t *session, + const svn_delta_editor_t **editor, + void **edit_baton, + const char *log_msg, + svn_commit_callback_t callback, + void *callback_baton, + apr_hash_t *lock_tokens, + svn_boolean_t keep_locks, + apr_pool_t *pool) +{ + svn_commit_callback2_t callback2; + void *callback2_baton; + + svn_compat_wrap_commit_callback(&callback2, &callback2_baton, + callback, callback_baton, + pool); + + return svn_ra_get_commit_editor2(session, editor, edit_baton, + log_msg, callback2, + callback2_baton, lock_tokens, + keep_locks, pool); +} + +svn_error_t *svn_ra_do_diff2(svn_ra_session_t *session, + const svn_ra_reporter2_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *diff_target, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + svn_boolean_t text_deltas, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b)); + SVN_ERR_ASSERT(svn_path_is_empty(diff_target) + || svn_path_is_single_path_component(diff_target)); + *reporter = &reporter_3in2_wrapper; + *report_baton = b; + return session->vtable->do_diff(session, + &(b->reporter3), &(b->reporter3_baton), + revision, diff_target, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + ignore_ancestry, text_deltas, versus_url, + diff_editor, diff_baton, pool); +} + +svn_error_t *svn_ra_do_diff(svn_ra_session_t *session, + const svn_ra_reporter2_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *diff_target, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(svn_path_is_empty(diff_target) + || svn_path_is_single_path_component(diff_target)); + return svn_ra_do_diff2(session, reporter, report_baton, revision, + diff_target, recurse, ignore_ancestry, TRUE, + versus_url, diff_editor, diff_baton, pool); +} + +svn_error_t *svn_ra_get_log(svn_ra_session_t *session, + const apr_array_header_t *paths, + svn_revnum_t start, + svn_revnum_t end, + int limit, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_log_message_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + svn_log_entry_receiver_t receiver2; + void *receiver2_baton; + + if (paths) + { + int i; + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + SVN_ERR_ASSERT(*path != '/'); + } + } + + svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton, + receiver, receiver_baton, + pool); + + return svn_ra_get_log2(session, paths, start, end, limit, + discover_changed_paths, strict_node_history, + FALSE, svn_compat_log_revprops_in(pool), + receiver2, receiver2_baton, pool); +} + +svn_error_t *svn_ra_get_file_revs(svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_file_rev_handler_t handler2; + void *handler2_baton; + + SVN_ERR_ASSERT(*path != '/'); + + svn_compat_wrap_file_rev_handler(&handler2, &handler2_baton, + handler, handler_baton, + pool); + + return svn_ra_get_file_revs2(session, path, start, end, FALSE, handler2, + handler2_baton, pool); +} + +svn_error_t *svn_ra_do_update(svn_ra_session_t *session, + const svn_ra_reporter2_t **reporter, + void **report_baton, + svn_revnum_t revision_to_update_to, + const char *update_target, + svn_boolean_t recurse, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b)); + SVN_ERR_ASSERT(svn_path_is_empty(update_target) + || svn_path_is_single_path_component(update_target)); + *reporter = &reporter_3in2_wrapper; + *report_baton = b; + return session->vtable->do_update(session, + &(b->reporter3), &(b->reporter3_baton), + revision_to_update_to, update_target, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + FALSE, /* no copyfrom args */ + update_editor, update_baton, + pool); +} + +svn_error_t *svn_ra_do_switch(svn_ra_session_t *session, + const svn_ra_reporter2_t **reporter, + void **report_baton, + svn_revnum_t revision_to_switch_to, + const char *switch_target, + svn_boolean_t recurse, + const char *switch_url, + const svn_delta_editor_t *switch_editor, + void *switch_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b)); + SVN_ERR_ASSERT(svn_path_is_empty(switch_target) + || svn_path_is_single_path_component(switch_target)); + *reporter = &reporter_3in2_wrapper; + *report_baton = b; + return session->vtable->do_switch(session, + &(b->reporter3), &(b->reporter3_baton), + revision_to_switch_to, switch_target, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + switch_url, switch_editor, switch_baton, + pool); +} + +svn_error_t *svn_ra_do_status(svn_ra_session_t *session, + const svn_ra_reporter2_t **reporter, + void **report_baton, + const char *status_target, + svn_revnum_t revision, + svn_boolean_t recurse, + const svn_delta_editor_t *status_editor, + void *status_baton, + apr_pool_t *pool) +{ + struct reporter_3in2_baton *b = apr_palloc(pool, sizeof(*b)); + SVN_ERR_ASSERT(svn_path_is_empty(status_target) + || svn_path_is_single_path_component(status_target)); + *reporter = &reporter_3in2_wrapper; + *report_baton = b; + return session->vtable->do_status(session, + &(b->reporter3), &(b->reporter3_baton), + status_target, revision, + SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse), + status_editor, status_baton, pool); +} diff --git a/subversion/libsvn_ra/ra_loader.c b/subversion/libsvn_ra/ra_loader.c new file mode 100644 index 0000000..b796a80 --- /dev/null +++ b/subversion/libsvn_ra/ra_loader.c @@ -0,0 +1,1406 @@ +/* + * ra_loader.c: logic for loading different RA library implementations + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + +/*** Includes. ***/ +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <apr.h> +#include <apr_strings.h> +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_uri.h> + +#include "svn_version.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_pools.h" +#include "svn_delta.h" +#include "svn_ra.h" +#include "svn_xml.h" +#include "svn_path.h" +#include "svn_dso.h" +#include "svn_config.h" +#include "ra_loader.h" + +#include "private/svn_ra_private.h" +#include "svn_private_config.h" + + +/* ### This file maps URL schemes to particular RA libraries. + ### Currently, the only pair of RA libraries which support the same + ### protocols are neon and serf. svn_ra_open3 makes the assumption + ### that this is the case; that their 'schemes' fields are both + ### dav_schemes; and that "neon" is listed first. + + ### Users can choose which dav library to use with the http-library + ### preference in .subversion/servers; however, it is ignored by + ### any code which uses the pre-1.2 API svn_ra_get_ra_library + ### instead of svn_ra_open. */ + +#if defined(SVN_HAVE_NEON) && defined(SVN_HAVE_SERF) +#define CHOOSABLE_DAV_MODULE +#endif + + +/* These are the URI schemes that the respective libraries *may* support. + * The schemes actually supported may be a subset of the schemes listed below. + * This can't be determine until the library is loaded. + * (Currently, this applies to the https scheme, which is only + * available if SSL is supported.) */ +static const char * const dav_schemes[] = { "http", "https", NULL }; +static const char * const svn_schemes[] = { "svn", NULL }; +static const char * const local_schemes[] = { "file", NULL }; + +static const struct ra_lib_defn { + /* the name of this RA library (e.g. "neon" or "local") */ + const char *ra_name; + + const char * const *schemes; + /* the initialization function if linked in; otherwise, NULL */ + svn_ra__init_func_t initfunc; + svn_ra_init_func_t compat_initfunc; +} ra_libraries[] = { + { + "neon", + dav_schemes, +#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_NEON + svn_ra_neon__init, + svn_ra_dav_init +#endif + }, + + { + "svn", + svn_schemes, +#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SVN + svn_ra_svn__init, + svn_ra_svn_init +#endif + }, + + { + "local", + local_schemes, +#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL + svn_ra_local__init, + svn_ra_local_init +#endif + }, + + { + "serf", + dav_schemes, +#ifdef SVN_LIBSVN_CLIENT_LINKS_RA_SERF + svn_ra_serf__init, + svn_ra_serf_init +#endif + }, + + /* ADD NEW RA IMPLEMENTATIONS HERE (as they're written) */ + + /* sentinel */ + { NULL } +}; + +/* Ensure that the RA library NAME is loaded. + * + * If FUNC is non-NULL, set *FUNC to the address of the svn_ra_NAME__init + * function of the library. + * + * If COMPAT_FUNC is non-NULL, set *COMPAT_FUNC to the address of the + * svn_ra_NAME_init compatibility init function of the library. + * + * ### todo: Any RA libraries implemented from this point forward + * ### don't really need an svn_ra_NAME_init compatibility function. + * ### Currently, load_ra_module() will error if no such function is + * ### found, but it might be more friendly to simply set *COMPAT_FUNC + * ### to null (assuming COMPAT_FUNC itself is non-null). + */ +static svn_error_t * +load_ra_module(svn_ra__init_func_t *func, + svn_ra_init_func_t *compat_func, + const char *ra_name, apr_pool_t *pool) +{ + if (func) + *func = NULL; + if (compat_func) + *compat_func = NULL; + +#if defined(SVN_USE_DSO) && APR_HAS_DSO + { + apr_dso_handle_t *dso; + apr_dso_handle_sym_t symbol; + const char *libname; + const char *funcname; + const char *compat_funcname; + apr_status_t status; + + libname = apr_psprintf(pool, "libsvn_ra_%s-%d.so.0", + ra_name, SVN_VER_MAJOR); + funcname = apr_psprintf(pool, "svn_ra_%s__init", ra_name); + compat_funcname = apr_psprintf(pool, "svn_ra_%s_init", ra_name); + + /* find/load the specified library */ + SVN_ERR(svn_dso_load(&dso, libname)); + if (! dso) + return SVN_NO_ERROR; + + /* find the initialization routines */ + if (func) + { + status = apr_dso_sym(&symbol, dso, funcname); + if (status) + { + return svn_error_wrap_apr(status, + _("'%s' does not define '%s()'"), + libname, funcname); + } + + *func = (svn_ra__init_func_t) symbol; + } + + if (compat_func) + { + status = apr_dso_sym(&symbol, dso, compat_funcname); + if (status) + { + return svn_error_wrap_apr(status, + _("'%s' does not define '%s()'"), + libname, compat_funcname); + } + + *compat_func = (svn_ra_init_func_t) symbol; + } + } +#endif /* APR_HAS_DSO */ + + return SVN_NO_ERROR; +} + +/* If DEFN may support URL, return the scheme. Else, return NULL. */ +static const char * +has_scheme_of(const struct ra_lib_defn *defn, const char *url) +{ + const char * const *schemes; + apr_size_t len; + + for (schemes = defn->schemes; *schemes != NULL; ++schemes) + { + const char *scheme = *schemes; + len = strlen(scheme); + /* Case-insensitive comparison, per RFC 2396 section 3.1. Allow + URL to contain a trailing "+foo" section in the scheme, since + that's how we specify tunnel schemes in ra_svn. */ + if (strncasecmp(scheme, url, len) == 0 && + (url[len] == ':' || url[len] == '+')) + return scheme; + } + + return NULL; +} + +/* Return an error if RA_VERSION doesn't match the version of this library. + Use SCHEME in the error message to describe the library that was loaded. */ +static svn_error_t * +check_ra_version(const svn_version_t *ra_version, const char *scheme) +{ + const svn_version_t *my_version = svn_ra_version(); + if (!svn_ver_equal(my_version, ra_version)) + return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, + _("Mismatched RA version for '%s':" + " found %d.%d.%d%s," + " expected %d.%d.%d%s"), + scheme, + my_version->major, my_version->minor, + my_version->patch, my_version->tag, + ra_version->major, ra_version->minor, + ra_version->patch, ra_version->tag); + + return SVN_NO_ERROR; +} + +/* -------------------------------------------------------------- */ + +/*** Public Interfaces ***/ + +svn_error_t *svn_ra_initialize(apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +/* Please note: the implementation of svn_ra_create_callbacks is + * duplicated in libsvn_ra/wrapper_template.h:compat_open() . This + * duplication is intentional, is there to avoid a circular + * dependancy, and is justified in great length in the code of + * compat_open() in libsvn_ra/wrapper_template.h. If you modify the + * implementation of svn_ra_create_callbacks(), be sure to keep the + * code in wrapper_template.h:compat_open() in sync with your + * changes. */ +svn_error_t * +svn_ra_create_callbacks(svn_ra_callbacks2_t **callbacks, + apr_pool_t *pool) +{ + *callbacks = apr_pcalloc(pool, sizeof(**callbacks)); + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_open4(svn_ra_session_t **session_p, + const char **corrected_url_p, + const char *repos_URL, + const char *uuid, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + apr_pool_t *sesspool = svn_pool_create(pool); + svn_ra_session_t *session; + const struct ra_lib_defn *defn; + const svn_ra__vtable_t *vtable = NULL; + svn_config_t *servers = NULL; + const char *server_group; + apr_uri_t repos_URI; + apr_status_t apr_err; +#ifdef CHOOSABLE_DAV_MODULE + const char *http_library = DEFAULT_HTTP_LIBRARY; +#endif + /* Auth caching parameters. */ + svn_boolean_t store_passwords = SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS; + svn_boolean_t store_auth_creds = SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS; + const char *store_plaintext_passwords + = SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS; + svn_boolean_t store_pp = SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP; + const char *store_pp_plaintext + = SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT; + const char *corrected_url; + + /* Initialize the return variable. */ + *session_p = NULL; + + apr_err = apr_uri_parse(sesspool, repos_URL, &repos_URI); + /* ### Should apr_uri_parse leave hostname NULL? It doesn't + * for "file:///" URLs, only for bogus URLs like "bogus". + * If this is the right behavior for apr_uri_parse, maybe we + * should have a svn_uri_parse wrapper. */ + if (apr_err != APR_SUCCESS || repos_URI.hostname == NULL) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Illegal repository URL '%s'"), + repos_URL); + + if (callbacks->auth_baton) + { + /* The 'store-passwords' and 'store-auth-creds' parameters used to + * live in SVN_CONFIG_CATEGORY_CONFIG. For backward compatibility, + * if values for these parameters have already been set by our + * callers, we use those values as defaults. + * + * Note that we can only catch the case where users explicitly set + * "store-passwords = no" or 'store-auth-creds = no". + * + * However, since the default value for both these options is + * currently (and has always been) "yes", users won't know + * the difference if they set "store-passwords = yes" or + * "store-auth-creds = yes" -- they'll get the expected behaviour. + */ + + if (svn_auth_get_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_DONT_STORE_PASSWORDS) != NULL) + store_passwords = FALSE; + + if (svn_auth_get_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_NO_AUTH_CACHE) != NULL) + store_auth_creds = FALSE; + } + + if (config) + { + /* Grab the 'servers' config. */ + servers = apr_hash_get(config, SVN_CONFIG_CATEGORY_SERVERS, + APR_HASH_KEY_STRING); + if (servers) + { + /* First, look in the global section. */ + + SVN_ERR(svn_config_get_bool + (servers, &store_passwords, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_PASSWORDS, + store_passwords)); + + SVN_ERR(svn_config_get_yes_no_ask + (servers, &store_plaintext_passwords, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS, + SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS)); + + SVN_ERR(svn_config_get_bool + (servers, &store_pp, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP, + store_pp)); + + SVN_ERR(svn_config_get_yes_no_ask + (servers, &store_pp_plaintext, + SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT, + SVN_CONFIG_DEFAULT_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT)); + + SVN_ERR(svn_config_get_bool + (servers, &store_auth_creds, SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + store_auth_creds)); + + /* Find out where we're about to connect to, and + * try to pick a server group based on the destination. */ + server_group = svn_config_find_group(servers, repos_URI.hostname, + SVN_CONFIG_SECTION_GROUPS, + sesspool); + + if (server_group) + { + /* Override global auth caching parameters with the ones + * for the server group, if any. */ + SVN_ERR(svn_config_get_bool(servers, &store_auth_creds, + server_group, + SVN_CONFIG_OPTION_STORE_AUTH_CREDS, + store_auth_creds)); + + SVN_ERR(svn_config_get_bool(servers, &store_passwords, + server_group, + SVN_CONFIG_OPTION_STORE_PASSWORDS, + store_passwords)); + + SVN_ERR(svn_config_get_yes_no_ask + (servers, &store_plaintext_passwords, server_group, + SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS, + store_plaintext_passwords)); + + SVN_ERR(svn_config_get_bool + (servers, &store_pp, + server_group, SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP, + store_pp)); + + SVN_ERR(svn_config_get_yes_no_ask + (servers, &store_pp_plaintext, server_group, + SVN_CONFIG_OPTION_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT, + store_pp_plaintext)); + } +#ifdef CHOOSABLE_DAV_MODULE + /* Now, which DAV-based RA method do we want to use today? */ + http_library + = svn_config_get_server_setting(servers, + server_group, /* NULL is OK */ + SVN_CONFIG_OPTION_HTTP_LIBRARY, + DEFAULT_HTTP_LIBRARY); + + if (strcmp(http_library, "neon") != 0 && + strcmp(http_library, "serf") != 0) + return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL, + _("Invalid config: unknown HTTP library " + "'%s'"), + http_library); +#endif + } + } + + if (callbacks->auth_baton) + { + /* Save auth caching parameters in the auth parameter hash. */ + if (! store_passwords) + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_DONT_STORE_PASSWORDS, ""); + + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_STORE_PLAINTEXT_PASSWORDS, + store_plaintext_passwords); + + if (! store_pp) + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_DONT_STORE_SSL_CLIENT_CERT_PP, + ""); + + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_STORE_SSL_CLIENT_CERT_PP_PLAINTEXT, + store_pp_plaintext); + + if (! store_auth_creds) + svn_auth_set_parameter(callbacks->auth_baton, + SVN_AUTH_PARAM_NO_AUTH_CACHE, ""); + } + + /* Find the library. */ + for (defn = ra_libraries; defn->ra_name != NULL; ++defn) + { + const char *scheme; + + if ((scheme = has_scheme_of(defn, repos_URL))) + { + svn_ra__init_func_t initfunc = defn->initfunc; + +#ifdef CHOOSABLE_DAV_MODULE + if (defn->schemes == dav_schemes + && strcmp(defn->ra_name, http_library) != 0) + continue; +#endif + + if (! initfunc) + SVN_ERR(load_ra_module(&initfunc, NULL, defn->ra_name, + sesspool)); + if (! initfunc) + /* Library not found. */ + continue; + + SVN_ERR(initfunc(svn_ra_version(), &vtable, sesspool)); + + SVN_ERR(check_ra_version(vtable->get_version(), scheme)); + + break; + } + } + + if (vtable == NULL) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Unrecognized URL scheme for '%s'"), + repos_URL); + + /* Create the session object. */ + session = apr_pcalloc(sesspool, sizeof(*session)); + session->vtable = vtable; + session->pool = sesspool; + + /* Ask the library to open the session. */ + SVN_ERR_W(vtable->open_session(session, &corrected_url, repos_URL, + callbacks, callback_baton, config, sesspool), + apr_psprintf(pool, "Unable to connect to a repository at URL '%s'", + repos_URL)); + + /* If the session open stuff detected a server-provided URL + correction (a 301 or 302 redirect response during the initial + OPTIONS request), then kill the session so the caller can decide + what to do. */ + if (corrected_url_p && corrected_url) + { + if (! svn_path_is_url(corrected_url)) + { + /* RFC1945 and RFC2616 state that the Location header's + value (from whence this CORRECTED_URL ultimately comes), + if present, must be an absolute URI. But some Apache + versions (those older than 2.2.11, it seems) transmit + only the path portion of the URI. See issue #3775 for + details. */ + apr_uri_t corrected_URI = repos_URI; + corrected_URI.path = (char *)corrected_url; + corrected_url = apr_uri_unparse(pool, &corrected_URI, 0); + } + *corrected_url_p = svn_uri_canonicalize(corrected_url, pool); + svn_pool_destroy(sesspool); + return SVN_NO_ERROR; + } + + /* Check the UUID. */ + if (uuid) + { + const char *repository_uuid; + + SVN_ERR(vtable->get_uuid(session, &repository_uuid, pool)); + if (strcmp(uuid, repository_uuid) != 0) + { + /* Duplicate the uuid as it is allocated in sesspool */ + repository_uuid = apr_pstrdup(pool, repository_uuid); + svn_pool_destroy(sesspool); + return svn_error_createf(SVN_ERR_RA_UUID_MISMATCH, NULL, + _("Repository UUID '%s' doesn't match " + "expected UUID '%s'"), + repository_uuid, uuid); + } + } + + *session_p = session; + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_reparent(svn_ra_session_t *session, + const char *url, + apr_pool_t *pool) +{ + const char *repos_root; + + /* Make sure the new URL is in the same repository, so that the + implementations don't have to do it. */ + SVN_ERR(svn_ra_get_repos_root2(session, &repos_root, pool)); + if (! svn_uri__is_ancestor(repos_root, url)) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("'%s' isn't in the same repository as '%s'"), + url, repos_root); + + return session->vtable->reparent(session, url, pool); +} + +svn_error_t *svn_ra_get_session_url(svn_ra_session_t *session, + const char **url, + apr_pool_t *pool) +{ + return session->vtable->get_session_url(session, url, pool); +} + +svn_error_t *svn_ra_get_path_relative_to_session(svn_ra_session_t *session, + const char **rel_path, + const char *url, + apr_pool_t *pool) +{ + const char *sess_url; + SVN_ERR(session->vtable->get_session_url(session, &sess_url, pool)); + if (strcmp(sess_url, url) == 0) + { + *rel_path = ""; + } + else + { + *rel_path = svn_uri__is_child(sess_url, url, pool); + if (! *rel_path) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("'%s' isn't a child of session URL '%s'"), + url, sess_url); + } + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_get_path_relative_to_root(svn_ra_session_t *session, + const char **rel_path, + const char *url, + apr_pool_t *pool) +{ + const char *root_url; + SVN_ERR(session->vtable->get_repos_root(session, &root_url, pool)); + if (strcmp(root_url, url) == 0) + { + *rel_path = ""; + } + else + { + *rel_path = svn_uri__is_child(root_url, url, pool); + if (! *rel_path) + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("'%s' isn't a child of repository root " + "URL '%s'"), + url, root_url); + } + + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_get_latest_revnum(svn_ra_session_t *session, + svn_revnum_t *latest_revnum, + apr_pool_t *pool) +{ + return session->vtable->get_latest_revnum(session, latest_revnum, pool); +} + +svn_error_t *svn_ra_get_dated_revision(svn_ra_session_t *session, + svn_revnum_t *revision, + apr_time_t tm, + apr_pool_t *pool) +{ + return session->vtable->get_dated_revision(session, revision, tm, pool); +} + +svn_error_t *svn_ra_change_rev_prop2(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + const svn_string_t *const *old_value_p, + const svn_string_t *value, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev)); + + /* If an old value was specified, make sure the server supports + * specifying it. */ + if (old_value_p) + { + svn_boolean_t has_atomic_revprops; + + SVN_ERR(svn_ra_has_capability(session, &has_atomic_revprops, + SVN_RA_CAPABILITY_ATOMIC_REVPROPS, + pool)); + + if (!has_atomic_revprops) + /* API violation. (Should be an ASSERT, but gstein talked me + * out of it.) */ + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Specifying 'old_value_p' is not allowed when " + "the '%s' capability is not advertised, and " + "could indicate a bug in your client"), + SVN_RA_CAPABILITY_ATOMIC_REVPROPS); + } + + return session->vtable->change_rev_prop(session, rev, name, + old_value_p, value, pool); +} + +svn_error_t *svn_ra_rev_proplist(svn_ra_session_t *session, + svn_revnum_t rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev)); + return session->vtable->rev_proplist(session, rev, props, pool); +} + +svn_error_t *svn_ra_rev_prop(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + svn_string_t **value, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(rev)); + return session->vtable->rev_prop(session, rev, name, value, pool); +} + +struct ccw_baton +{ + svn_commit_callback2_t original_callback; + void *original_baton; + + svn_ra_session_t *session; +}; + +/* Wrapper which populates the repos_root field of the commit_info struct */ +static svn_error_t * +commit_callback_wrapper(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + struct ccw_baton *ccwb = baton; + svn_commit_info_t *ci = svn_commit_info_dup(commit_info, pool); + + SVN_ERR(svn_ra_get_repos_root2(ccwb->session, &ci->repos_root, pool)); + + return ccwb->original_callback(ci, ccwb->original_baton, pool); +} + +svn_error_t *svn_ra_get_commit_editor3(svn_ra_session_t *session, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_hash_t *revprop_table, + svn_commit_callback2_t callback, + void *callback_baton, + apr_hash_t *lock_tokens, + svn_boolean_t keep_locks, + apr_pool_t *pool) +{ + /* Allocate this in a pool, since the callback will be called long after + this function as returned. */ + struct ccw_baton *ccwb = apr_palloc(pool, sizeof(*ccwb)); + + ccwb->original_callback = callback; + ccwb->original_baton = callback_baton; + ccwb->session = session; + + return session->vtable->get_commit_editor(session, editor, edit_baton, + revprop_table, + callback + ? commit_callback_wrapper + : NULL, + callback ? ccwb : NULL, + lock_tokens, keep_locks, pool); +} + +svn_error_t *svn_ra_get_file(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->get_file(session, path, revision, stream, + fetched_rev, props, pool); +} + +svn_error_t *svn_ra_get_dir(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->get_dir(session, dirents, fetched_rev, props, + path, revision, SVN_DIRENT_ALL, pool); +} + +svn_error_t *svn_ra_get_dir2(svn_ra_session_t *session, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + const char *path, + svn_revnum_t revision, + apr_uint32_t dirent_fields, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->get_dir(session, dirents, fetched_rev, props, + path, revision, dirent_fields, pool); +} + +svn_error_t *svn_ra_get_mergeinfo(svn_ra_session_t *session, + svn_mergeinfo_catalog_t *catalog, + const apr_array_header_t *paths, + svn_revnum_t revision, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + apr_pool_t *pool) +{ + svn_error_t *err; + int i; + + /* Validate path format. */ + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + SVN_ERR_ASSERT(*path != '/'); + } + + /* Check server Merge Tracking capability. */ + err = svn_ra__assert_mergeinfo_capable_server(session, NULL, pool); + if (err) + { + *catalog = NULL; + return err; + } + + return session->vtable->get_mergeinfo(session, catalog, paths, + revision, inherit, + include_descendants, pool); +} + +svn_error_t *svn_ra_do_update2(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision_to_update_to, + const char *update_target, + svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(svn_path_is_empty(update_target) + || svn_path_is_single_path_component(update_target)); + return session->vtable->do_update(session, + reporter, report_baton, + revision_to_update_to, update_target, + depth, send_copyfrom_args, + update_editor, update_baton, + pool); +} + +svn_error_t *svn_ra_do_switch2(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision_to_switch_to, + const char *switch_target, + svn_depth_t depth, + const char *switch_url, + const svn_delta_editor_t *switch_editor, + void *switch_baton, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(svn_path_is_empty(switch_target) + || svn_path_is_single_path_component(switch_target)); + return session->vtable->do_switch(session, + reporter, report_baton, + revision_to_switch_to, switch_target, + depth, switch_url, switch_editor, + switch_baton, pool); +} + +svn_error_t *svn_ra_do_status2(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + const char *status_target, + svn_revnum_t revision, + svn_depth_t depth, + const svn_delta_editor_t *status_editor, + void *status_baton, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(svn_path_is_empty(status_target) + || svn_path_is_single_path_component(status_target)); + return session->vtable->do_status(session, + reporter, report_baton, + status_target, revision, depth, + status_editor, status_baton, pool); +} + +svn_error_t *svn_ra_do_diff3(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *diff_target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t text_deltas, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, + apr_pool_t *pool) +{ + return session->vtable->do_diff(session, + reporter, report_baton, + revision, diff_target, + depth, ignore_ancestry, + text_deltas, versus_url, diff_editor, + diff_baton, pool); +} + +svn_error_t *svn_ra_get_log2(svn_ra_session_t *session, + const apr_array_header_t *paths, + svn_revnum_t start, + svn_revnum_t end, + int limit, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t *revprops, + svn_log_entry_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + if (paths) + { + int i; + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + SVN_ERR_ASSERT(*path != '/'); + } + } + + if (include_merged_revisions) + SVN_ERR(svn_ra__assert_mergeinfo_capable_server(session, NULL, pool)); + + return session->vtable->get_log(session, paths, start, end, limit, + discover_changed_paths, strict_node_history, + include_merged_revisions, revprops, + receiver, receiver_baton, pool); +} + +svn_error_t *svn_ra_check_path(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->check_path(session, path, revision, kind, pool); +} + +svn_error_t *svn_ra_stat(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_dirent_t **dirent, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->stat(session, path, revision, dirent, pool); +} + +svn_error_t *svn_ra_get_uuid2(svn_ra_session_t *session, + const char **uuid, + apr_pool_t *pool) +{ + SVN_ERR(session->vtable->get_uuid(session, uuid, pool)); + *uuid = *uuid ? apr_pstrdup(pool, *uuid) : NULL; + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_get_uuid(svn_ra_session_t *session, + const char **uuid, + apr_pool_t *pool) +{ + return session->vtable->get_uuid(session, uuid, pool); +} + +svn_error_t *svn_ra_get_repos_root2(svn_ra_session_t *session, + const char **url, + apr_pool_t *pool) +{ + SVN_ERR(session->vtable->get_repos_root(session, url, pool)); + *url = *url ? apr_pstrdup(pool, *url) : NULL; + return SVN_NO_ERROR; +} + +svn_error_t *svn_ra_get_repos_root(svn_ra_session_t *session, + const char **url, + apr_pool_t *pool) +{ + return session->vtable->get_repos_root(session, url, pool); +} + +svn_error_t *svn_ra_get_locations(svn_ra_session_t *session, + apr_hash_t **locations, + const char *path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(*path != '/'); + err = session->vtable->get_locations(session, locations, path, + peg_revision, location_revisions, pool); + if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) + { + svn_error_clear(err); + + /* Do it the slow way, using get-logs, for older servers. */ + err = svn_ra__locations_from_log(session, locations, path, + peg_revision, location_revisions, + pool); + } + return err; +} + +svn_error_t * +svn_ra_get_location_segments(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) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(*path != '/'); + err = session->vtable->get_location_segments(session, path, peg_revision, + start_rev, end_rev, + receiver, receiver_baton, pool); + if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) + { + svn_error_clear(err); + + /* Do it the slow way, using get-logs, for older servers. */ + err = svn_ra__location_segments_from_log(session, path, + peg_revision, start_rev, + end_rev, receiver, + receiver_baton, pool); + } + return err; +} + +svn_error_t *svn_ra_get_file_revs2(svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t include_merged_revisions, + svn_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(*path != '/'); + + if (include_merged_revisions) + SVN_ERR(svn_ra__assert_mergeinfo_capable_server(session, NULL, pool)); + + err = session->vtable->get_file_revs(session, path, start, end, + include_merged_revisions, + handler, handler_baton, pool); + if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) + { + svn_error_clear(err); + + /* Do it the slow way, using get-logs, for older servers. */ + err = svn_ra__file_revs_from_log(session, path, start, end, + handler, handler_baton, pool); + } + return err; +} + +svn_error_t *svn_ra_lock(svn_ra_session_t *session, + apr_hash_t *path_revs, + const char *comment, + svn_boolean_t steal_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + + SVN_ERR_ASSERT(*path != '/'); + } + + if (comment && ! svn_xml_is_xml_safe(comment, strlen(comment))) + return svn_error_create + (SVN_ERR_XML_UNESCAPABLE_DATA, NULL, + _("Lock comment contains illegal characters")); + + return session->vtable->lock(session, path_revs, comment, steal_lock, + lock_func, lock_baton, pool); +} + +svn_error_t *svn_ra_unlock(svn_ra_session_t *session, + apr_hash_t *path_tokens, + svn_boolean_t break_lock, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + + SVN_ERR_ASSERT(*path != '/'); + } + + return session->vtable->unlock(session, path_tokens, break_lock, + lock_func, lock_baton, pool); +} + +svn_error_t *svn_ra_get_lock(svn_ra_session_t *session, + svn_lock_t **lock, + const char *path, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + return session->vtable->get_lock(session, lock, path, pool); +} + +svn_error_t *svn_ra_get_locks2(svn_ra_session_t *session, + apr_hash_t **locks, + const char *path, + svn_depth_t depth, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(*path != '/'); + SVN_ERR_ASSERT((depth == svn_depth_empty) || + (depth == svn_depth_files) || + (depth == svn_depth_immediates) || + (depth == svn_depth_infinity)); + return session->vtable->get_locks(session, locks, path, depth, pool); +} + +svn_error_t *svn_ra_get_locks(svn_ra_session_t *session, + apr_hash_t **locks, + const char *path, + apr_pool_t *pool) +{ + return svn_ra_get_locks2(session, locks, path, svn_depth_infinity, pool); +} + +svn_error_t *svn_ra_replay(svn_ra_session_t *session, + svn_revnum_t revision, + svn_revnum_t low_water_mark, + svn_boolean_t text_deltas, + const svn_delta_editor_t *editor, + void *edit_baton, + apr_pool_t *pool) +{ + return session->vtable->replay(session, revision, low_water_mark, + text_deltas, editor, edit_baton, pool); +} + +svn_error_t * +svn_ra_replay_range(svn_ra_session_t *session, + svn_revnum_t start_revision, + svn_revnum_t end_revision, + svn_revnum_t low_water_mark, + svn_boolean_t text_deltas, + svn_ra_replay_revstart_callback_t revstart_func, + svn_ra_replay_revfinish_callback_t revfinish_func, + void *replay_baton, + apr_pool_t *pool) +{ + svn_error_t *err = + session->vtable->replay_range(session, start_revision, end_revision, + low_water_mark, text_deltas, + revstart_func, revfinish_func, + replay_baton, pool); + + if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) + { + apr_pool_t *subpool = svn_pool_create(pool); + svn_revnum_t rev; + + svn_error_clear(err); + err = SVN_NO_ERROR; + + for (rev = start_revision ; rev <= end_revision ; rev++) + { + const svn_delta_editor_t *editor; + void *edit_baton; + apr_hash_t *rev_props; + + svn_pool_clear(subpool); + + SVN_ERR(svn_ra_rev_proplist(session, rev, &rev_props, subpool)); + + SVN_ERR(revstart_func(rev, replay_baton, + &editor, &edit_baton, + rev_props, + subpool)); + SVN_ERR(svn_ra_replay(session, rev, low_water_mark, + text_deltas, editor, edit_baton, + subpool)); + SVN_ERR(revfinish_func(rev, replay_baton, + editor, edit_baton, + rev_props, + subpool)); + } + svn_pool_destroy(subpool); + } + + return err; +} + +svn_error_t *svn_ra_has_capability(svn_ra_session_t *session, + svn_boolean_t *has, + const char *capability, + apr_pool_t *pool) +{ + return session->vtable->has_capability(session, has, capability, pool); +} + +svn_error_t * +svn_ra_get_deleted_rev(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool) +{ + svn_error_t *err; + + /* Path must be relative. */ + SVN_ERR_ASSERT(*path != '/'); + + 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")); + err = session->vtable->get_deleted_rev(session, path, + peg_revision, + end_revision, + revision_deleted, + pool); + if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE /* serf */ + || err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED)) /* neon */ + { + svn_error_clear(err); + + /* Do it the slow way, using get-logs, for older servers. */ + err = svn_ra__get_deleted_rev_from_log(session, path, peg_revision, + end_revision, revision_deleted, + pool); + } + return err; +} + + + +svn_error_t * +svn_ra_print_modules(svn_stringbuf_t *output, + apr_pool_t *pool) +{ + const struct ra_lib_defn *defn; + const char * const *schemes; + svn_ra__init_func_t initfunc; + const svn_ra__vtable_t *vtable; + apr_pool_t *iterpool = svn_pool_create(pool); + + for (defn = ra_libraries; defn->ra_name != NULL; ++defn) + { + char *line; + + svn_pool_clear(iterpool); + + initfunc = defn->initfunc; + if (! initfunc) + SVN_ERR(load_ra_module(&initfunc, NULL, defn->ra_name, + iterpool)); + + if (initfunc) + { + SVN_ERR(initfunc(svn_ra_version(), &vtable, iterpool)); + + SVN_ERR(check_ra_version(vtable->get_version(), defn->ra_name)); + + /* Note: if you change the formatting of the description, + bear in mind that ra_svn's description has multiple lines when + built with SASL. */ + line = apr_psprintf(iterpool, "* ra_%s : %s\n", + defn->ra_name, + vtable->get_description()); + svn_stringbuf_appendcstr(output, line); + + for (schemes = vtable->get_schemes(iterpool); *schemes != NULL; + ++schemes) + { + line = apr_psprintf(iterpool, _(" - handles '%s' scheme\n"), + *schemes); + svn_stringbuf_appendcstr(output, line); + } + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_ra_print_ra_libraries(svn_stringbuf_t **descriptions, + void *ra_baton, + apr_pool_t *pool) +{ + *descriptions = svn_stringbuf_create("", pool); + return svn_ra_print_modules(*descriptions, pool); +} + + +/* Return the library version number. */ +const svn_version_t * +svn_ra_version(void) +{ + SVN_VERSION_BODY; +} + + +/*** Compatibility Interfaces **/ +svn_error_t * +svn_ra_init_ra_libs(void **ra_baton, + apr_pool_t *pool) +{ + *ra_baton = pool; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_get_ra_library(svn_ra_plugin_t **library, + void *ra_baton, + const char *url, + apr_pool_t *pool) +{ + const struct ra_lib_defn *defn; + apr_pool_t *load_pool = ra_baton; + apr_hash_t *ht = apr_hash_make(pool); + + /* Figure out which RA library key matches URL. */ + for (defn = ra_libraries; defn->ra_name != NULL; ++defn) + { + const char *scheme; + if ((scheme = has_scheme_of(defn, url))) + { + svn_ra_init_func_t compat_initfunc = defn->compat_initfunc; + + if (! compat_initfunc) + { + SVN_ERR(load_ra_module + (NULL, &compat_initfunc, defn->ra_name, load_pool)); + } + if (! compat_initfunc) + { + continue; + } + + SVN_ERR(compat_initfunc(SVN_RA_ABI_VERSION, load_pool, ht)); + + *library = apr_hash_get(ht, scheme, APR_HASH_KEY_STRING); + + /* The library may support just a subset of the schemes listed, + so we have to check here too. */ + if (! *library) + break; + + return check_ra_version((*library)->get_version(), scheme); + } + } + + /* Couldn't find a match... */ + *library = NULL; + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Unrecognized URL scheme '%s'"), url); +} + +/* For each libsvn_ra_foo library that is not linked in, provide a default + implementation for svn_ra_foo_init which returns a "not implemented" + error. */ + +#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_NEON +svn_error_t * +svn_ra_dav_init(int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); +} +#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_NEON */ + +#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_SVN +svn_error_t * +svn_ra_svn_init(int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); +} +#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_SVN */ + +#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL +svn_error_t * +svn_ra_local_init(int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); +} +#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL */ + +#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_SERF +svn_error_t * +svn_ra_serf_init(int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); +} +#endif /* ! SVN_LIBSVN_CLIENT_LINKS_RA_SERF */ diff --git a/subversion/libsvn_ra/ra_loader.h b/subversion/libsvn_ra/ra_loader.h new file mode 100644 index 0000000..ddb2581 --- /dev/null +++ b/subversion/libsvn_ra/ra_loader.h @@ -0,0 +1,458 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + * + * @file ra_loader.h + * @brief structures related to repository access, private to libsvn_ra and the + * RA implementation libraries. + */ + + + +#ifndef LIBSVN_RA_RA_LOADER_H +#define LIBSVN_RA_RA_LOADER_H + +#include "svn_ra.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The RA layer vtable. */ +typedef struct svn_ra__vtable_t { + /* This field should always remain first in the vtable. */ + const svn_version_t *(*get_version)(void); + + /* Return a short description of the RA implementation, as a localized + * string. */ + const char *(*get_description)(void); + + /* Return a list of actual URI schemes supported by this implementation. + * The returned array is NULL-terminated. */ + const char * const *(*get_schemes)(apr_pool_t *pool); + + /* Implementations of the public API functions. */ + + /* See svn_ra_open4(). */ + /* All fields in SESSION, except priv, have been initialized by the + time this is called. SESSION->priv may be set by this function. */ + svn_error_t *(*open_session)(svn_ra_session_t *session, + const char **corrected_url, + const char *repos_URL, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool); + /* See svn_ra_reparent(). */ + /* URL is guaranteed to have what get_repos_root() returns as a prefix. */ + svn_error_t *(*reparent)(svn_ra_session_t *session, + const char *url, + apr_pool_t *pool); + /* See svn_ra_get_session_url(). */ + svn_error_t *(*get_session_url)(svn_ra_session_t *session, + const char **url, + apr_pool_t *pool); + /* See svn_ra_get_latest_revnum(). */ + svn_error_t *(*get_latest_revnum)(svn_ra_session_t *session, + svn_revnum_t *latest_revnum, + apr_pool_t *pool); + /* See svn_ra_get_dated_revision(). */ + svn_error_t *(*get_dated_revision)(svn_ra_session_t *session, + svn_revnum_t *revision, + apr_time_t tm, + apr_pool_t *pool); + /* See svn_ra_change_rev_prop2(). */ + svn_error_t *(*change_rev_prop)(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + const svn_string_t *const *old_value_p, + const svn_string_t *value, + apr_pool_t *pool); + + /* See svn_ra_rev_proplist(). */ + svn_error_t *(*rev_proplist)(svn_ra_session_t *session, + svn_revnum_t rev, + apr_hash_t **props, + apr_pool_t *pool); + /* See svn_ra_rev_prop(). */ + svn_error_t *(*rev_prop)(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + svn_string_t **value, + apr_pool_t *pool); + /* See svn_ra_get_commit_editor3(). */ + svn_error_t *(*get_commit_editor)(svn_ra_session_t *session, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_hash_t *revprop_table, + svn_commit_callback2_t callback, + void *callback_baton, + apr_hash_t *lock_tokens, + svn_boolean_t keep_locks, + apr_pool_t *pool); + /* See svn_ra_get_file(). */ + svn_error_t *(*get_file)(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool); + /* See svn_ra_get_dir2(). */ + svn_error_t *(*get_dir)(svn_ra_session_t *session, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + const char *path, + svn_revnum_t revision, + apr_uint32_t dirent_fields, + apr_pool_t *pool); + /* See svn_ra_get_mergeinfo(). */ + svn_error_t *(*get_mergeinfo)(svn_ra_session_t *session, + svn_mergeinfo_catalog_t *mergeinfo, + const apr_array_header_t *paths, + svn_revnum_t revision, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_merged_revisions, + apr_pool_t *pool); + /* See svn_ra_do_update2(). */ + svn_error_t *(*do_update)(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision_to_update_to, + const char *update_target, + svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *pool); + /* See svn_ra_do_switch2(). */ + svn_error_t *(*do_switch)(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision_to_switch_to, + const char *switch_target, + svn_depth_t depth, + const char *switch_url, + const svn_delta_editor_t *switch_editor, + void *switch_baton, + apr_pool_t *pool); + /* See svn_ra_do_status2(). */ + svn_error_t *(*do_status)(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + const char *status_target, + svn_revnum_t revision, + svn_depth_t depth, + const svn_delta_editor_t *status_editor, + void *status_baton, + apr_pool_t *pool); + /* See svn_ra_do_diff3(). */ + svn_error_t *(*do_diff)(svn_ra_session_t *session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *diff_target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t text_deltas, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, + apr_pool_t *pool); + /* See svn_ra_get_log2(). */ + svn_error_t *(*get_log)(svn_ra_session_t *session, + const apr_array_header_t *paths, + svn_revnum_t start, + svn_revnum_t end, + int limit, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t *revprops, + svn_log_entry_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool); + /* See svn_ra_check_path(). */ + svn_error_t *(*check_path)(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *pool); + /* See svn_ra_stat(). */ + svn_error_t *(*stat)(svn_ra_session_t *session, + const char *path, + svn_revnum_t revision, + svn_dirent_t **dirent, + apr_pool_t *pool); + /* See svn_ra_get_uuid2(). */ + svn_error_t *(*get_uuid)(svn_ra_session_t *session, + const char **uuid, + apr_pool_t *pool); + /* See svn_ra_get_repos_root2(). */ + svn_error_t *(*get_repos_root)(svn_ra_session_t *session, + const char **url, + apr_pool_t *pool); + /* See svn_ra_get_locations(). */ + svn_error_t *(*get_locations)(svn_ra_session_t *session, + apr_hash_t **locations, + const char *path, + svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool); + /* See svn_ra_get_location_segments(). */ + svn_error_t *(*get_location_segments)(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 rcvr, + void *receiver_baton, + apr_pool_t *pool); + /* See svn_ra_get_file_revs2(). */ + svn_error_t *(*get_file_revs)(svn_ra_session_t *session, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t include_merged_revisions, + svn_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool); + /* See svn_ra_lock(). */ + svn_error_t *(*lock)(svn_ra_session_t *session, + apr_hash_t *path_revs, + const char *comment, + svn_boolean_t force, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool); + /* See svn_ra_unlock(). */ + svn_error_t *(*unlock)(svn_ra_session_t *session, + apr_hash_t *path_tokens, + svn_boolean_t force, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *pool); + /* See svn_ra_get_lock(). */ + svn_error_t *(*get_lock)(svn_ra_session_t *session, + svn_lock_t **lock, + const char *path, + apr_pool_t *pool); + /* See svn_ra_get_locks2(). */ + svn_error_t *(*get_locks)(svn_ra_session_t *session, + apr_hash_t **locks, + const char *path, + svn_depth_t depth, + apr_pool_t *pool); + /* See svn_ra_replay(). */ + svn_error_t *(*replay)(svn_ra_session_t *session, + svn_revnum_t revision, + svn_revnum_t low_water_mark, + svn_boolean_t text_deltas, + const svn_delta_editor_t *editor, + void *edit_baton, + apr_pool_t *pool); + /* See svn_ra_has_capability(). */ + svn_error_t *(*has_capability)(svn_ra_session_t *session, + svn_boolean_t *has, + const char *capability, + apr_pool_t *pool); + /* See svn_ra_replay_range(). */ + svn_error_t * + (*replay_range)(svn_ra_session_t *session, + svn_revnum_t start_revision, + svn_revnum_t end_revision, + svn_revnum_t low_water_mark, + svn_boolean_t text_deltas, + svn_ra_replay_revstart_callback_t revstart_func, + svn_ra_replay_revfinish_callback_t revfinish_func, + void *replay_baton, + apr_pool_t *pool); + /* See svn_ra_get_deleted_rev(). */ + svn_error_t *(*get_deleted_rev)(svn_ra_session_t *session, + const char *path, + svn_revnum_t peg_revision, + svn_revnum_t end_revision, + svn_revnum_t *revision_deleted, + apr_pool_t *pool); + +} svn_ra__vtable_t; + +/* The RA session object. */ +struct svn_ra_session_t { + const svn_ra__vtable_t *vtable; + + /* Pool used to manage this session. */ + apr_pool_t *pool; + + /* Private data for the RA implementation. */ + void *priv; +}; + +/* Each libsvn_ra_foo defines a function named svn_ra_foo__init of this type. + * + * The LOADER_VERSION parameter must remain first in the list, and the + * function must use the C calling convention on all platforms, so that + * the init functions can safely read the version parameter. + * + * POOL will be available as long as this module is being used. + * + * ### need to force this to be __cdecl on Windows... how?? + */ +typedef svn_error_t * +(*svn_ra__init_func_t)(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool); + +/* Declarations of the init functions for the available RA libraries. */ +svn_error_t *svn_ra_local__init(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool); +svn_error_t *svn_ra_svn__init(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool); +svn_error_t *svn_ra_neon__init(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool); +svn_error_t *svn_ra_serf__init(const svn_version_t *loader_version, + const svn_ra__vtable_t **vtable, + apr_pool_t *pool); + + + +/*** Compat Functions ***/ + +/** + * Set *LOCATIONS to the locations (at the repository revisions + * LOCATION_REVISIONS) of the file identified by PATH in PEG_REVISION. + * PATH is relative to the URL to which SESSION was opened. + * LOCATION_REVISIONS is an array of svn_revnum_t's. *LOCATIONS will + * be a mapping from the revisions to their appropriate absolute + * paths. If the file doesn't exist in a location_revision, that + * revision will be ignored. + * + * Use POOL for all allocations. + * + * NOTE: This function uses the RA get_log interfaces to do its work, + * as a fallback mechanism for servers which don't support the native + * get_locations API. + */ +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); + +/** + * Call RECEIVER (with RECEIVER_BATON) for each segment in the + * location history of PATH in START_REV, working backwards in time + * from START_REV to END_REV. + * + * END_REV may be SVN_INVALID_REVNUM to indicate that you want to + * trace the history of the object to its origin. + * + * START_REV may be SVN_INVALID_REVNUM to indicate that you want to + * trace the history of the object beginning in the HEAD revision. + * Otherwise, START_REV must be younger than END_REV (unless END_REV + * is SVN_INVALID_REVNUM). + * + * Use POOL for all allocations. + * + * NOTE: This function uses the RA get_log interfaces to do its work, + * as a fallback mechanism for servers which don't support the native + * get_location_segments API. + */ +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); + +/** + * Retrieve a subset of the interesting revisions of a file PATH + * as seen in revision END (see svn_fs_history_prev() for a + * definition of "interesting revisions"). Invoke HANDLER with + * @a handler_baton as its first argument for each such revision. + * @a session is an open RA session. Use POOL for all allocations. + * + * If there is an interesting revision of the file that is less than or + * equal to START, the iteration will begin at that revision. + * Else, the iteration will begin at the first revision of the file in + * the repository, which has to be less than or equal to END. Note + * that if the function succeeds, HANDLER will have been called at + * least once. + * + * In a series of calls to HANDLER, the file contents for the first + * interesting revision will be provided as a text delta against the + * empty file. In the following calls, the delta will be against the + * fulltext contents for the previous call. + * + * NOTE: This function uses the RA get_log interfaces to do its work, + * as a fallback mechanism for servers which don't support the native + * get_location_segments API. + */ +svn_error_t * +svn_ra__file_revs_from_log(svn_ra_session_t *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); + + +/** + * Given a path REL_DELETED_PATH, relative to the URL of SESSION, which + * exists at PEG_REVISION, and an END_REVISION > PEG_REVISION at which + * REL_DELETED_PATH no longer exists, set *REVISION_DELETED to the revision + * REL_DELETED_PATH was first deleted or replaced, within the inclusive + * revision range defined by PEG_REVISION and END_REVISION. + * + * If REL_DELETED_PATH does not exist at PEG_REVISION or was not deleted prior + * to END_REVISION within the specified range, then set *REVISION_DELETED to + * SVN_INVALID_REVNUM. If PEG_REVISION or END_REVISION are invalid or if + * END_REVISION <= PEG_REVISION, then return SVN_ERR_CLIENT_BAD_REVISION. + * + * Use POOL for all allocations. + * + * NOTE: This function uses the RA get_log interfaces to do its work, + * as a fallback mechanism for servers which don't support the native + * get_deleted_rev API. + */ +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); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subversion/libsvn_ra/util.c b/subversion/libsvn_ra/util.c new file mode 100644 index 0000000..47e4865 --- /dev/null +++ b/subversion/libsvn_ra/util.c @@ -0,0 +1,242 @@ +/* + * util.c: Repository access utility routines. + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* ==================================================================== */ + +/*** Includes. ***/ +#include <apr_pools.h> +#include <apr_network_io.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_ra.h" + +#include "svn_private_config.h" +#include "private/svn_ra_private.h" + +svn_error_t * +svn_ra__assert_mergeinfo_capable_server(svn_ra_session_t *ra_session, + const char *path_or_url, + apr_pool_t *pool) +{ + svn_boolean_t mergeinfo_capable; + SVN_ERR(svn_ra_has_capability(ra_session, &mergeinfo_capable, + SVN_RA_CAPABILITY_MERGEINFO, pool)); + if (! mergeinfo_capable) + { + if (path_or_url == NULL) + { + svn_error_t *err = svn_ra_get_session_url(ra_session, &path_or_url, + pool); + if (err) + { + /* The SVN_ERR_UNSUPPORTED_FEATURE error is more important, + so dummy up the session's URL and chuck this error. */ + svn_error_clear(err); + path_or_url = "<repository>"; + } + } + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Retrieval of mergeinfo unsupported by '%s'"), + svn_path_is_url(path_or_url) + ? path_or_url + : svn_dirent_local_style(path_or_url, pool)); + } + return SVN_NO_ERROR; +} + +/* Does ERR mean "the current value of the revprop isn't equal to + the *OLD_VALUE_P you gave me"? + */ +static svn_boolean_t is_atomicity_error(svn_error_t *err) +{ + return svn_error_find_cause(err, SVN_ERR_FS_PROP_BASEVALUE_MISMATCH) != NULL; +} + +svn_error_t * +svn_ra__release_operational_lock(svn_ra_session_t *session, + const char *lock_revprop_name, + const svn_string_t *mylocktoken, + apr_pool_t *scratch_pool) +{ + svn_string_t *reposlocktoken; + svn_boolean_t be_atomic; + + SVN_ERR(svn_ra_has_capability(session, &be_atomic, + SVN_RA_CAPABILITY_ATOMIC_REVPROPS, + scratch_pool)); + SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name, + &reposlocktoken, scratch_pool)); + if (reposlocktoken && svn_string_compare(reposlocktoken, mylocktoken)) + { + svn_error_t *err; + + err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name, + be_atomic ? &mylocktoken : NULL, NULL, + scratch_pool); + if (is_atomicity_error(err)) + { + return svn_error_createf(err->apr_err, err, + _("Lock was stolen by '%s'; unable to " + "remove it"), reposlocktoken->data); + } + else + SVN_ERR(err); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra__get_operational_lock(const svn_string_t **lock_string_p, + const svn_string_t **stolen_lock_p, + svn_ra_session_t *session, + const char *lock_revprop_name, + svn_boolean_t steal_lock, + int num_retries, + svn_ra__lock_retry_func_t retry_func, + void *retry_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + char hostname_str[APRMAXHOSTLEN + 1] = { 0 }; + svn_string_t *mylocktoken, *reposlocktoken; + apr_status_t apr_err; + svn_boolean_t be_atomic; + apr_pool_t *subpool; + int i; + + *lock_string_p = NULL; + if (stolen_lock_p) + *stolen_lock_p = NULL; + + SVN_ERR(svn_ra_has_capability(session, &be_atomic, + SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool)); + + /* We build a lock token from the local hostname and a UUID. */ + apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, + _("Unable to determine local hostname")); + mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str, + svn_uuid_generate(pool)); + + /* Ye Olde Retry Loope */ + subpool = svn_pool_create(pool); + + for (i = 0; i < num_retries; ++i) + { + svn_error_t *err; + const svn_string_t *unset = NULL; + + svn_pool_clear(subpool); + + /* Check for cancellation. If we're cancelled, don't leave a + stray lock behind! */ + if (cancel_func) + { + err = cancel_func(cancel_baton); + if (err && err->apr_err == SVN_ERR_CANCELLED) + return svn_error_compose_create( + svn_ra__release_operational_lock(session, + lock_revprop_name, + mylocktoken, + subpool), + err); + SVN_ERR(err); + } + + /* Ask the repository for the value of the LOCK_REVPROP_NAME. */ + SVN_ERR(svn_ra_rev_prop(session, 0, lock_revprop_name, + &reposlocktoken, subpool)); + + /* Did we get a value from the repository? We'll check to see + if it matches our token. If so, we call it success. If not + and we're told to steal locks, we remember the existing lock + token and fall through to the locking code; othewise, we + sleep and retry. */ + if (reposlocktoken) + { + if (svn_string_compare(reposlocktoken, mylocktoken)) + { + *lock_string_p = mylocktoken; + return SVN_NO_ERROR; + } + else if (! steal_lock) + { + if (retry_func) + SVN_ERR(retry_func(retry_baton, reposlocktoken, subpool)); + apr_sleep(apr_time_from_sec(1)); + continue; + } + else + { + if (stolen_lock_p) + *stolen_lock_p = svn_string_dup(reposlocktoken, pool); + unset = reposlocktoken; + } + } + + /* No lock value in the repository, or we plan to steal it? + Well, if we've got a spare iteration, we'll try to set the + lock. (We use the spare iteration to verify that we still + have the lock after setting it.) */ + if (i < num_retries - 1) + { + /* Except in the very last iteration, try to set the lock. */ + err = svn_ra_change_rev_prop2(session, 0, lock_revprop_name, + be_atomic ? &unset : NULL, + mylocktoken, subpool); + + if (be_atomic && err && is_atomicity_error(err)) + { + /* Someone else has the lock. No problem, we'll loop again. */ + svn_error_clear(err); + } + else if (be_atomic && err == SVN_NO_ERROR) + { + /* Yay! We have the lock! However, for compatibility + with concurrent processes that don't support + atomicity, loop anyway to double-check that they + haven't overwritten our lock. + */ + continue; + } + else + { + /* We have a genuine error, or aren't atomic and need + to loop. */ + SVN_ERR(err); + } + } + } + + return svn_error_createf(APR_EINVAL, NULL, + _("Couldn't get lock on destination repos " + "after %d attempts"), i); +} diff --git a/subversion/libsvn_ra/wrapper_template.h b/subversion/libsvn_ra/wrapper_template.h new file mode 100644 index 0000000..e5426dc --- /dev/null +++ b/subversion/libsvn_ra/wrapper_template.h @@ -0,0 +1,506 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + */ + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_time.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_props.h" +#include "svn_compat.h" + +/* This file is a template for a compatibility wrapper for an RA library. + * It contains an svn_ra_plugin_t and wrappers for all of its functions, + * implemented in terms of svn_ra__vtable_t functions. It also contains + * the implementations of an svn_ra_FOO_init for the FOO RA library. + * + * A file in the RA library includes this file, providing the + * following macros before inclusion: + * + * NAME The library name, e.g. "ra_local". + * DESCRIPTION The short library description as a string constant. + * VTBL The name of an svn_ra_vtable_t object for the library. + * INITFUNC The init function for the library, e.g. svn_ra_local__init. + * COMPAT_INITFUNC The compatibility init function, e.g. svn_ra_local_init. + */ + +/* Check that all our "arguments" are defined. */ +#if ! defined(NAME) || ! defined(DESCRIPTION) || ! defined(VTBL) \ + || ! defined(INITFUNC) || ! defined(COMPAT_INITFUNC) +#error Missing define for RA compatibility wrapper. +#endif + + +static svn_error_t *compat_open(void **session_baton, + const char *repos_URL, + const svn_ra_callbacks_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool) +{ + /* Here, we should be calling svn_ra_create_callbacks to initialize + * the svn_ra_callbacks2_t structure. However, doing that + * introduces a circular dependancy between libsvn_ra and + * libsvn_ra_{local,neon,serf,svn}, which include + * wrapper_template.h. In turn, circular dependancies break the + * build on win32 (and possibly other systems). + * + * In order to avoid this happening at all, the code of + * svn_ra_create_callbacks is duplicated here. This is evil, but + * the alternative (creating a new ra_util library) would be massive + * overkill for the time being. Just be sure to keep the following + * line and the code of svn_ra_create_callbacks in sync. */ + apr_pool_t *sesspool = svn_pool_create(pool); + svn_ra_callbacks2_t *callbacks2 = apr_pcalloc(sesspool, + sizeof(*callbacks2)); + + svn_ra_session_t *sess = apr_pcalloc(sesspool, sizeof(*sess)); + const char *session_url; + + sess->vtable = &VTBL; + sess->pool = sesspool; + + callbacks2->open_tmp_file = callbacks->open_tmp_file; + callbacks2->auth_baton = callbacks->auth_baton; + callbacks2->get_wc_prop = callbacks->get_wc_prop; + callbacks2->set_wc_prop = callbacks->set_wc_prop; + callbacks2->push_wc_prop = callbacks->push_wc_prop; + callbacks2->invalidate_wc_props = callbacks->invalidate_wc_props; + callbacks2->progress_func = NULL; + callbacks2->progress_baton = NULL; + + SVN_ERR(VTBL.open_session(sess, &session_url, repos_URL, + callbacks2, callback_baton, config, sesspool)); + + if (strcmp(repos_URL, session_url) != 0) + { + svn_pool_destroy(sesspool); + return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL, + _("Session URL '%s' does not match requested " + " URL '%s', and redirection was disallowed."), + session_url, repos_URL); + } + + *session_baton = sess; + return SVN_NO_ERROR; +} + +static svn_error_t *compat_get_latest_revnum(void *session_baton, + svn_revnum_t *latest_revnum, + apr_pool_t *pool) +{ + return VTBL.get_latest_revnum(session_baton, latest_revnum, pool); +} + +static svn_error_t *compat_get_dated_revision(void *session_baton, + svn_revnum_t *revision, + apr_time_t tm, + apr_pool_t *pool) +{ + return VTBL.get_dated_revision(session_baton, revision, tm, pool); +} + +static svn_error_t *compat_change_rev_prop(void *session_baton, + svn_revnum_t rev, + const char *propname, + const svn_string_t *value, + apr_pool_t *pool) +{ + return VTBL.change_rev_prop(session_baton, rev, propname, NULL, value, pool); +} + +static svn_error_t *compat_rev_proplist(void *session_baton, + svn_revnum_t rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + return VTBL.rev_proplist(session_baton, rev, props, pool); +} + +static svn_error_t *compat_rev_prop(void *session_baton, + svn_revnum_t rev, + const char *propname, + svn_string_t **value, + apr_pool_t *pool) +{ + return VTBL.rev_prop(session_baton, rev, propname, value, pool); +} + +static svn_error_t *compat_get_commit_editor(void *session_baton, + const svn_delta_editor_t + **editor, + void **edit_baton, + const char *log_msg, + svn_commit_callback_t callback, + void *callback_baton, + apr_pool_t *pool) +{ + svn_commit_callback2_t callback2; + void *callback2_baton; + apr_hash_t *revprop_table = apr_hash_make(pool); + + svn_compat_wrap_commit_callback(&callback2, &callback2_baton, + callback, callback_baton, + pool); + apr_hash_set(revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, + svn_string_create(log_msg, pool)); + return VTBL.get_commit_editor(session_baton, editor, edit_baton, + revprop_table, callback2, callback2_baton, + NULL, TRUE, pool); +} + +static svn_error_t *compat_get_file(void *session_baton, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + return VTBL.get_file(session_baton, path, revision, stream, fetched_rev, + props, pool); +} + +static svn_error_t *compat_get_dir(void *session_baton, + const char *path, + svn_revnum_t revision, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + return VTBL.get_dir(session_baton, dirents, fetched_rev, props, + path, revision, SVN_DIRENT_ALL, pool); +} + +/** Reporter compat code. **/ + +struct compat_report_baton { + const svn_ra_reporter3_t *reporter; + void *baton; +}; + +static svn_error_t *compat_set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_boolean_t start_empty, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = report_baton; + + return crb->reporter->set_path(crb->baton, path, revision, + svn_depth_infinity, start_empty, + NULL, pool); +} + +static svn_error_t *compat_delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = report_baton; + + return crb->reporter->delete_path(crb->baton, path, pool); +} + +static svn_error_t *compat_link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_boolean_t start_empty, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = report_baton; + + return crb->reporter->link_path(crb->baton, path, url, revision, + svn_depth_infinity, start_empty, + NULL, pool); +} + +static svn_error_t *compat_finish_report(void *report_baton, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = report_baton; + + return crb->reporter->finish_report(crb->baton, pool); +} + +static svn_error_t *compat_abort_report(void *report_baton, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = report_baton; + + return crb->reporter->abort_report(crb->baton, pool); +} + +static const svn_ra_reporter_t compat_reporter = { + compat_set_path, + compat_delete_path, + compat_link_path, + compat_finish_report, + compat_abort_report +}; + +static void compat_wrap_reporter(const svn_ra_reporter_t **reporter, + void **baton, + const svn_ra_reporter3_t *wrapped, + void *wrapped_baton, + apr_pool_t *pool) +{ + struct compat_report_baton *crb = apr_palloc(pool, sizeof(*crb)); + crb->reporter = wrapped; + crb->baton = wrapped_baton; + + *reporter = &compat_reporter; + *baton = crb; +} + +static svn_error_t *compat_do_update(void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t revision_to_update_to, + const char *update_target, + svn_boolean_t recurse, + const svn_delta_editor_t *editor, + void *update_baton, + apr_pool_t *pool) +{ + const svn_ra_reporter3_t *reporter3; + void *baton3; + svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); + + SVN_ERR(VTBL.do_update(session_baton, &reporter3, &baton3, + revision_to_update_to, update_target, depth, + FALSE, /* no copyfrom args */ + editor, update_baton, pool)); + compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *compat_do_switch(void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t revision_to_switch_to, + const char *switch_target, + svn_boolean_t recurse, + const char *switch_url, + const svn_delta_editor_t *editor, + void *switch_baton, + apr_pool_t *pool) +{ + const svn_ra_reporter3_t *reporter3; + void *baton3; + svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); + + SVN_ERR(VTBL.do_switch(session_baton, &reporter3, &baton3, + revision_to_switch_to, switch_target, depth, + switch_url, editor, switch_baton, pool)); + + compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *compat_do_status(void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + const char *status_target, + svn_revnum_t revision, + svn_boolean_t recurse, + const svn_delta_editor_t *editor, + void *status_baton, + apr_pool_t *pool) +{ + const svn_ra_reporter3_t *reporter3; + void *baton3; + svn_depth_t depth = SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse); + + SVN_ERR(VTBL.do_status(session_baton, &reporter3, &baton3, status_target, + revision, depth, editor, status_baton, pool)); + + compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *compat_do_diff(void *session_baton, + const svn_ra_reporter_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *diff_target, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + const char *versus_url, + const svn_delta_editor_t *diff_editor, + void *diff_baton, + apr_pool_t *pool) +{ + const svn_ra_reporter3_t *reporter3; + void *baton3; + svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse); + + SVN_ERR(VTBL.do_diff(session_baton, &reporter3, &baton3, revision, + diff_target, depth, ignore_ancestry, TRUE, + versus_url, diff_editor, diff_baton, pool)); + + compat_wrap_reporter(reporter, report_baton, reporter3, baton3, pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *compat_get_log(void *session_baton, + const apr_array_header_t *paths, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_log_message_receiver_t receiver, + void *receiver_baton, + apr_pool_t *pool) +{ + svn_log_entry_receiver_t receiver2; + void *receiver2_baton; + + svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton, + receiver, receiver_baton, + pool); + + return VTBL.get_log(session_baton, paths, start, end, 0, /* limit */ + discover_changed_paths, strict_node_history, + FALSE, /* include_merged_revisions */ + svn_compat_log_revprops_in(pool), /* revprops */ + receiver2, receiver2_baton, pool); +} + +static svn_error_t *compat_check_path(void *session_baton, + const char *path, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *pool) +{ + return VTBL.check_path(session_baton, path, revision, kind, pool); +} + +static svn_error_t *compat_get_uuid(void *session_baton, + const char **uuid, + apr_pool_t *pool) +{ + return VTBL.get_uuid(session_baton, uuid, pool); +} + +static svn_error_t *compat_get_repos_root(void *session_baton, + const char **url, + apr_pool_t *pool) +{ + return VTBL.get_repos_root(session_baton, url, pool); +} + +static svn_error_t *compat_get_locations(void *session_baton, + apr_hash_t **locations, + const char *path, + svn_revnum_t peg_revision, + apr_array_header_t *location_revs, + apr_pool_t *pool) +{ + return VTBL.get_locations(session_baton, locations, path, peg_revision, + location_revs, pool); +} + +static svn_error_t *compat_get_file_revs(void *session_baton, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_ra_file_rev_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_file_rev_handler_t handler2; + void *handler2_baton; + + svn_compat_wrap_file_rev_handler(&handler2, &handler2_baton, + handler, handler_baton, + pool); + + return VTBL.get_file_revs(session_baton, path, start, end, + FALSE, /* include merged revisions */ + handler2, handler2_baton, pool); +} + +static const svn_version_t *compat_get_version(void) +{ + return VTBL.get_version(); +} + + +static const svn_ra_plugin_t compat_plugin = { + NAME, + DESCRIPTION, + compat_open, + compat_get_latest_revnum, + compat_get_dated_revision, + compat_change_rev_prop, + compat_rev_proplist, + compat_rev_prop, + compat_get_commit_editor, + compat_get_file, + compat_get_dir, + compat_do_update, + compat_do_switch, + compat_do_status, + compat_do_diff, + compat_get_log, + compat_check_path, + compat_get_uuid, + compat_get_repos_root, + compat_get_locations, + compat_get_file_revs, + compat_get_version +}; + +svn_error_t * +COMPAT_INITFUNC(int abi_version, + apr_pool_t *pool, + apr_hash_t *hash) +{ + const svn_ra__vtable_t *vtable; + const char * const * schemes; + + if (abi_version < 1 + || abi_version > SVN_RA_ABI_VERSION) + return svn_error_createf(SVN_ERR_RA_UNSUPPORTED_ABI_VERSION, NULL, + _("Unsupported RA plugin ABI version (%d) " + "for %s"), abi_version, NAME); + + /* We call the new init function so it can check library dependencies or + do other initialization things. We fake the loader version, since we + rely on the ABI version check instead. */ + SVN_ERR(INITFUNC(VTBL.get_version(), &vtable, pool)); + + schemes = VTBL.get_schemes(pool); + + for (; *schemes != NULL; ++schemes) + apr_hash_set(hash, *schemes, APR_HASH_KEY_STRING, &compat_plugin); + + return SVN_NO_ERROR; +} |