Author: cmpilato
Date: Fri May 4 13:13:00 2012
New Revision: 1333936
URL: http://svn.apache.org/viewvc?rev=1333936&view=rev
Log:
Teach libsvn_ra_serf to make use of the server-provided SHA1 checksums
I introduced in 1.7 (iff they are provided, of course) to avoid
downloading server content that's already locally available.
* subversion/include/private/svn_wc_private.h,
* subversion/libsvn_wc/adm_ops.c
(svn_wc__get_pristine_contents_by_checksum): New function.
* subversion/include/svn_ra.h
(svn_ra_get_wc_contents_func_t): New callback type.
(svn_ra_callbacks2_t): Add get_wc_contents vtable member.
* subversion/libsvn_client/ra.c
(callback_baton_t): Add 'wcroot_abspath' member.
(get_wc_contents): New function.
(svn_client__open_ra_session_internal): Initialize new
'get_wc_contents' baton member.
* subversion/libsvn_ra_serf/update.c
(report_ctx_t): Add 'cached_contents' member.
(local_fetch, handle_local_fetch): New functions.
(fetch_file): Use the 'get_wc_contents' RA callback to check for a
local copy of the file contents whose SHA1 checksum we are about to
fetch from the server. If we've got those contents already, read
them (via the callback-returned stream) instead of from the network.
Modified:
subversion/trunk/subversion/include/private/svn_wc_private.h
subversion/trunk/subversion/include/svn_ra.h
subversion/trunk/subversion/libsvn_client/ra.c
subversion/trunk/subversion/libsvn_ra_serf/update.c
subversion/trunk/subversion/libsvn_wc/adm_ops.c
Modified: subversion/trunk/subversion/include/private/svn_wc_private.h
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_wc_private.h?rev=1333936&r1=1333935&r2=1333936&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_wc_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_wc_private.h Fri May 4
13:13:00 2012
@@ -1076,6 +1076,18 @@ svn_wc__node_get_md5_from_sha1(const svn
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Like svn_wc_get_pristine_contents2(), but keyed on the
+ SHA1_CHECKSUM rather than on the local absolute path of the working
+ file. WCROOT_ABSPATH is the absolute path of the root of the
+ working copy in whose pristine database we'll be looking for these
+ contents. */
+svn_error_t *
+svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* Gets an array of const char *repos_relpaths of descendants of LOCAL_ABSPATH,
* which must be the op root of an addition, copy or move. The descendants
Modified: subversion/trunk/subversion/include/svn_ra.h
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_ra.h?rev=1333936&r1=1333935&r2=1333936&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_ra.h (original)
+++ subversion/trunk/subversion/include/svn_ra.h Fri May 4 13:13:00 2012
@@ -120,6 +120,16 @@ typedef svn_error_t *(*svn_ra_invalidate
const char *name,
apr_pool_t *pool);
+/** This is a function type which allows the RA layer to fetch the
+ * cached pristine file contents whose SHA1 checksum is @a
+ * sha1_checksum, if any. @a *contents will be a read stream
+ * containing those contents if they are found; NULL otherwise.
+ */
+typedef svn_error_t *(*svn_ra_get_wc_contents_func_t)(void *baton,
+ svn_stream_t **contents,
+ svn_checksum_t
*sha1_checksum,
+ apr_pool_t *pool);
+
/** A function type for retrieving the youngest revision from a repos. */
typedef svn_error_t *(*svn_ra_get_latest_revnum_func_t)(
@@ -138,6 +148,7 @@ typedef svn_error_t *(*svn_ra_get_client
apr_pool_t *pool);
+
/**
* A callback function type for use in @c get_file_revs.
* @a baton is provided by the caller, @a path is the pathname of the file
@@ -516,6 +527,11 @@ typedef struct svn_ra_callbacks2_t
*/
svn_ra_get_client_string_func_t get_client_string;
+ /** Working copy file content fetching function.
+ * @since New in 1.8.
+ */
+ svn_ra_get_wc_contents_func_t get_wc_contents;
+
} svn_ra_callbacks2_t;
/** Similar to svn_ra_callbacks2_t, except that the progress
Modified: subversion/trunk/subversion/libsvn_client/ra.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/ra.c?rev=1333936&r1=1333935&r2=1333936&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/ra.c (original)
+++ subversion/trunk/subversion/libsvn_client/ra.c Fri May 4 13:13:00 2012
@@ -52,6 +52,10 @@ typedef struct callback_baton_t
this base directory. */
const char *base_dir_abspath;
+ /* Holds the absolute path of the working copy root for the working
+ copy in which BASE_DIR_ABSPATH is found. */
+ const char *wcroot_abspath;
+
/* An array of svn_client_commit_item3_t * structures, present only
during working copy commits. */
const apr_array_header_t *commit_items;
@@ -234,6 +238,30 @@ invalidate_wc_props(void *baton,
}
+/* This implements the `svn_ra_get_wc_contents_func_t' interface. */
+static svn_error_t *
+get_wc_contents(void *baton,
+ svn_stream_t **contents,
+ svn_checksum_t *sha1_checksum,
+ apr_pool_t *pool)
+{
+ callback_baton_t *cb = baton;
+
+ if (! cb->wcroot_abspath)
+ {
+ *contents = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(
+ svn_wc__get_pristine_contents_by_checksum(contents,
+ cb->ctx->wc_ctx,
+ cb->wcroot_abspath,
+ sha1_checksum,
+ pool, pool));
+}
+
+
static svn_error_t *
cancel_callback(void *baton)
{
@@ -284,6 +312,7 @@ svn_client__open_ra_session_internal(svn
cbtable->progress_baton = ctx->progress_baton;
cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL;
cbtable->get_client_string = get_client_string;
+ cbtable->get_wc_contents = get_wc_contents;
cb->base_dir_abspath = base_dir_abspath;
cb->commit_items = commit_items;
@@ -291,6 +320,7 @@ svn_client__open_ra_session_internal(svn
if (base_dir_abspath)
{
+ const char *wcroot_abspath;
svn_error_t *err = svn_wc__node_get_repos_info(NULL, &uuid, ctx->wc_ctx,
base_dir_abspath,
pool, pool);
@@ -303,7 +333,13 @@ svn_client__open_ra_session_internal(svn
uuid = NULL;
}
else
- SVN_ERR(err);
+ {
+ SVN_ERR(err);
+ }
+
+ SVN_ERR(svn_wc__get_wc_root(&wcroot_abspath, ctx->wc_ctx,
+ base_dir_abspath, pool, pool));
+ cb->wcroot_abspath = wcroot_abspath;
}
/* If the caller allows for auto-following redirections, and the
Modified: subversion/trunk/subversion/libsvn_ra_serf/update.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_ra_serf/update.c?rev=1333936&r1=1333935&r2=1333936&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_ra_serf/update.c (original)
+++ subversion/trunk/subversion/libsvn_ra_serf/update.c Fri May 4 13:13:00 2012
@@ -237,6 +237,10 @@ typedef struct report_info_t
/* 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.
*/
@@ -1229,6 +1233,179 @@ handle_propchange_only(report_info_t *in
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. */
+static svn_error_t *
+local_fetch(report_info_t *info)
+{
+ const svn_delta_editor_t *update_editor = info->dir->update_editor;
+ svn_txdelta_window_t delta_window = { 0 };
+ svn_txdelta_op_t delta_op;
+ svn_string_t window_data;
+ char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
+
+ SVN_ERR(open_dir(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);
+ }
+
+ 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));
+ }
+
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+
+ while (1)
+ {
+ apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
+
+ SVN_ERR(svn_stream_read(info->cached_contents, read_buf, &read_len));
+ if (read_len == 0)
+ break;
+
+ window_data.data = read_buf;
+ window_data.len = read_len;
+
+ delta_op.action_code = svn_txdelta_new;
+ delta_op.offset = 0;
+ delta_op.length = read_len;
+
+ delta_window.tview_len = read_len;
+ delta_window.num_ops = 1;
+ delta_window.ops = &delta_op;
+ delta_window.new_data = &window_data;
+
+ SVN_ERR(info->textdelta(&delta_window, info->textdelta_baton));
+
+ if (read_len < SVN__STREAM_CHUNK_SIZE)
+ break;
+ }
+
+ SVN_ERR(info->textdelta(NULL, info->textdelta_baton));
+
+ SVN_ERR(svn_stream_close(info->cached_contents));
+ info->cached_contents = NULL;
+
+ if (info->lock_token)
+ check_lock(info);
+
+ /* 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,
+ info->pool));
+
+ SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
+ info->base_name,
+ info->base_rev,
+ remove_file_props, info,
+ info->pool));
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->url,
+ info->target_rev,
+ set_file_props, info,
+ info->pool));
+ }
+
+ SVN_ERR(info->dir->update_editor->close_file(info->file_baton,
+ info->final_checksum,
+ info->pool));
+
+ /* We're done with our pools. */
+ svn_pool_destroy(info->editor_pool);
+ svn_pool_destroy(info->pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_local_fetch(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ report_fetch_t *fetch_ctx = handler_baton;
+ apr_status_t status;
+ serf_status_line sl;
+ svn_error_t *err;
+ const char *data;
+ apr_size_t len;
+
+ /* 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. */
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+ if (sl.code != 200)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request failed: %d %s"),
+ sl.code, sl.reason);
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ while (1)
+ {
+ status = serf_bucket_read(response, 8000, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+ if (APR_STATUS_IS_EOF(status))
+ {
+ err = local_fetch(fetch_ctx->info);
+ if (err)
+ {
+ return 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;
+ return svn_error_wrap_apr(status, NULL);
+ }
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+/* --------------------------------------------------------- */
+
static svn_error_t *
fetch_file(report_context_t *ctx, report_info_t *info)
{
@@ -1239,9 +1416,8 @@ fetch_file(report_context_t *ctx, report
conn = ctx->sess->conns[ctx->sess->cur_conn];
/* 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");
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
if (!info->url)
{
@@ -1269,34 +1445,98 @@ fetch_file(report_context_t *ctx, report
*/
if (info->fetch_file && ctx->text_deltas)
{
- report_fetch_t *fetch_ctx;
+ svn_stream_t *contents = NULL;
- 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;
+ if (ctx->sess->wc_callbacks->get_wc_contents
+ && info->final_sha1_checksum)
+ {
+ svn_error_t *err;
+ svn_checksum_t *sha1_checksum;
- handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
+ err = svn_checksum_parse_hex(&sha1_checksum, svn_checksum_sha1,
+ info->final_sha1_checksum, info->pool);
+ if (!err)
+ {
+ err = ctx->sess->wc_callbacks->get_wc_contents(
+ ctx->sess->wc_callback_baton, &contents,
+ sha1_checksum, info->pool);
+ }
- handler->method = "GET";
- handler->path = fetch_ctx->info->url;
+ 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);
+ }
+ else
+ {
+ info->cached_contents = contents;
+ }
+ }
- handler->conn = conn;
- handler->session = ctx->sess;
+ /* If the working copy can provided cached contents for this
+ file, we'll send a simple HEAD request (which I'll claim is
+ to verify readability, but really is just so I can provide a
+ Serf-queued-request-compliant way of processing the contents
+ after the PROPFIND for the file's properties ... ugh).
- handler->header_delegate = headers_fetch;
- handler->header_delegate_baton = fetch_ctx;
+ Otherwise, we use a GET request for the file's contents. */
+ if (info->cached_contents)
+ {
+ report_fetch_t *fetch_ctx;
- handler->response_handler = handle_fetch;
- handler->response_baton = 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->response_error = cancel_fetch;
- handler->response_error_baton = fetch_ctx;
+ handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
- svn_ra_serf__request_create(handler);
+ handler->method = "HEAD";
+ handler->path = fetch_ctx->info->url;
- ctx->active_fetches++;
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ handler->response_handler = handle_local_fetch;
+ handler->response_baton = fetch_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->active_fetches++;
+ }
+ else
+ {
+ 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->method = "GET";
+ handler->path = fetch_ctx->info->url;
+
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ 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;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->active_fetches++;
+ }
}
else if (info->propfind)
{
Modified: subversion/trunk/subversion/libsvn_wc/adm_ops.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/adm_ops.c?rev=1333936&r1=1333935&r2=1333936&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/adm_ops.c (original)
+++ subversion/trunk/subversion/libsvn_wc/adm_ops.c Fri May 4 13:13:00 2012
@@ -2250,6 +2250,18 @@ svn_wc_get_pristine_contents2(svn_stream
scratch_pool));
}
+svn_error_t *
+svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents,
+ svn_wc_context_t *wc_ctx,
+ const char *wcroot_abspath,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ return svn_error_trace(svn_wc__db_pristine_read(contents, NULL, wc_ctx->db,
+ wcroot_abspath,
sha1_checksum,
+ result_pool, scratch_pool));
+}
svn_error_t *
svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db,