diff options
Diffstat (limited to 'subversion/libsvn_ra_serf/update.c')
-rw-r--r-- | subversion/libsvn_ra_serf/update.c | 3639 |
1 files changed, 3639 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c new file mode 100644 index 0000000000000..21ed2dfc9cd9f --- /dev/null +++ b/subversion/libsvn_ra_serf/update.c @@ -0,0 +1,3639 @@ +/* + * update.c : entry point for update RA functions for ra_serf + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_version.h> +#include <apr_want.h> + +#include <apr_uri.h> + +#include <serf.h> + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "svn_dav.h" +#include "svn_xml.h" +#include "svn_delta.h" +#include "svn_path.h" +#include "svn_base64.h" +#include "svn_props.h" + +#include "svn_private_config.h" +#include "private/svn_dep_compat.h" +#include "private/svn_fspath.h" +#include "private/svn_string_private.h" + +#include "ra_serf.h" +#include "../libsvn_ra/ra_loader.h" + + +/* + * This enum represents the current state of our XML parsing for a REPORT. + * + * A little explanation of how the parsing works. Every time we see + * an open-directory tag, we enter the OPEN_DIR state. Likewise, for + * add-directory, open-file, etc. When we see the closing variant of the + * open-directory tag, we'll 'pop' out of that state. + * + * Each state has a pool associated with it that can have temporary + * allocations that will live as long as the tag is opened. Once + * the tag is 'closed', the pool will be reused. + */ +typedef enum report_state_e { + NONE = 0, + INITIAL = 0, + UPDATE_REPORT, + TARGET_REVISION, + OPEN_DIR, + ADD_DIR, + ABSENT_DIR, + OPEN_FILE, + ADD_FILE, + ABSENT_FILE, + PROP, + IGNORE_PROP_NAME, + NEED_PROP_NAME, + TXDELTA +} report_state_e; + + +/* While we process the REPORT response, we will queue up GET and PROPFIND + requests. For a very large checkout, it is very easy to queue requests + faster than they are resolved. Thus, we need to pause the XML processing + (which queues more requests) to avoid queueing too many, with their + attendant memory costs. When the queue count drops low enough, we will + resume XML processing. + + Note that we don't want the count to drop to zero. We have multiple + connections that we want to keep busy. These are also heuristic numbers + since network and parsing behavior (ie. it doesn't pause immediately) + can make the measurements quite imprecise. + + We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and + NUM_ACTIVE_PROPFINDS in the report_context_t structure. */ +#define REQUEST_COUNT_TO_PAUSE 50 +#define REQUEST_COUNT_TO_RESUME 40 + + +/* Forward-declare our report context. */ +typedef struct report_context_t report_context_t; + +/* + * This structure represents the information for a directory. + */ +typedef struct report_dir_t +{ + /* Our parent directory. + * + * This value is NULL when we are the root. + */ + struct report_dir_t *parent_dir; + + apr_pool_t *pool; + + /* Pointer back to our original report context. */ + report_context_t *report_context; + + /* Our name sans any parents. */ + const char *base_name; + + /* the expanded directory name (including all parent names) */ + const char *name; + + /* the canonical url for this directory after updating. (received) */ + const char *url; + + /* The original repos_relpath of this url (from the working copy) + or NULL if the repos_relpath can be calculated from the edit root. */ + const char *repos_relpath; + + /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */ + svn_revnum_t base_rev; + + /* controlling dir baton - this is only created in ensure_dir_opened() */ + void *dir_baton; + apr_pool_t *dir_baton_pool; + + /* How many references to this directory do we still have open? */ + apr_size_t ref_count; + + /* Namespace list allocated out of this ->pool. */ + svn_ra_serf__ns_t *ns_list; + + /* hashtable for all of the properties (shared within a dir) */ + apr_hash_t *props; + + /* hashtable for all to-be-removed properties (shared within a dir) */ + apr_hash_t *removed_props; + + /* The propfind request for our current directory */ + svn_ra_serf__handler_t *propfind_handler; + + /* Has the server told us to fetch the dir props? */ + svn_boolean_t fetch_props; + + /* Have we closed the directory tag (meaning no more additions)? */ + svn_boolean_t tag_closed; + + /* The children of this directory */ + struct report_dir_t *children; + + /* The next sibling of this directory */ + struct report_dir_t *sibling; +} report_dir_t; + +/* + * This structure represents the information for a file. + * + * A directory may have a report_info_t associated with it as well. + * + * This structure is created as we parse the REPORT response and + * once the element is completed, we create a report_fetch_t structure + * to give to serf to retrieve this file. + */ +typedef struct report_info_t +{ + apr_pool_t *pool; + + /* The enclosing directory. + * + * If this structure refers to a directory, the dir it points to will be + * itself. + */ + report_dir_t *dir; + + /* Our name sans any directory info. */ + const char *base_name; + + /* the expanded file name (including all parent directory names) */ + const char *name; + + /* the canonical url for this file. */ + const char *url; + + /* lock token, if we had one to start off with. */ + const char *lock_token; + + /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */ + svn_revnum_t base_rev; + + /* our delta base, if present (NULL if we're adding the file) */ + const char *delta_base; + + /* Path of original item if add with history */ + const char *copyfrom_path; + + /* Revision of original item if add with history */ + svn_revnum_t copyfrom_rev; + + /* The propfind request for our current file (if present) */ + svn_ra_serf__handler_t *propfind_handler; + + /* Has the server told us to fetch the file props? */ + svn_boolean_t fetch_props; + + /* Has the server told us to go fetch - only valid if we had it already */ + svn_boolean_t fetch_file; + + /* The properties for this file */ + apr_hash_t *props; + + /* pool passed to update->add_file, etc. */ + apr_pool_t *editor_pool; + + /* controlling file_baton and textdelta handler */ + void *file_baton; + const char *base_checksum; + const char *final_sha1_checksum; + svn_txdelta_window_handler_t textdelta; + void *textdelta_baton; + svn_stream_t *svndiff_decoder; + svn_stream_t *base64_decoder; + + /* Checksum for close_file */ + const char *final_checksum; + + /* Stream containing file contents already cached in the working + copy (which may be used to avoid a GET request for the same). */ + svn_stream_t *cached_contents; + + /* temporary property for this file which is currently being parsed + * It will eventually be stored in our parent directory's property hash. + */ + const char *prop_ns; + const char *prop_name; + svn_stringbuf_t *prop_value; + const char *prop_encoding; +} report_info_t; + +/* + * This structure represents a single request to GET (fetch) a file with + * its associated Serf session/connection. + */ +typedef struct report_fetch_t { + + /* The handler representing this particular fetch. */ + svn_ra_serf__handler_t *handler; + + /* The session we should use to fetch the file. */ + svn_ra_serf__session_t *sess; + + /* The connection we should use to fetch file. */ + svn_ra_serf__connection_t *conn; + + /* Stores the information for the file we want to fetch. */ + report_info_t *info; + + /* Have we read our response headers yet? */ + svn_boolean_t read_headers; + + /* This flag is set when our response is aborted before we reach the + * end and we decide to requeue this request. + */ + svn_boolean_t aborted_read; + apr_off_t aborted_read_size; + + /* This is the amount of data that we have read so far. */ + apr_off_t read_size; + + /* If we're receiving an svndiff, this will be non-NULL. */ + svn_stream_t *delta_stream; + + /* If we're writing this file to a stream, this will be non-NULL. */ + svn_stream_t *target_stream; + + /* Are we done fetching this file? */ + svn_boolean_t done; + + /* Discard the rest of the content? */ + svn_boolean_t discard; + + svn_ra_serf__list_t **done_list; + svn_ra_serf__list_t done_item; + +} report_fetch_t; + +/* + * The master structure for a REPORT request and response. + */ +struct report_context_t { + apr_pool_t *pool; + + svn_ra_serf__session_t *sess; + svn_ra_serf__connection_t *conn; + + /* Source path and destination path */ + const char *source; + const char *destination; + + /* Our update target. */ + const char *update_target; + + /* What is the target revision that we want for this REPORT? */ + svn_revnum_t target_rev; + + /* Have we been asked to ignore ancestry or textdeltas? */ + svn_boolean_t ignore_ancestry; + svn_boolean_t text_deltas; + + /* Do we want the server to send copyfrom args or not? */ + svn_boolean_t send_copyfrom_args; + + /* Is the server sending everything in one response? */ + svn_boolean_t send_all_mode; + + /* Is the server including properties inline for newly added + files/dirs? */ + svn_boolean_t add_props_included; + + /* Path -> lock token mapping. */ + apr_hash_t *lock_path_tokens; + + /* Path -> const char *repos_relpath mapping */ + apr_hash_t *switched_paths; + + /* Boolean indicating whether "" is switched. + (This indicates that the we are updating a single file) */ + svn_boolean_t root_is_switched; + + /* Our master update editor and baton. */ + const svn_delta_editor_t *update_editor; + void *update_baton; + + /* The file holding request body for the REPORT. + * + * ### todo: It will be better for performance to store small + * request bodies (like 4k) in memory and bigger bodies on disk. + */ + apr_file_t *body_file; + + /* root directory object */ + report_dir_t *root_dir; + + /* number of pending GET requests */ + unsigned int num_active_fetches; + + /* completed fetches (contains report_fetch_t) */ + svn_ra_serf__list_t *done_fetches; + + /* number of pending PROPFIND requests */ + unsigned int num_active_propfinds; + + /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */ + svn_ra_serf__list_t *done_propfinds; + svn_ra_serf__list_t *done_dir_propfinds; + + /* list of outstanding prop changes (contains report_dir_t) */ + svn_ra_serf__list_t *active_dir_propfinds; + + /* list of files that only have prop changes (contains report_info_t) */ + svn_ra_serf__list_t *file_propchanges_only; + + /* The path to the REPORT request */ + const char *path; + + /* Are we done parsing the REPORT response? */ + svn_boolean_t done; + + /* Did we receive all data from the network? */ + svn_boolean_t report_received; + + /* Did we get a complete (non-truncated) report? */ + svn_boolean_t report_completed; + + /* The XML parser context for the REPORT response. */ + svn_ra_serf__xml_parser_t *parser_ctx; + + /* Did we close the root directory? */ + svn_boolean_t closed_root; +}; + + +#ifdef NOT_USED_YET + +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +static const svn_ra_serf__xml_transition_t update_ttable[] = { + { INITIAL, S_, "update-report", UPDATE_REPORT, + FALSE, { NULL }, FALSE }, + + { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION, + FALSE, { "rev", NULL }, TRUE }, + + { UPDATE_REPORT, S_, "open-directory", OPEN_DIR, + FALSE, { "rev", NULL }, TRUE }, + + { OPEN_DIR, S_, "open-directory", OPEN_DIR, + FALSE, { "rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "add-directory", ADD_DIR, + FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + + { ADD_DIR, S_, "add-directory", ADD_DIR, + FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + + { OPEN_DIR, S_, "open-file", OPEN_FILE, + FALSE, { "rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "add-file", ADD_FILE, + FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + + { ADD_DIR, S_, "add-file", ADD_FILE, + FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + + { OPEN_DIR, S_, "delete-entry", OPEN_FILE, + FALSE, { "?rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "absent-directory", ABSENT_DIR, + FALSE, { "name", NULL }, TRUE }, + + { ADD_DIR, S_, "absent-directory", ABSENT_DIR, + FALSE, { "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "absent-file", ABSENT_FILE, + FALSE, { "name", NULL }, TRUE }, + + { ADD_DIR, S_, "absent-file", ABSENT_FILE, + FALSE, { "name", NULL }, TRUE }, + + { 0 } +}; + + + +/* Conforms to svn_ra_serf__xml_opened_t */ +static svn_error_t * +update_opened(svn_ra_serf__xml_estate_t *xes, + void *baton, + int entered_state, + const svn_ra_serf__dav_props_t *tag, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = baton; + + return SVN_NO_ERROR; +} + + + +/* Conforms to svn_ra_serf__xml_closed_t */ +static svn_error_t * +update_closed(svn_ra_serf__xml_estate_t *xes, + void *baton, + int leaving_state, + const svn_string_t *cdata, + apr_hash_t *attrs, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = baton; + + if (leaving_state == TARGET_REVISION) + { + const char *rev = svn_hash_gets(attrs, "rev"); + + SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, + SVN_STR_TO_REV(rev), + ctx->sess->pool)); + } + + return SVN_NO_ERROR; +} + + +/* Conforms to svn_ra_serf__xml_cdata_t */ +static svn_error_t * +update_cdata(svn_ra_serf__xml_estate_t *xes, + void *baton, + int current_state, + const char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = baton; + + return SVN_NO_ERROR; +} + +#endif /* NOT_USED_YET */ + + +/* Returns best connection for fetching files/properties. */ +static svn_ra_serf__connection_t * +get_best_connection(report_context_t *ctx) +{ + svn_ra_serf__connection_t *conn; + int first_conn = 1; + + /* Skip the first connection if the REPORT response hasn't been completely + received yet or if we're being told to limit our connections to + 2 (because this could be an attempt to ensure that we do all our + auxiliary GETs/PROPFINDs on a single connection). + + ### FIXME: This latter requirement (max_connections > 2) is + ### really just a hack to work around the fact that some update + ### editor implementations (such as svnrdump's dump editor) + ### simply can't handle the way ra_serf violates the editor v1 + ### drive ordering requirements. + ### + ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116. + */ + if (ctx->report_received && (ctx->sess->max_connections > 2)) + first_conn = 0; + + /* Currently, we just cycle connections. In the future we could + store the number of pending requests on each connection, or + perform other heuristics, to achieve better connection usage. + (As an optimization, if there's only one available auxiliary + connection to use, don't bother doing all the cur_conn math -- + just return that one connection.) */ + if (ctx->sess->num_conns - first_conn == 1) + { + conn = ctx->sess->conns[first_conn]; + } + else + { + conn = ctx->sess->conns[ctx->sess->cur_conn]; + ctx->sess->cur_conn++; + if (ctx->sess->cur_conn >= ctx->sess->num_conns) + ctx->sess->cur_conn = first_conn; + } + return conn; +} + + +/** Report state management helper **/ + +static report_info_t * +push_state(svn_ra_serf__xml_parser_t *parser, + report_context_t *ctx, + report_state_e state) +{ + report_info_t *info; + apr_pool_t *info_parent_pool; + + svn_ra_serf__xml_push_state(parser, state); + + info = parser->state->private; + + /* Our private pool needs to be disjoint from the state pool. */ + if (!info) + { + info_parent_pool = ctx->pool; + } + else + { + info_parent_pool = info->pool; + } + + if (state == OPEN_DIR || state == ADD_DIR) + { + report_info_t *new_info; + + new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); + new_info->pool = svn_pool_create(info_parent_pool); + new_info->lock_token = NULL; + new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); + + new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir)); + new_info->dir->pool = new_info->pool; + + /* Create the root property tree. */ + new_info->dir->props = apr_hash_make(new_info->pool); + new_info->props = new_info->dir->props; + new_info->dir->removed_props = apr_hash_make(new_info->pool); + + new_info->dir->report_context = ctx; + + if (info) + { + info->dir->ref_count++; + + new_info->dir->parent_dir = info->dir; + + /* Point our ns_list at our parents to try to reuse it. */ + new_info->dir->ns_list = info->dir->ns_list; + + /* Add ourselves to our parent's list */ + new_info->dir->sibling = info->dir->children; + info->dir->children = new_info->dir; + } + else + { + /* Allow us to be found later. */ + ctx->root_dir = new_info->dir; + } + + parser->state->private = new_info; + } + else if (state == OPEN_FILE || state == ADD_FILE) + { + report_info_t *new_info; + + new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); + new_info->pool = svn_pool_create(info_parent_pool); + new_info->file_baton = NULL; + new_info->lock_token = NULL; + new_info->fetch_file = FALSE; + new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); + + /* Point at our parent's directory state. */ + new_info->dir = info->dir; + info->dir->ref_count++; + + new_info->props = apr_hash_make(new_info->pool); + + parser->state->private = new_info; + } + + return parser->state->private; +} + + +/** Wrappers around our various property walkers **/ + +static svn_error_t * +set_file_props(void *baton, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + report_info_t *info = baton; + const svn_delta_editor_t *editor = info->dir->report_context->update_editor; + const char *prop_name; + + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); + if (prop_name != NULL) + return svn_error_trace(editor->change_file_prop(info->file_baton, + prop_name, + val, + scratch_pool)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +set_dir_props(void *baton, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + report_dir_t *dir = baton; + const svn_delta_editor_t *editor = dir->report_context->update_editor; + const char *prop_name; + + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); + if (prop_name != NULL) + return svn_error_trace(editor->change_dir_prop(dir->dir_baton, + prop_name, + val, + scratch_pool)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +remove_file_props(void *baton, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + report_info_t *info = baton; + const svn_delta_editor_t *editor = info->dir->report_context->update_editor; + const char *prop_name; + + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); + if (prop_name != NULL) + return svn_error_trace(editor->change_file_prop(info->file_baton, + prop_name, + NULL, + scratch_pool)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +remove_dir_props(void *baton, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + report_dir_t *dir = baton; + const svn_delta_editor_t *editor = dir->report_context->update_editor; + const char *prop_name; + + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); + if (prop_name != NULL) + return svn_error_trace(editor->change_dir_prop(dir->dir_baton, + prop_name, + NULL, + scratch_pool)); + return SVN_NO_ERROR; +} + + +/** Helpers to open and close directories */ + +static svn_error_t* +ensure_dir_opened(report_dir_t *dir) +{ + report_context_t *ctx = dir->report_context; + + /* if we're already open, return now */ + if (dir->dir_baton) + { + return SVN_NO_ERROR; + } + + if (dir->base_name[0] == '\0') + { + dir->dir_baton_pool = svn_pool_create(dir->pool); + + if (ctx->destination + && ctx->sess->wc_callbacks->invalidate_wc_props) + { + SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props( + ctx->sess->wc_callback_baton, + ctx->update_target, + SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool)); + } + + SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev, + dir->dir_baton_pool, + &dir->dir_baton)); + } + else + { + SVN_ERR(ensure_dir_opened(dir->parent_dir)); + + dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool); + + if (SVN_IS_VALID_REVNUM(dir->base_rev)) + { + SVN_ERR(ctx->update_editor->open_directory(dir->name, + dir->parent_dir->dir_baton, + dir->base_rev, + dir->dir_baton_pool, + &dir->dir_baton)); + } + else + { + SVN_ERR(ctx->update_editor->add_directory(dir->name, + dir->parent_dir->dir_baton, + NULL, SVN_INVALID_REVNUM, + dir->dir_baton_pool, + &dir->dir_baton)); + } + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_dir(report_dir_t *dir) +{ + report_dir_t *prev; + report_dir_t *sibling; + + /* ### is there a better pool... this is tossed at end-of-func */ + apr_pool_t *scratch_pool = dir->dir_baton_pool; + + SVN_ERR_ASSERT(! dir->ref_count); + + SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name, + dir->base_rev, + set_dir_props, dir, + scratch_pool)); + + SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name, + dir->base_rev, remove_dir_props, dir, + scratch_pool)); + + if (dir->fetch_props) + { + SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url, + dir->report_context->target_rev, + set_dir_props, dir, + scratch_pool)); + } + + SVN_ERR(dir->report_context->update_editor->close_directory( + dir->dir_baton, scratch_pool)); + + /* remove us from our parent's children list */ + if (dir->parent_dir) + { + prev = NULL; + sibling = dir->parent_dir->children; + + while (sibling != dir) + { + prev = sibling; + sibling = sibling->sibling; + if (!sibling) + SVN_ERR_MALFUNCTION(); + } + + if (!prev) + { + dir->parent_dir->children = dir->sibling; + } + else + { + prev->sibling = dir->sibling; + } + } + + svn_pool_destroy(dir->dir_baton_pool); + svn_pool_destroy(dir->pool); + + return SVN_NO_ERROR; +} + +static svn_error_t *close_all_dirs(report_dir_t *dir) +{ + while (dir->children) + { + SVN_ERR(close_all_dirs(dir->children)); + dir->ref_count--; + } + + SVN_ERR_ASSERT(! dir->ref_count); + + SVN_ERR(ensure_dir_opened(dir)); + + return close_dir(dir); +} + + +/** Routines called when we are fetching a file */ + +/* This function works around a bug in some older versions of + * mod_dav_svn in that it will not send remove-prop in the update + * report when a lock property disappears when send-all is false. + * + * Therefore, we'll try to look at our properties and see if there's + * an active lock. If not, then we'll assume there isn't a lock + * anymore. + */ +static void +check_lock(report_info_t *info) +{ + const char *lock_val; + + lock_val = svn_ra_serf__get_ver_prop(info->props, info->url, + info->dir->report_context->target_rev, + "DAV:", "lockdiscovery"); + + if (lock_val) + { + char *new_lock; + new_lock = apr_pstrdup(info->editor_pool, lock_val); + apr_collapse_spaces(new_lock, new_lock); + lock_val = new_lock; + } + + if (!lock_val || lock_val[0] == '\0') + { + svn_string_t *str; + + str = svn_string_ncreate("", 1, info->editor_pool); + + svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name, + info->base_rev, "DAV:", "lock-token", + str, info->dir->pool); + } +} + +static svn_error_t * +headers_fetch(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool) +{ + report_fetch_t *fetch_ctx = baton; + + /* note that we have old VC URL */ + if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) && + fetch_ctx->info->delta_base) + { + serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER, + fetch_ctx->info->delta_base); + serf_bucket_headers_setn(headers, "Accept-Encoding", + "svndiff1;q=0.9,svndiff;q=0.8"); + } + else if (fetch_ctx->sess->using_compression) + { + serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +cancel_fetch(serf_request_t *request, + serf_bucket_t *response, + int status_code, + void *baton) +{ + report_fetch_t *fetch_ctx = baton; + + /* Uh-oh. Our connection died on us. + * + * The core ra_serf layer will requeue our request - we just need to note + * that we got cut off in the middle of our song. + */ + if (!response) + { + /* If we already started the fetch and opened the file handle, we need + * to hold subsequent read() ops until we get back to where we were + * before the close and we can then resume the textdelta() calls. + */ + if (fetch_ctx->read_headers) + { + if (!fetch_ctx->aborted_read && fetch_ctx->read_size) + { + fetch_ctx->aborted_read = TRUE; + fetch_ctx->aborted_read_size = fetch_ctx->read_size; + } + fetch_ctx->read_size = 0; + } + + return SVN_NO_ERROR; + } + + /* We have no idea what went wrong. */ + SVN_ERR_MALFUNCTION(); +} + +static svn_error_t * +error_fetch(serf_request_t *request, + report_fetch_t *fetch_ctx, + svn_error_t *err) +{ + fetch_ctx->done = TRUE; + + fetch_ctx->done_item.data = fetch_ctx; + fetch_ctx->done_item.next = *fetch_ctx->done_list; + *fetch_ctx->done_list = &fetch_ctx->done_item; + + /* Discard the rest of this request + (This makes sure it doesn't error when the request is aborted later) */ + serf_request_set_handler(request, + svn_ra_serf__response_discard_handler, NULL); + + /* Some errors would be handled by serf; make sure they really make + the update fail by wrapping it in a different error. */ + if (!SERF_BUCKET_READ_ERROR(err->apr_err)) + return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); + + return err; +} + +/* Wield the editor referenced by INFO to open (or add) the file + file also associated with INFO, setting properties on the file and + calling the editor's apply_textdelta() function on it if necessary + (or if FORCE_APPLY_TEXTDELTA is set). + + Callers will probably want to also see the function that serves + the opposite purpose of this one, close_updated_file(). */ +static svn_error_t * +open_updated_file(report_info_t *info, + svn_boolean_t force_apply_textdelta, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = info->dir->report_context; + const svn_delta_editor_t *update_editor = ctx->update_editor; + + /* Ensure our parent is open. */ + SVN_ERR(ensure_dir_opened(info->dir)); + info->editor_pool = svn_pool_create(info->dir->dir_baton_pool); + + /* Expand our full name now if we haven't done so yet. */ + if (!info->name) + { + info->name = svn_relpath_join(info->dir->name, info->base_name, + info->editor_pool); + } + + /* Open (or add) the file. */ + if (SVN_IS_VALID_REVNUM(info->base_rev)) + { + SVN_ERR(update_editor->open_file(info->name, + info->dir->dir_baton, + info->base_rev, + info->editor_pool, + &info->file_baton)); + } + else + { + SVN_ERR(update_editor->add_file(info->name, + info->dir->dir_baton, + info->copyfrom_path, + info->copyfrom_rev, + info->editor_pool, + &info->file_baton)); + } + + /* Check for lock information. */ + if (info->lock_token) + check_lock(info); + + /* Get (maybe) a textdelta window handler for transmitting file + content changes. */ + if (info->fetch_file || force_apply_textdelta) + { + SVN_ERR(update_editor->apply_textdelta(info->file_baton, + info->base_checksum, + info->editor_pool, + &info->textdelta, + &info->textdelta_baton)); + } + + return SVN_NO_ERROR; +} + +/* Close the file associated with INFO->file_baton, and cleanup other + bits of that structure managed by open_updated_file(). */ +static svn_error_t * +close_updated_file(report_info_t *info, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = info->dir->report_context; + + /* Set all of the properties we received */ + SVN_ERR(svn_ra_serf__walk_all_props(info->props, + info->base_name, + info->base_rev, + set_file_props, info, + scratch_pool)); + SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props, + info->base_name, + info->base_rev, + remove_file_props, info, + scratch_pool)); + if (info->fetch_props) + { + SVN_ERR(svn_ra_serf__walk_all_props(info->props, + info->url, + ctx->target_rev, + set_file_props, info, + scratch_pool)); + } + + /* Close the file via the editor. */ + SVN_ERR(info->dir->report_context->update_editor->close_file( + info->file_baton, info->final_checksum, scratch_pool)); + + /* We're done with our editor pool. */ + svn_pool_destroy(info->editor_pool); + + return SVN_NO_ERROR; +} + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_fetch(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + const char *data; + apr_size_t len; + apr_status_t status; + report_fetch_t *fetch_ctx = handler_baton; + svn_error_t *err; + + /* ### new field. make sure we didn't miss some initialization. */ + SVN_ERR_ASSERT(fetch_ctx->handler != NULL); + + if (!fetch_ctx->read_headers) + { + serf_bucket_t *hdrs; + const char *val; + report_info_t *info; + + hdrs = serf_bucket_response_get_headers(response); + val = serf_bucket_headers_get(hdrs, "Content-Type"); + info = fetch_ctx->info; + + if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0) + { + fetch_ctx->delta_stream = + svn_txdelta_parse_svndiff(info->textdelta, + info->textdelta_baton, + TRUE, info->editor_pool); + + /* Validate the delta base claimed by the server matches + what we asked for! */ + val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER); + if (val && (strcmp(val, info->delta_base) != 0)) + { + err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("GET request returned unexpected " + "delta base: %s"), val); + return error_fetch(request, fetch_ctx, err); + } + } + else + { + fetch_ctx->delta_stream = NULL; + } + + fetch_ctx->read_headers = TRUE; + } + + /* If the error code wasn't 200, something went wrong. Don't use the returned + data as its probably an error message. Just bail out instead. */ + if (fetch_ctx->handler->sline.code != 200) + { + err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("GET request failed: %d %s"), + fetch_ctx->handler->sline.code, + fetch_ctx->handler->sline.reason); + return error_fetch(request, fetch_ctx, err); + } + + while (1) + { + svn_txdelta_window_t delta_window = { 0 }; + svn_txdelta_op_t delta_op; + svn_string_t window_data; + + status = serf_bucket_read(response, 8000, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + + fetch_ctx->read_size += len; + + if (fetch_ctx->aborted_read) + { + apr_off_t skip; + /* We haven't caught up to where we were before. */ + if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) + { + /* Eek. What did the file shrink or something? */ + if (APR_STATUS_IS_EOF(status)) + { + SVN_ERR_MALFUNCTION(); + } + + /* Skip on to the next iteration of this loop. */ + if (APR_STATUS_IS_EAGAIN(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + continue; + } + + /* Woo-hoo. We're back. */ + fetch_ctx->aborted_read = FALSE; + + /* Update data and len to just provide the new data. */ + skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); + data += skip; + len -= skip; + } + + if (fetch_ctx->delta_stream) + { + err = svn_stream_write(fetch_ctx->delta_stream, data, &len); + if (err) + { + return error_fetch(request, fetch_ctx, err); + } + } + /* otherwise, manually construct the text delta window. */ + else if (len) + { + window_data.data = data; + window_data.len = len; + + delta_op.action_code = svn_txdelta_new; + delta_op.offset = 0; + delta_op.length = len; + + delta_window.tview_len = len; + delta_window.num_ops = 1; + delta_window.ops = &delta_op; + delta_window.new_data = &window_data; + + /* write to the file located in the info. */ + err = fetch_ctx->info->textdelta(&delta_window, + fetch_ctx->info->textdelta_baton); + if (err) + { + return error_fetch(request, fetch_ctx, err); + } + } + + if (APR_STATUS_IS_EOF(status)) + { + report_info_t *info = fetch_ctx->info; + + if (fetch_ctx->delta_stream) + err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream)); + else + err = svn_error_trace(info->textdelta(NULL, + info->textdelta_baton)); + if (err) + { + return error_fetch(request, fetch_ctx, err); + } + + err = close_updated_file(info, info->pool); + if (err) + { + return svn_error_trace(error_fetch(request, fetch_ctx, err)); + } + + fetch_ctx->done = TRUE; + + fetch_ctx->done_item.data = fetch_ctx; + fetch_ctx->done_item.next = *fetch_ctx->done_list; + *fetch_ctx->done_list = &fetch_ctx->done_item; + + /* We're done with our pool. */ + svn_pool_destroy(info->pool); + + if (status) + return svn_ra_serf__wrap_err(status, NULL); + } + if (APR_STATUS_IS_EAGAIN(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + } + /* not reached */ +} + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_stream(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + report_fetch_t *fetch_ctx = handler_baton; + svn_error_t *err; + apr_status_t status; + + /* ### new field. make sure we didn't miss some initialization. */ + SVN_ERR_ASSERT(fetch_ctx->handler != NULL); + + err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline.code, + fetch_ctx->info->name, + fetch_ctx->handler->location); + if (err) + { + fetch_ctx->handler->done = TRUE; + + err = svn_error_compose_create( + err, + svn_ra_serf__handle_discard_body(request, response, NULL, pool)); + + return svn_error_trace(err); + } + + while (1) + { + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 8000, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + + fetch_ctx->read_size += len; + + if (fetch_ctx->aborted_read) + { + /* We haven't caught up to where we were before. */ + if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) + { + /* Eek. What did the file shrink or something? */ + if (APR_STATUS_IS_EOF(status)) + { + SVN_ERR_MALFUNCTION(); + } + + /* Skip on to the next iteration of this loop. */ + if (APR_STATUS_IS_EAGAIN(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + continue; + } + + /* Woo-hoo. We're back. */ + fetch_ctx->aborted_read = FALSE; + + /* Increment data and len by the difference. */ + data += fetch_ctx->read_size - fetch_ctx->aborted_read_size; + len += fetch_ctx->read_size - fetch_ctx->aborted_read_size; + } + + if (len) + { + apr_size_t written_len; + + written_len = len; + + SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data, + &written_len)); + } + + if (APR_STATUS_IS_EOF(status)) + { + fetch_ctx->done = TRUE; + } + + if (status) + { + return svn_ra_serf__wrap_err(status, NULL); + } + } + /* not reached */ +} + +/* Close the directory represented by DIR -- and any suitable parents + thereof -- if we are able to do so. This is the case whenever: + + - there are no remaining open items within the directory, and + - the directory's XML close tag has been processed (so we know + there are no more children to worry about in the future), and + - either: + - we aren't fetching properties for this directory, or + - we've already finished fetching those properties. +*/ +static svn_error_t * +maybe_close_dir_chain(report_dir_t *dir) +{ + report_dir_t *cur_dir = dir; + + SVN_ERR(ensure_dir_opened(cur_dir)); + + while (cur_dir + && !cur_dir->ref_count + && cur_dir->tag_closed + && (!cur_dir->fetch_props || cur_dir->propfind_handler->done)) + { + report_dir_t *parent = cur_dir->parent_dir; + report_context_t *report_context = cur_dir->report_context; + svn_boolean_t propfind_in_done_list = FALSE; + svn_ra_serf__list_t *done_list; + + /* Make sure there are no references to this dir in the + active_dir_propfinds list. If there are, don't close the + directory -- which would delete the pool from which the + relevant active_dir_propfinds list item is allocated -- and + of course don't crawl upward to check the parents for + a closure opportunity, either. */ + done_list = report_context->active_dir_propfinds; + while (done_list) + { + if (done_list->data == cur_dir) + { + propfind_in_done_list = TRUE; + break; + } + done_list = done_list->next; + } + if (propfind_in_done_list) + break; + + SVN_ERR(close_dir(cur_dir)); + if (parent) + { + parent->ref_count--; + } + else + { + report_context->closed_root = TRUE; + } + cur_dir = parent; + } + + return SVN_NO_ERROR; +} + +/* Open the file associated with INFO for editing, pass along any + propchanges we've recorded for it, and then close the file. */ +static svn_error_t * +handle_propchange_only(report_info_t *info, + apr_pool_t *scratch_pool) +{ + SVN_ERR(open_updated_file(info, FALSE, scratch_pool)); + SVN_ERR(close_updated_file(info, scratch_pool)); + + /* We're done with our pool. */ + svn_pool_destroy(info->pool); + + info->dir->ref_count--; + + /* See if the parent directory of this file (and perhaps even + parents of that) can be closed now. */ + SVN_ERR(maybe_close_dir_chain(info->dir)); + + return SVN_NO_ERROR; +} + +/* "Fetch" a file whose contents were made available via the + get_wc_contents() callback (as opposed to requiring a GET to the + server), and feed the information through the associated update + editor. In editor-speak, this will add/open the file, transmit any + property changes, handle the contents, and then close the file. */ +static svn_error_t * +handle_local_content(report_info_t *info, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta, + info->textdelta_baton, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(info->cached_contents)); + info->cached_contents = NULL; + SVN_ERR(close_updated_file(info, scratch_pool)); + + /* We're done with our pool. */ + svn_pool_destroy(info->pool); + + info->dir->ref_count--; + + /* See if the parent directory of this fetched item (and + perhaps even parents of that) can be closed now. */ + SVN_ERR(maybe_close_dir_chain(info->dir)); + + return SVN_NO_ERROR; +} + +/* --------------------------------------------------------- */ + +static svn_error_t * +fetch_file(report_context_t *ctx, report_info_t *info) +{ + svn_ra_serf__connection_t *conn; + svn_ra_serf__handler_t *handler; + + /* What connection should we go on? */ + conn = get_best_connection(ctx); + + /* If needed, create the PROPFIND to retrieve the file's properties. */ + info->propfind_handler = NULL; + if (info->fetch_props) + { + SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props, + ctx->sess, conn, info->url, + ctx->target_rev, "0", all_props, + &ctx->done_propfinds, + info->dir->pool)); + SVN_ERR_ASSERT(info->propfind_handler); + + /* Create a serf request for the PROPFIND. */ + svn_ra_serf__request_create(info->propfind_handler); + + ctx->num_active_propfinds++; + } + + /* If we've been asked to fetch the file or it's an add, do so. + * Otherwise, handle the case where only the properties changed. + */ + if (info->fetch_file && ctx->text_deltas) + { + svn_stream_t *contents = NULL; + + /* Open the file for editing. */ + SVN_ERR(open_updated_file(info, FALSE, info->pool)); + + if (info->textdelta == svn_delta_noop_window_handler) + { + /* There is nobody looking for an actual stream. + + Just report an empty stream instead of fetching + to be ingored data */ + info->cached_contents = svn_stream_empty(info->pool); + } + else if (ctx->sess->wc_callbacks->get_wc_contents + && info->final_sha1_checksum) + { + svn_error_t *err = NULL; + svn_checksum_t *checksum = NULL; + + /* Parse the optional SHA1 checksum (1.7+) */ + err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, + info->final_sha1_checksum, + info->pool); + + /* Okay so far? Let's try to get a stream on some readily + available matching content. */ + if (!err && checksum) + { + err = ctx->sess->wc_callbacks->get_wc_contents( + ctx->sess->wc_callback_baton, &contents, + checksum, info->pool); + + if (! err) + info->cached_contents = contents; + } + + if (err) + { + /* Meh. Maybe we'll care one day why we're in an + errorful state, but this codepath is optional. */ + svn_error_clear(err); + } + } + + /* If the working copy can provide cached contents for this + file, we don't have to fetch them from the server. */ + if (info->cached_contents) + { + /* If we'll be doing a PROPFIND for this file... */ + if (info->propfind_handler) + { + /* ... then we'll just leave ourselves a little "todo" + about that fact (and we'll deal with the file content + stuff later, after we've handled that PROPFIND + response. */ + svn_ra_serf__list_t *list_item; + + list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); + list_item->data = info; + list_item->next = ctx->file_propchanges_only; + ctx->file_propchanges_only = list_item; + } + else + { + /* Otherwise, if we've no PROPFIND to do, we might as + well take care of those locally accessible file + contents now. */ + SVN_ERR(handle_local_content(info, info->pool)); + } + } + else + { + /* Otherwise, we use a GET request for the file's contents. */ + report_fetch_t *fetch_ctx; + + fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx)); + fetch_ctx->info = info; + fetch_ctx->done_list = &ctx->done_fetches; + fetch_ctx->sess = ctx->sess; + fetch_ctx->conn = conn; + + handler = apr_pcalloc(info->dir->pool, sizeof(*handler)); + + handler->handler_pool = info->dir->pool; + handler->method = "GET"; + handler->path = fetch_ctx->info->url; + + handler->conn = conn; + handler->session = ctx->sess; + + handler->custom_accept_encoding = TRUE; + handler->header_delegate = headers_fetch; + handler->header_delegate_baton = fetch_ctx; + + handler->response_handler = handle_fetch; + handler->response_baton = fetch_ctx; + + handler->response_error = cancel_fetch; + handler->response_error_baton = fetch_ctx; + + fetch_ctx->handler = handler; + + svn_ra_serf__request_create(handler); + + ctx->num_active_fetches++; + } + } + else if (info->propfind_handler) + { + svn_ra_serf__list_t *list_item; + + list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); + list_item->data = info; + list_item->next = ctx->file_propchanges_only; + ctx->file_propchanges_only = list_item; + } + else + { + /* No propfind or GET request. Just handle the prop changes now. */ + SVN_ERR(handle_propchange_only(info, info->pool)); + } + + if (ctx->num_active_fetches + ctx->num_active_propfinds + > REQUEST_COUNT_TO_PAUSE) + ctx->parser_ctx->paused = TRUE; + + return SVN_NO_ERROR; +} + + +/** XML callbacks for our update-report response parsing */ + +static svn_error_t * +start_report(svn_ra_serf__xml_parser_t *parser, + svn_ra_serf__dav_props_t name, + const char **attrs, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = parser->user_data; + report_state_e state; + + state = parser->state->current_state; + + if (state == NONE && strcmp(name.name, "update-report") == 0) + { + const char *val; + + val = svn_xml_get_attr_value("inline-props", attrs); + if (val && (strcmp(val, "true") == 0)) + ctx->add_props_included = TRUE; + + val = svn_xml_get_attr_value("send-all", attrs); + if (val && (strcmp(val, "true") == 0)) + { + ctx->send_all_mode = TRUE; + + /* All properties are included in send-all mode. */ + ctx->add_props_included = TRUE; + } + } + else if (state == NONE && strcmp(name.name, "target-revision") == 0) + { + const char *rev; + + rev = svn_xml_get_attr_value("rev", attrs); + + if (!rev) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing revision attr in target-revision element")); + } + + SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, + SVN_STR_TO_REV(rev), + ctx->sess->pool)); + } + else if (state == NONE && strcmp(name.name, "open-directory") == 0) + { + const char *rev; + report_info_t *info; + + rev = svn_xml_get_attr_value("rev", attrs); + + if (!rev) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing revision attr in open-directory element")); + } + + info = push_state(parser, ctx, OPEN_DIR); + + info->base_rev = SVN_STR_TO_REV(rev); + info->dir->base_rev = info->base_rev; + info->fetch_props = TRUE; + + info->dir->base_name = ""; + info->dir->name = ""; + + info->base_name = info->dir->base_name; + info->name = info->dir->name; + + info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, ""); + + if (!info->dir->repos_relpath) + SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath, + ctx->sess->session_url.path, + ctx->sess, ctx->conn, + info->dir->pool)); + } + else if (state == NONE) + { + /* do nothing as we haven't seen our valid start tag yet. */ + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "open-directory") == 0) + { + const char *rev, *dirname; + report_dir_t *dir; + report_info_t *info; + + rev = svn_xml_get_attr_value("rev", attrs); + + if (!rev) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing revision attr in open-directory element")); + } + + dirname = svn_xml_get_attr_value("name", attrs); + + if (!dirname) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in open-directory element")); + } + + info = push_state(parser, ctx, OPEN_DIR); + + dir = info->dir; + + info->base_rev = SVN_STR_TO_REV(rev); + dir->base_rev = info->base_rev; + + info->fetch_props = FALSE; + + dir->base_name = apr_pstrdup(dir->pool, dirname); + info->base_name = dir->base_name; + + /* Expand our name. */ + dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, + dir->pool); + info->name = dir->name; + + dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name); + + if (!dir->repos_relpath) + dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, + dir->base_name, dir->pool); + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "add-directory") == 0) + { + const char *dir_name, *cf, *cr; + report_dir_t *dir; + report_info_t *info; + + dir_name = svn_xml_get_attr_value("name", attrs); + if (!dir_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in add-directory element")); + } + cf = svn_xml_get_attr_value("copyfrom-path", attrs); + cr = svn_xml_get_attr_value("copyfrom-rev", attrs); + + info = push_state(parser, ctx, ADD_DIR); + + dir = info->dir; + + dir->base_name = apr_pstrdup(dir->pool, dir_name); + info->base_name = dir->base_name; + + /* Expand our name. */ + dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, + dir->pool); + info->name = dir->name; + + info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; + info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; + + /* Mark that we don't have a base. */ + info->base_rev = SVN_INVALID_REVNUM; + dir->base_rev = info->base_rev; + + /* If the server isn't included properties for added items, + we'll need to fetch them ourselves. */ + if (! ctx->add_props_included) + dir->fetch_props = TRUE; + + dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, + dir->base_name, dir->pool); + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "open-file") == 0) + { + const char *file_name, *rev; + report_info_t *info; + + file_name = svn_xml_get_attr_value("name", attrs); + + if (!file_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in open-file element")); + } + + rev = svn_xml_get_attr_value("rev", attrs); + + if (!rev) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing revision attr in open-file element")); + } + + info = push_state(parser, ctx, OPEN_FILE); + + info->base_rev = SVN_STR_TO_REV(rev); + info->fetch_props = FALSE; + + info->base_name = apr_pstrdup(info->pool, file_name); + info->name = NULL; + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "add-file") == 0) + { + const char *file_name, *cf, *cr; + report_info_t *info; + + file_name = svn_xml_get_attr_value("name", attrs); + cf = svn_xml_get_attr_value("copyfrom-path", attrs); + cr = svn_xml_get_attr_value("copyfrom-rev", attrs); + + if (!file_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in add-file element")); + } + + info = push_state(parser, ctx, ADD_FILE); + + info->base_rev = SVN_INVALID_REVNUM; + + /* If the server isn't in "send-all" mode, we should expect to + fetch contents for added files. */ + if (! ctx->send_all_mode) + info->fetch_file = TRUE; + + /* If the server isn't included properties for added items, + we'll need to fetch them ourselves. */ + if (! ctx->add_props_included) + info->fetch_props = TRUE; + + info->base_name = apr_pstrdup(info->pool, file_name); + info->name = NULL; + + info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; + info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; + + info->final_sha1_checksum = + svn_xml_get_attr_value("sha1-checksum", attrs); + if (info->final_sha1_checksum) + info->final_sha1_checksum = apr_pstrdup(info->pool, + info->final_sha1_checksum); + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "delete-entry") == 0) + { + const char *file_name; + const char *rev_str; + report_info_t *info; + apr_pool_t *tmppool; + const char *full_path; + svn_revnum_t delete_rev = SVN_INVALID_REVNUM; + + file_name = svn_xml_get_attr_value("name", attrs); + + if (!file_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in delete-entry element")); + } + + rev_str = svn_xml_get_attr_value("rev", attrs); + if (rev_str) /* Not available on older repositories! */ + delete_rev = SVN_STR_TO_REV(rev_str); + + info = parser->state->private; + + SVN_ERR(ensure_dir_opened(info->dir)); + + tmppool = svn_pool_create(info->dir->dir_baton_pool); + + full_path = svn_relpath_join(info->dir->name, file_name, tmppool); + + SVN_ERR(ctx->update_editor->delete_entry(full_path, + delete_rev, + info->dir->dir_baton, + tmppool)); + + svn_pool_destroy(tmppool); + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "absent-directory") == 0) + { + const char *file_name; + report_info_t *info; + + file_name = svn_xml_get_attr_value("name", attrs); + + if (!file_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in absent-directory element")); + } + + info = parser->state->private; + + SVN_ERR(ensure_dir_opened(info->dir)); + + SVN_ERR(ctx->update_editor->absent_directory( + svn_relpath_join(info->name, file_name, + info->dir->pool), + info->dir->dir_baton, + info->dir->pool)); + } + else if ((state == OPEN_DIR || state == ADD_DIR) && + strcmp(name.name, "absent-file") == 0) + { + const char *file_name; + report_info_t *info; + + file_name = svn_xml_get_attr_value("name", attrs); + + if (!file_name) + { + return svn_error_create( + SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in absent-file element")); + } + + info = parser->state->private; + + SVN_ERR(ensure_dir_opened(info->dir)); + + SVN_ERR(ctx->update_editor->absent_file( + svn_relpath_join(info->name, file_name, + info->dir->pool), + info->dir->dir_baton, + info->dir->pool)); + } + else if (state == OPEN_DIR || state == ADD_DIR) + { + report_info_t *info; + + if (strcmp(name.name, "checked-in") == 0) + { + info = push_state(parser, ctx, IGNORE_PROP_NAME); + info->prop_ns = name.namespace; + info->prop_name = apr_pstrdup(parser->state->pool, name.name); + info->prop_encoding = NULL; + svn_stringbuf_setempty(info->prop_value); + } + else if (strcmp(name.name, "set-prop") == 0 || + strcmp(name.name, "remove-prop") == 0) + { + const char *full_prop_name; + const char *colon; + + info = push_state(parser, ctx, PROP); + + full_prop_name = svn_xml_get_attr_value("name", attrs); + if (!full_prop_name) + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in %s element"), + name.name); + } + + colon = strchr(full_prop_name, ':'); + + if (colon) + colon++; + else + colon = full_prop_name; + + info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, + colon - full_prop_name); + info->prop_name = apr_pstrdup(parser->state->pool, colon); + info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); + svn_stringbuf_setempty(info->prop_value); + } + else if (strcmp(name.name, "prop") == 0) + { + /* need to fetch it. */ + push_state(parser, ctx, NEED_PROP_NAME); + } + else if (strcmp(name.name, "fetch-props") == 0) + { + info = parser->state->private; + + info->dir->fetch_props = TRUE; + } + else + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Unknown tag '%s' while at state %d"), + name.name, state); + } + + } + else if (state == OPEN_FILE || state == ADD_FILE) + { + report_info_t *info; + + if (strcmp(name.name, "checked-in") == 0) + { + info = push_state(parser, ctx, IGNORE_PROP_NAME); + info->prop_ns = name.namespace; + info->prop_name = apr_pstrdup(parser->state->pool, name.name); + info->prop_encoding = NULL; + svn_stringbuf_setempty(info->prop_value); + } + else if (strcmp(name.name, "prop") == 0) + { + /* need to fetch it. */ + push_state(parser, ctx, NEED_PROP_NAME); + } + else if (strcmp(name.name, "fetch-props") == 0) + { + info = parser->state->private; + + info->fetch_props = TRUE; + } + else if (strcmp(name.name, "fetch-file") == 0) + { + info = parser->state->private; + info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs); + + if (info->base_checksum) + info->base_checksum = apr_pstrdup(info->pool, info->base_checksum); + + info->final_sha1_checksum = + svn_xml_get_attr_value("sha1-checksum", attrs); + if (info->final_sha1_checksum) + info->final_sha1_checksum = apr_pstrdup(info->pool, + info->final_sha1_checksum); + + info->fetch_file = TRUE; + } + else if (strcmp(name.name, "set-prop") == 0 || + strcmp(name.name, "remove-prop") == 0) + { + const char *full_prop_name; + const char *colon; + + info = push_state(parser, ctx, PROP); + + full_prop_name = svn_xml_get_attr_value("name", attrs); + if (!full_prop_name) + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing name attr in %s element"), + name.name); + } + colon = strchr(full_prop_name, ':'); + + if (colon) + colon++; + else + colon = full_prop_name; + + info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, + colon - full_prop_name); + info->prop_name = apr_pstrdup(parser->state->pool, colon); + info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); + svn_stringbuf_setempty(info->prop_value); + } + else if (strcmp(name.name, "txdelta") == 0) + { + /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in + addition to <fetch-file>s and such) when *not* in + "send-all" mode. As a client, we're smart enough to know + that's wrong, so we'll just ignore these tags. */ + if (ctx->send_all_mode) + { + const svn_delta_editor_t *update_editor = ctx->update_editor; + + info = push_state(parser, ctx, TXDELTA); + + if (! info->file_baton) + { + SVN_ERR(open_updated_file(info, FALSE, info->pool)); + } + + info->base_checksum = svn_xml_get_attr_value("base-checksum", + attrs); + SVN_ERR(update_editor->apply_textdelta(info->file_baton, + info->base_checksum, + info->editor_pool, + &info->textdelta, + &info->textdelta_baton)); + info->svndiff_decoder = svn_txdelta_parse_svndiff( + info->textdelta, + info->textdelta_baton, + TRUE, info->pool); + info->base64_decoder = svn_base64_decode(info->svndiff_decoder, + info->pool); + } + } + else + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Unknown tag '%s' while at state %d"), + name.name, state); + } + } + else if (state == IGNORE_PROP_NAME) + { + report_info_t *info = push_state(parser, ctx, PROP); + info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); + } + else if (state == NEED_PROP_NAME) + { + report_info_t *info; + + info = push_state(parser, ctx, PROP); + + info->prop_ns = name.namespace; + info->prop_name = apr_pstrdup(parser->state->pool, name.name); + info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); + svn_stringbuf_setempty(info->prop_value); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +end_report(svn_ra_serf__xml_parser_t *parser, + svn_ra_serf__dav_props_t name, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = parser->user_data; + report_state_e state; + + state = parser->state->current_state; + + if (state == NONE) + { + if (strcmp(name.name, "update-report") == 0) + { + ctx->report_completed = TRUE; + } + else + { + /* nothing to close yet. */ + return SVN_NO_ERROR; + } + } + + if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) || + (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0)))) + { + const char *checked_in_url; + report_info_t *info = parser->state->private; + + /* We've now closed this directory; note it. */ + info->dir->tag_closed = TRUE; + + /* go fetch info->file_name from DAV:checked-in */ + checked_in_url = + svn_ra_serf__get_ver_prop(info->dir->props, info->base_name, + info->base_rev, "DAV:", "checked-in"); + + /* If we were expecting to have the properties and we aren't able to + * get it, bail. + */ + if (!checked_in_url && + (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)) + { + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("The REPORT or PROPFIND response did not " + "include the requested checked-in value")); + } + + info->dir->url = checked_in_url; + + /* At this point, we should have the checked-in href. + * If needed, create the PROPFIND to retrieve the dir's properties. + */ + if (info->dir->fetch_props) + { + svn_ra_serf__list_t *list_item; + + SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler, + info->dir->props, ctx->sess, + get_best_connection(ctx), + info->dir->url, + ctx->target_rev, "0", + all_props, + &ctx->done_dir_propfinds, + info->dir->pool)); + SVN_ERR_ASSERT(info->dir->propfind_handler); + + /* Create a serf request for the PROPFIND. */ + svn_ra_serf__request_create(info->dir->propfind_handler); + + ctx->num_active_propfinds++; + + list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); + list_item->data = info->dir; + list_item->next = ctx->active_dir_propfinds; + ctx->active_dir_propfinds = list_item; + + if (ctx->num_active_fetches + ctx->num_active_propfinds + > REQUEST_COUNT_TO_PAUSE) + ctx->parser_ctx->paused = TRUE; + } + else + { + info->dir->propfind_handler = NULL; + } + + /* See if this directory (and perhaps even parents of that) can + be closed now. This is likely to be the case only if we + didn't need to contact the server for supplemental + information required to handle any of this directory's + children. */ + SVN_ERR(maybe_close_dir_chain(info->dir)); + svn_ra_serf__xml_pop_state(parser); + } + else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0) + { + report_info_t *info = parser->state->private; + + /* Expand our full name now if we haven't done so yet. */ + if (!info->name) + { + info->name = svn_relpath_join(info->dir->name, info->base_name, + info->pool); + } + + info->lock_token = svn_hash_gets(ctx->lock_path_tokens, info->name); + + if (info->lock_token && !info->fetch_props) + info->fetch_props = TRUE; + + /* If possible, we'd like to fetch only a delta against a + * version of the file we already have in our working copy, + * rather than fetching a fulltext. + * + * In HTTP v2, we can simply construct the URL we need given the + * repos_relpath and base revision number. + */ + if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess)) + { + const char *repos_relpath; + + /* If this file is switched vs the editor root we should provide + its real url instead of the one calculated from the session root. + */ + repos_relpath = svn_hash_gets(ctx->switched_paths, info->name); + + if (!repos_relpath) + { + if (ctx->root_is_switched) + { + /* We are updating a direct target (most likely a file) + that is switched vs its parent url */ + SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool) + == '\0'); + + repos_relpath = svn_hash_gets(ctx->switched_paths, ""); + } + else + repos_relpath = svn_relpath_join(info->dir->repos_relpath, + info->base_name, info->pool); + } + + info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s", + ctx->sess->rev_root_stub, + info->base_rev, + svn_path_uri_encode(repos_relpath, + info->pool)); + } + else if (ctx->sess->wc_callbacks->get_wc_prop) + { + /* If we have a WC, we might be able to dive all the way into the WC + * to get the previous URL so we can do a differential GET with the + * base URL. + */ + const svn_string_t *value = NULL; + SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop( + ctx->sess->wc_callback_baton, info->name, + SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool)); + + info->delta_base = value ? value->data : NULL; + } + + /* go fetch info->name from DAV:checked-in */ + info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, + info->base_rev, "DAV:", "checked-in"); + if (!info->url) + { + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("The REPORT or PROPFIND response did not " + "include the requested checked-in value")); + } + + /* If the server is in "send-all" mode, we might have opened the + file when we started seeing content for it. If we didn't get + any content for it, we still need to open the file. But in + any case, we can then immediately close it. */ + if (ctx->send_all_mode) + { + if (! info->file_baton) + { + SVN_ERR(open_updated_file(info, FALSE, info->pool)); + } + SVN_ERR(close_updated_file(info, info->pool)); + info->dir->ref_count--; + } + /* Otherwise, if the server is *not* in "send-all" mode, we + should be at a point where we can queue up any auxiliary + content-fetching requests. */ + else + { + SVN_ERR(fetch_file(ctx, info)); + } + + svn_ra_serf__xml_pop_state(parser); + } + else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0) + { + report_info_t *info = parser->state->private; + + /* go fetch info->name from DAV:checked-in */ + info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, + info->base_rev, "DAV:", "checked-in"); + if (!info->url) + { + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("The REPORT or PROPFIND response did not " + "include the requested checked-in value")); + } + + /* If the server is in "send-all" mode, we might have opened the + file when we started seeing content for it. If we didn't get + any content for it, we still need to open the file. But in + any case, we can then immediately close it. */ + if (ctx->send_all_mode) + { + if (! info->file_baton) + { + SVN_ERR(open_updated_file(info, FALSE, info->pool)); + } + SVN_ERR(close_updated_file(info, info->pool)); + info->dir->ref_count--; + } + /* Otherwise, if the server is *not* in "send-all" mode, we + should be at a point where we can queue up any auxiliary + content-fetching requests. */ + else + { + SVN_ERR(fetch_file(ctx, info)); + } + + svn_ra_serf__xml_pop_state(parser); + } + else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0) + { + report_info_t *info = parser->state->private; + + /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to + <fetch-file>s and such) when *not* in "send-all" mode. As a + client, we're smart enough to know that's wrong, so when not + in "receiving-all" mode, we'll ignore these tags. */ + if (ctx->send_all_mode) + { + SVN_ERR(svn_stream_close(info->base64_decoder)); + } + + svn_ra_serf__xml_pop_state(parser); + } + else if (state == PROP) + { + /* We need to move the prop_ns, prop_name, and prop_value into the + * same lifetime as the dir->pool. + */ + svn_ra_serf__ns_t *ns, *ns_name_match; + svn_boolean_t found = FALSE; + report_info_t *info; + report_dir_t *dir; + apr_hash_t *props; + const svn_string_t *set_val_str; + apr_pool_t *pool; + + info = parser->state->private; + dir = info->dir; + + /* We're going to be slightly tricky. We don't care what the ->url + * field is here at this point. So, we're going to stick a single + * copy of the property name inside of the ->url field. + */ + ns_name_match = NULL; + for (ns = dir->ns_list; ns; ns = ns->next) + { + if (strcmp(ns->namespace, info->prop_ns) == 0) + { + ns_name_match = ns; + if (strcmp(ns->url, info->prop_name) == 0) + { + found = TRUE; + break; + } + } + } + + if (!found) + { + ns = apr_palloc(dir->pool, sizeof(*ns)); + if (!ns_name_match) + { + ns->namespace = apr_pstrdup(dir->pool, info->prop_ns); + } + else + { + ns->namespace = ns_name_match->namespace; + } + ns->url = apr_pstrdup(dir->pool, info->prop_name); + + ns->next = dir->ns_list; + dir->ns_list = ns; + } + + if (strcmp(name.name, "remove-prop") != 0) + { + props = info->props; + pool = info->pool; + } + else + { + props = dir->removed_props; + pool = dir->pool; + svn_stringbuf_setempty(info->prop_value); + } + + if (info->prop_encoding) + { + if (strcmp(info->prop_encoding, "base64") == 0) + { + svn_string_t tmp; + + /* Don't use morph_info_string cuz we need prop_value to + remain usable. */ + tmp.data = info->prop_value->data; + tmp.len = info->prop_value->len; + + set_val_str = svn_base64_decode_string(&tmp, pool); + } + else + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, + NULL, + _("Got unrecognized encoding '%s'"), + info->prop_encoding); + } + } + else + { + set_val_str = svn_string_create_from_buf(info->prop_value, pool); + } + + svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev, + ns->namespace, ns->url, set_val_str, pool); + + /* Advance handling: if we spotted the md5-checksum property on + the wire, remember it's value. */ + if (strcmp(ns->url, "md5-checksum") == 0 + && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0) + info->final_checksum = apr_pstrdup(info->pool, set_val_str->data); + + svn_ra_serf__xml_pop_state(parser); + } + else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME) + { + svn_ra_serf__xml_pop_state(parser); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +cdata_report(svn_ra_serf__xml_parser_t *parser, + const char *data, + apr_size_t len, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = parser->user_data; + + UNUSED_CTX(ctx); + + if (parser->state->current_state == PROP) + { + report_info_t *info = parser->state->private; + + svn_stringbuf_appendbytes(info->prop_value, data, len); + } + else if (parser->state->current_state == TXDELTA) + { + /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to + <fetch-file>s and such) when *not* in "send-all" mode. As a + client, we're smart enough to know that's wrong, so when not + in "receiving-all" mode, we'll ignore these tags. */ + if (ctx->send_all_mode) + { + apr_size_t nlen = len; + report_info_t *info = parser->state->private; + + SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen)); + if (nlen != len) + { + /* Short write without associated error? "Can't happen." */ + return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, + _("Error writing to '%s': unexpected EOF"), + info->name); + } + } + } + + return SVN_NO_ERROR; +} + + +/** Editor callbacks given to callers to create request body */ + +/* Helper to create simple xml tag without attributes. */ +static void +make_simple_xml_tag(svn_stringbuf_t **buf_p, + const char *tagname, + const char *cdata, + apr_pool_t *pool) +{ + svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL); + svn_xml_escape_cdata_cstring(buf_p, cdata, pool); + svn_xml_make_close_tag(buf_p, pool, tagname); +} + +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) +{ + report_context_t *report = report_baton; + svn_stringbuf_t *buf = NULL; + + svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", + "rev", apr_ltoa(pool, revision), + "lock-token", lock_token, + "depth", svn_depth_to_word(depth), + "start-empty", start_empty ? "true" : NULL, + NULL); + svn_xml_escape_cdata_cstring(&buf, path, pool); + svn_xml_make_close_tag(&buf, pool, "S:entry"); + + SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, + NULL, pool)); + + if (lock_token) + { + svn_hash_sets(report->lock_path_tokens, + apr_pstrdup(report->pool, path), + apr_pstrdup(report->pool, lock_token)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + report_context_t *report = report_baton; + svn_stringbuf_t *buf = NULL; + + make_simple_xml_tag(&buf, "S:missing", path, pool); + + SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, + NULL, 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) +{ + report_context_t *report = report_baton; + const char *link, *report_target; + apr_uri_t uri; + apr_status_t status; + svn_stringbuf_t *buf = NULL; + + /* We need to pass in the baseline relative path. + * + * TODO Confirm that it's on the same server? + */ + status = apr_uri_parse(pool, url, &uri); + if (status) + { + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Unable to parse URL '%s'"), url); + } + + SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, + NULL, pool)); + SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, + NULL, pool)); + + link = apr_pstrcat(pool, "/", link, (char *)NULL); + + svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", + "rev", apr_ltoa(pool, revision), + "lock-token", lock_token, + "depth", svn_depth_to_word(depth), + "linkpath", link, + "start-empty", start_empty ? "true" : NULL, + NULL); + svn_xml_escape_cdata_cstring(&buf, path, pool); + svn_xml_make_close_tag(&buf, pool, "S:entry"); + + SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, + NULL, pool)); + + /* Store the switch roots to allow generating repos_relpaths from just + the working copy paths. (Needed for HTTPv2) */ + path = apr_pstrdup(report->pool, path); + svn_hash_sets(report->switched_paths, + path, apr_pstrdup(report->pool, link + 1)); + + if (!*path) + report->root_is_switched = TRUE; + + if (lock_token) + { + svn_hash_sets(report->lock_path_tokens, + path, apr_pstrdup(report->pool, lock_token)); + } + + return APR_SUCCESS; +} + +/** Minimum nr. of outstanding requests needed before a new connection is + * opened. */ +#define REQS_PER_CONN 8 + +/** This function creates a new connection for this serf session, but only + * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is + * only one main connection open. + */ +static svn_error_t * +open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs) +{ + /* For each REQS_PER_CONN outstanding requests open a new connection, with + * a minimum of 1 extra connection. */ + if (sess->num_conns == 1 || + ((num_active_reqs / REQS_PER_CONN) > sess->num_conns)) + { + int cur = sess->num_conns; + apr_status_t status; + + sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur])); + sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool, + NULL, NULL); + sess->conns[cur]->last_status_code = -1; + sess->conns[cur]->session = sess; + status = serf_connection_create2(&sess->conns[cur]->conn, + sess->context, + sess->session_url, + svn_ra_serf__conn_setup, + sess->conns[cur], + svn_ra_serf__conn_closed, + sess->conns[cur], + sess->pool); + if (status) + return svn_ra_serf__wrap_err(status, NULL); + + sess->num_conns++; + } + + return SVN_NO_ERROR; +} + +/* Serf callback to create update request body bucket. */ +static svn_error_t * +create_update_report_body(serf_bucket_t **body_bkt, + void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool) +{ + report_context_t *report = baton; + apr_off_t offset; + + offset = 0; + apr_file_seek(report->body_file, APR_SET, &offset); + + *body_bkt = serf_bucket_file_create(report->body_file, alloc); + + return SVN_NO_ERROR; +} + +/* Serf callback to setup update request headers. */ +static svn_error_t * +setup_update_report_headers(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool) +{ + report_context_t *report = baton; + + if (report->sess->using_compression) + { + serf_bucket_headers_setn(headers, "Accept-Encoding", + "gzip;svndiff1;q=0.9,svndiff;q=0.8"); + } + else + { + serf_bucket_headers_setn(headers, "Accept-Encoding", + "svndiff1;q=0.9,svndiff;q=0.8"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +finish_report(void *report_baton, + apr_pool_t *pool) +{ + report_context_t *report = report_baton; + svn_ra_serf__session_t *sess = report->sess; + svn_ra_serf__handler_t *handler; + svn_ra_serf__xml_parser_t *parser_ctx; + const char *report_target; + svn_stringbuf_t *buf = NULL; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_error_t *err; + apr_interval_time_t waittime_left = sess->timeout; + + svn_xml_make_close_tag(&buf, iterpool, "S:update-report"); + SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, + NULL, iterpool)); + + /* We need to flush the file, make it unbuffered (so that it can be + * zero-copied via mmap), and reset the position before attempting to + * deliver the file. + * + * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap + * and zero-copy the PUT body. However, on older APR versions, we can't + * check the buffer status; but serf will fall through and create a file + * bucket for us on the buffered svndiff handle. + */ + apr_file_flush(report->body_file); +#if APR_VERSION_AT_LEAST(1, 3, 0) + apr_file_buffer_set(report->body_file, NULL, 0); +#endif + + SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool)); + + /* create and deliver request */ + report->path = report_target; + + handler = apr_pcalloc(pool, sizeof(*handler)); + + handler->handler_pool = pool; + handler->method = "REPORT"; + handler->path = report->path; + handler->body_delegate = create_update_report_body; + handler->body_delegate_baton = report; + handler->body_type = "text/xml"; + handler->custom_accept_encoding = TRUE; + handler->header_delegate = setup_update_report_headers; + handler->header_delegate_baton = report; + handler->conn = sess->conns[0]; + handler->session = sess; + + parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); + + parser_ctx->pool = pool; + parser_ctx->response_type = "update-report"; + parser_ctx->user_data = report; + parser_ctx->start = start_report; + parser_ctx->end = end_report; + parser_ctx->cdata = cdata_report; + parser_ctx->done = &report->done; + + handler->response_handler = svn_ra_serf__handle_xml_parser; + handler->response_baton = parser_ctx; + + report->parser_ctx = parser_ctx; + + svn_ra_serf__request_create(handler); + + /* Open the first extra connection. */ + SVN_ERR(open_connection_if_needed(sess, 0)); + + sess->cur_conn = 1; + + /* Note that we may have no active GET or PROPFIND requests, yet the + processing has not been completed. This could be from a delay on the + network or because we've spooled the entire response into our "pending" + content of the XML parser. The DONE flag will get set when all the + XML content has been received *and* parsed. */ + while (!report->done + || report->num_active_fetches + || report->num_active_propfinds) + { + apr_pool_t *iterpool_inner; + svn_ra_serf__list_t *done_list; + int i; + apr_status_t status; + + /* Note: this throws out the old ITERPOOL_INNER. */ + svn_pool_clear(iterpool); + + if (sess->cancel_func) + SVN_ERR(sess->cancel_func(sess->cancel_baton)); + + /* We need to be careful between the outer and inner ITERPOOLs, + and what items are allocated within. */ + iterpool_inner = svn_pool_create(iterpool); + + status = serf_context_run(sess->context, + SVN_RA_SERF__CONTEXT_RUN_DURATION, + iterpool_inner); + + err = sess->pending_error; + sess->pending_error = SVN_NO_ERROR; + + if (!err && handler->done && handler->server_error) + { + err = handler->server_error->error; + } + + /* If the context duration timeout is up, we'll subtract that + duration from the total time alloted for such things. If + there's no time left, we fail with a message indicating that + the connection timed out. */ + if (APR_STATUS_IS_TIMEUP(status)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + status = 0; + + if (sess->timeout) + { + if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) + { + waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; + } + else + { + return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, + _("Connection timed out")); + } + } + } + else + { + waittime_left = sess->timeout; + } + + if (status && handler->sline.code != 200) + { + return svn_error_trace( + svn_error_compose_create( + svn_ra_serf__error_on_status(handler->sline.code, + handler->path, + handler->location), + err)); + } + SVN_ERR(err); + if (status) + { + return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT")); + } + + /* Open extra connections if we have enough requests to send. */ + if (sess->num_conns < sess->max_connections) + SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches + + report->num_active_propfinds)); + + /* Prune completed file PROPFINDs. */ + done_list = report->done_propfinds; + while (done_list) + { + svn_ra_serf__list_t *next_done = done_list->next; + + svn_pool_clear(iterpool_inner); + + report->num_active_propfinds--; + + /* If we have some files that we won't be fetching the content + * for, ensure that we update the file with any altered props. + */ + if (report->file_propchanges_only) + { + svn_ra_serf__list_t *cur, *prev; + + prev = NULL; + cur = report->file_propchanges_only; + + while (cur) + { + report_info_t *item = cur->data; + + if (item->propfind_handler == done_list->data) + { + break; + } + + prev = cur; + cur = cur->next; + } + + /* If we found a match, set the new props and remove this + * propchange from our list. + */ + if (cur) + { + report_info_t *info = cur->data; + + if (!prev) + { + report->file_propchanges_only = cur->next; + } + else + { + prev->next = cur->next; + } + + /* If we've got cached file content for this file, + take care of the locally collected properties and + file content at once. Otherwise, just deal with + the collected properties. + + NOTE: These functions below could delete + info->dir->pool (via maybe_close_dir_chain()), + from which is allocated the list item in + report->file_propchanges_only. + */ + if (info->cached_contents) + { + SVN_ERR(handle_local_content(info, iterpool_inner)); + } + else + { + SVN_ERR(handle_propchange_only(info, iterpool_inner)); + } + } + } + + done_list = next_done; + } + report->done_propfinds = NULL; + + /* Prune completed fetches from our list. */ + done_list = report->done_fetches; + while (done_list) + { + report_fetch_t *done_fetch = done_list->data; + svn_ra_serf__list_t *next_done = done_list->next; + report_dir_t *cur_dir; + + /* Decrease the refcount in the parent directory of the file + whose fetch has completed. */ + cur_dir = done_fetch->info->dir; + cur_dir->ref_count--; + + /* Decrement our active fetch count. */ + report->num_active_fetches--; + + /* See if the parent directory of this fetched item (and + perhaps even parents of that) can be closed now. + + NOTE: This could delete cur_dir->pool, from which is + allocated the list item in report->done_fetches. + */ + SVN_ERR(maybe_close_dir_chain(cur_dir)); + + done_list = next_done; + } + report->done_fetches = NULL; + + /* Prune completed directory PROPFINDs. */ + done_list = report->done_dir_propfinds; + while (done_list) + { + svn_ra_serf__list_t *next_done = done_list->next; + + report->num_active_propfinds--; + + if (report->active_dir_propfinds) + { + svn_ra_serf__list_t *cur, *prev; + + prev = NULL; + cur = report->active_dir_propfinds; + + while (cur) + { + report_dir_t *item = cur->data; + + if (item->propfind_handler == done_list->data) + { + break; + } + + prev = cur; + cur = cur->next; + } + SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */ + + /* If we found a match, set the new props and remove this + * propchange from our list. + */ + if (cur) + { + report_dir_t *cur_dir = cur->data; + + if (!prev) + { + report->active_dir_propfinds = cur->next; + } + else + { + prev->next = cur->next; + } + + /* See if this directory (and perhaps even parents of that) + can be closed now. + + NOTE: This could delete cur_dir->pool, from which is + allocated the list item in report->active_dir_propfinds. + */ + SVN_ERR(maybe_close_dir_chain(cur_dir)); + } + } + + done_list = next_done; + } + report->done_dir_propfinds = NULL; + + /* If the parser is paused, and the number of active requests has + dropped far enough, then resume parsing. */ + if (parser_ctx->paused + && (report->num_active_fetches + report->num_active_propfinds + < REQUEST_COUNT_TO_RESUME)) + parser_ctx->paused = FALSE; + + /* If we have not paused the parser and it looks like data MAY be + present (we can't know for sure because of the private structure), + then go process the pending content. */ + if (!parser_ctx->paused && parser_ctx->pending != NULL) + SVN_ERR(svn_ra_serf__process_pending(parser_ctx, + &report->report_received, + iterpool_inner)); + + /* Debugging purposes only! */ + for (i = 0; i < sess->num_conns; i++) + { + serf_debug__closed_conn(sess->conns[i]->bkt_alloc); + } + } + + /* If we got a complete report, close the edit. Otherwise, abort it. */ + if (report->report_completed) + { + /* Ensure that we opened and closed our root dir and that we closed + * all of our children. */ + if (!report->closed_root && report->root_dir != NULL) + { + SVN_ERR(close_all_dirs(report->root_dir)); + } + + err = report->update_editor->close_edit(report->update_baton, iterpool); + } + else + { + /* Tell the editor that something failed */ + err = report->update_editor->abort_edit(report->update_baton, iterpool); + + err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err, + _("Missing update-report close tag")); + } + + svn_pool_destroy(iterpool); + return svn_error_trace(err); +} + + +static svn_error_t * +abort_report(void *report_baton, + apr_pool_t *pool) +{ +#if 0 + report_context_t *report = report_baton; +#endif + + /* Should we perform some cleanup here? */ + + return SVN_NO_ERROR; +} + +static const svn_ra_reporter3_t ra_serf_reporter = { + set_path, + delete_path, + link_path, + finish_report, + abort_report +}; + + +/** RA function implementations and body */ + +static svn_error_t * +make_update_reporter(svn_ra_session_t *ra_session, + const svn_ra_reporter3_t **reporter, + void **report_baton, + svn_revnum_t revision, + const char *src_path, + const char *dest_path, + const char *update_target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t text_deltas, + svn_boolean_t send_copyfrom_args, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + report_context_t *report; + const svn_delta_editor_t *filter_editor; + void *filter_baton; + svn_boolean_t has_target = *update_target != '\0'; + svn_boolean_t server_supports_depth; + svn_ra_serf__session_t *sess = ra_session->priv; + svn_stringbuf_t *buf = NULL; + svn_boolean_t use_bulk_updates; + + SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth, + SVN_RA_CAPABILITY_DEPTH, scratch_pool)); + /* We can skip the depth filtering when the user requested + depth_files or depth_infinity because the server will + transmit the right stuff anyway. */ + if ((depth != svn_depth_files) + && (depth != svn_depth_infinity) + && ! server_supports_depth) + { + SVN_ERR(svn_delta_depth_filter_editor(&filter_editor, + &filter_baton, + update_editor, + update_baton, + depth, has_target, + sess->pool)); + update_editor = filter_editor; + update_baton = filter_baton; + } + + report = apr_pcalloc(result_pool, sizeof(*report)); + report->pool = result_pool; + report->sess = sess; + report->conn = report->sess->conns[0]; + report->target_rev = revision; + report->ignore_ancestry = ignore_ancestry; + report->send_copyfrom_args = send_copyfrom_args; + report->text_deltas = text_deltas; + report->lock_path_tokens = apr_hash_make(report->pool); + report->switched_paths = apr_hash_make(report->pool); + + report->source = src_path; + report->destination = dest_path; + report->update_target = update_target; + + report->update_editor = update_editor; + report->update_baton = update_baton; + report->done = FALSE; + + *reporter = &ra_serf_reporter; + *report_baton = report; + + SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL, + svn_io_file_del_on_pool_cleanup, + report->pool, scratch_pool)); + + if (sess->bulk_updates == svn_tristate_true) + { + /* User would like to use bulk updates. */ + use_bulk_updates = TRUE; + } + else if (sess->bulk_updates == svn_tristate_false) + { + /* User doesn't want bulk updates. */ + use_bulk_updates = FALSE; + } + else + { + /* User doesn't have any preferences on bulk updates. Decide on server + preferences and capabilities. */ + if (sess->server_allows_bulk) + { + if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0) + { + /* Server doesn't want bulk updates */ + use_bulk_updates = FALSE; + } + else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0) + { + /* Server prefers bulk updates, and we respect that */ + use_bulk_updates = TRUE; + } + else + { + /* Server allows bulk updates, but doesn't dictate its use. Do + whatever is the default. */ + use_bulk_updates = FALSE; + } + } + else + { + /* Pre-1.8 server didn't send the bulk_updates header. Check if server + supports inlining properties in update editor report. */ + if (sess->supports_inline_props) + { + /* Inline props supported: do not use bulk updates. */ + use_bulk_updates = FALSE; + } + else + { + /* Inline props are not supported: use bulk updates to avoid + * PROPFINDs for every added node. */ + use_bulk_updates = TRUE; + } + } + } + + if (use_bulk_updates) + { + svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, + "S:update-report", + "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true", + NULL); + } + else + { + svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, + "S:update-report", + "xmlns:S", SVN_XML_NAMESPACE, + NULL); + /* Subversion 1.8+ servers can be told to send properties for newly + added items inline even when doing a skelta response. */ + make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool); + } + + make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool); + + if (SVN_IS_VALID_REVNUM(report->target_rev)) + { + make_simple_xml_tag(&buf, "S:target-revision", + apr_ltoa(scratch_pool, report->target_rev), + scratch_pool); + } + + if (report->destination && *report->destination) + { + make_simple_xml_tag(&buf, "S:dst-path", report->destination, + scratch_pool); + } + + if (report->update_target && *report->update_target) + { + make_simple_xml_tag(&buf, "S:update-target", report->update_target, + scratch_pool); + } + + if (report->ignore_ancestry) + { + make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool); + } + + if (report->send_copyfrom_args) + { + make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool); + } + + /* Old servers know "recursive" but not "depth"; help them DTRT. */ + if (depth == svn_depth_files || depth == svn_depth_empty) + { + make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool); + } + + /* When in 'send-all' mode, mod_dav_svn will assume that it should + calculate and transmit real text-deltas (instead of empty windows + that merely indicate "text is changed") unless it finds this + element. + + NOTE: Do NOT count on servers actually obeying this, as some exist + which obey send-all, but do not check for this directive at all! + + NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to + override our request and send text-deltas. */ + if (! text_deltas) + { + make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool); + } + + make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool); + + SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, + NULL, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__do_update(svn_ra_session_t *ra_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, + svn_boolean_t ignore_ancestry, + const svn_delta_editor_t *update_editor, + void *update_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + + SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, + revision_to_update_to, + session->session_url.path, NULL, update_target, + depth, ignore_ancestry, TRUE /* text_deltas */, + send_copyfrom_args, + update_editor, update_baton, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__do_diff(svn_ra_session_t *ra_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) +{ + svn_ra_serf__session_t *session = ra_session->priv; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, + revision, + session->session_url.path, versus_url, diff_target, + depth, ignore_ancestry, text_deltas, FALSE, + diff_editor, diff_baton, + pool, scratch_pool)); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__do_status(svn_ra_session_t *ra_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_ra_serf__session_t *session = ra_session->priv; + apr_pool_t *scratch_pool = svn_pool_create(pool); + + SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, + revision, + session->session_url.path, NULL, status_target, + depth, FALSE, FALSE, FALSE, + status_editor, status_baton, + pool, scratch_pool)); + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__do_switch(svn_ra_session_t *ra_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, + svn_boolean_t send_copyfrom_args, + svn_boolean_t ignore_ancestry, + const svn_delta_editor_t *switch_editor, + void *switch_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + + return make_update_reporter(ra_session, reporter, report_baton, + revision_to_switch_to, + session->session_url.path, + switch_url, switch_target, + depth, + ignore_ancestry, + TRUE /* text_deltas */, + send_copyfrom_args, + switch_editor, switch_baton, + result_pool, scratch_pool); +} + +/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents + * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is + * present in PROPS. + * + * Sets *FOUND_P to TRUE if file contents was successfuly fetched. + * + * Performs all temporary allocations in POOL. + */ +static svn_error_t * +try_get_wc_contents(svn_boolean_t *found_p, + svn_ra_serf__session_t *session, + apr_hash_t *props, + svn_stream_t *dst_stream, + apr_pool_t *pool) +{ + apr_hash_t *svn_props; + const char *sha1_checksum_prop; + svn_checksum_t *checksum; + svn_stream_t *wc_stream; + svn_error_t *err; + + /* No contents found by default. */ + *found_p = FALSE; + + if (!session->wc_callbacks->get_wc_contents) + { + /* No callback, nothing to do. */ + return SVN_NO_ERROR; + } + + + svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV); + if (!svn_props) + { + /* No properties -- therefore no checksum property -- in response. */ + return SVN_NO_ERROR; + } + + sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum"); + if (sha1_checksum_prop == NULL) + { + /* No checksum property in response. */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, + sha1_checksum_prop, pool)); + + err = session->wc_callbacks->get_wc_contents( + session->wc_callback_baton, &wc_stream, checksum, pool); + + if (err) + { + svn_error_clear(err); + + /* Ignore errors for now. */ + return SVN_NO_ERROR; + } + + if (wc_stream) + { + SVN_ERR(svn_stream_copy3(wc_stream, + svn_stream_disown(dst_stream, pool), + NULL, NULL, pool)); + *found_p = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__get_file(svn_ra_session_t *ra_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_ra_serf__session_t *session = ra_session->priv; + svn_ra_serf__connection_t *conn; + const char *fetch_url; + apr_hash_t *fetch_props; + svn_node_kind_t res_kind; + const svn_ra_serf__dav_props_t *which_props; + + /* What connection should we go on? */ + conn = session->conns[session->cur_conn]; + + /* Fetch properties. */ + + fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); + + /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. + * + * Otherwise, we need to get the baseline version for this particular + * revision and then fetch that file. + */ + if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) + { + SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, + session, conn, + fetch_url, revision, + pool, pool)); + revision = SVN_INVALID_REVNUM; + } + /* REVISION is always SVN_INVALID_REVNUM */ + SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); + + if (props) + { + which_props = all_props; + } + else if (stream && session->wc_callbacks->get_wc_contents) + { + which_props = type_and_checksum_props; + } + else + { + which_props = check_path_props; + } + + SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url, + SVN_INVALID_REVNUM, + which_props, + pool, pool)); + + /* Verify that resource type is not collection. */ + SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props)); + if (res_kind != svn_node_file) + { + return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, + _("Can't get text contents of a directory")); + } + + /* TODO Filter out all of our props into a usable format. */ + if (props) + { + /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() + ### put them into POOL, so we're okay. */ + SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props, + pool, pool)); + } + + if (stream) + { + svn_boolean_t found; + SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool)); + + /* No contents found in the WC, let's fetch from server. */ + if (!found) + { + report_fetch_t *stream_ctx; + svn_ra_serf__handler_t *handler; + + /* Create the fetch context. */ + stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); + stream_ctx->target_stream = stream; + stream_ctx->sess = session; + stream_ctx->conn = conn; + stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info)); + stream_ctx->info->name = fetch_url; + + handler = apr_pcalloc(pool, sizeof(*handler)); + + handler->handler_pool = pool; + handler->method = "GET"; + handler->path = fetch_url; + handler->conn = conn; + handler->session = session; + + handler->custom_accept_encoding = TRUE; + handler->header_delegate = headers_fetch; + handler->header_delegate_baton = stream_ctx; + + handler->response_handler = handle_stream; + handler->response_baton = stream_ctx; + + handler->response_error = cancel_fetch; + handler->response_error_baton = stream_ctx; + + stream_ctx->handler = handler; + + svn_ra_serf__request_create(handler); + + SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool)); + } + } + + return SVN_NO_ERROR; +} |