#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"
typedef enum report_state_e {
INITIAL = XML_STATE_INITIAL ,
UPDATE_REPORT,
TARGET_REVISION,
OPEN_DIR,
ADD_DIR,
OPEN_FILE,
ADD_FILE,
DELETE_ENTRY,
ABSENT_DIR,
ABSENT_FILE,
SET_PROP,
REMOVE_PROP,
PROP,
FETCH_FILE,
FETCH_PROPS,
TXDELTA,
CHECKED_IN,
CHECKED_IN_HREF,
MD5_CHECKSUM,
VERSION_NAME,
CREATIONDATE,
CREATOR_DISPLAYNAME
} report_state_e;
#define D_ "DAV:"
#define S_ SVN_XML_NAMESPACE
#define V_ SVN_DAV_PROP_NS_DAV
static const svn_ra_serf__xml_transition_t update_ttable[] = {
{ INITIAL, S_, "update-report", UPDATE_REPORT,
FALSE, { "?inline-props", "?send-all", NULL }, TRUE },
{ 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 },
{ ADD_DIR, S_, "open-directory", OPEN_DIR,
FALSE, { "rev", "name", NULL }, TRUE },
{ OPEN_DIR, S_, "add-directory", ADD_DIR,
FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
NULL }, TRUE },
{ ADD_DIR, S_, "add-directory", ADD_DIR,
FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
NULL }, TRUE },
{ OPEN_DIR, S_, "open-file", OPEN_FILE,
FALSE, { "rev", "name", NULL }, TRUE },
{ ADD_DIR, S_, "open-file", OPEN_FILE,
FALSE, { "rev", "name", NULL }, TRUE },
{ OPEN_DIR, S_, "add-file", ADD_FILE,
FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
"?sha1-checksum", NULL }, TRUE },
{ ADD_DIR, S_, "add-file", ADD_FILE,
FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
"?sha1-checksum", NULL }, TRUE },
{ OPEN_DIR, S_, "delete-entry", DELETE_ENTRY,
FALSE, { "?rev", "name", NULL }, TRUE },
{ ADD_DIR, S_, "delete-entry", DELETE_ENTRY,
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 },
{ OPEN_DIR, D_, "checked-in", CHECKED_IN,
FALSE, { NULL }, FALSE },
{ ADD_DIR, D_, "checked-in", CHECKED_IN,
FALSE, { NULL }, FALSE },
{ OPEN_FILE, D_, "checked-in", CHECKED_IN,
FALSE, { NULL }, FALSE },
{ ADD_FILE, D_, "checked-in", CHECKED_IN,
FALSE, { NULL }, FALSE },
{ OPEN_DIR, S_, "set-prop", SET_PROP,
TRUE, { "name", "?encoding", NULL }, TRUE },
{ ADD_DIR, S_, "set-prop", SET_PROP,
TRUE, { "name", "?encoding", NULL }, TRUE },
{ OPEN_FILE, S_, "set-prop", SET_PROP,
TRUE, { "name", "?encoding", NULL }, TRUE },
{ ADD_FILE, S_, "set-prop", SET_PROP,
TRUE, { "name", "?encoding", NULL }, TRUE },
{ OPEN_DIR, S_, "remove-prop", REMOVE_PROP,
TRUE, { "name", NULL }, TRUE },
{ ADD_DIR, S_, "remove-prop", REMOVE_PROP,
TRUE, { "name", NULL }, TRUE },
{ OPEN_FILE, S_, "remove-prop", REMOVE_PROP,
TRUE, { "name", NULL }, TRUE },
{ ADD_FILE, S_, "remove-prop", REMOVE_PROP,
TRUE, { "name", NULL }, TRUE },
{ OPEN_FILE, S_, "prop", PROP,
FALSE, { NULL }, FALSE },
{ OPEN_DIR, S_, "prop", PROP,
FALSE, { NULL }, FALSE },
{ ADD_FILE, S_, "prop", PROP,
FALSE, { NULL }, FALSE },
{ ADD_DIR, S_, "prop", PROP,
FALSE, { NULL }, FALSE },
{ OPEN_FILE, S_, "txdelta", TXDELTA,
FALSE, { "?base-checksum" }, TRUE },
{ ADD_FILE, S_, "txdelta", TXDELTA,
FALSE, { "?base-checksum" }, TRUE },
{ OPEN_FILE, S_, "fetch-file", FETCH_FILE,
FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE},
{ ADD_FILE, S_, "fetch-file", FETCH_FILE,
FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE },
{ CHECKED_IN, D_, "href", CHECKED_IN_HREF,
TRUE, { NULL }, TRUE },
{ PROP, V_, "md5-checksum", MD5_CHECKSUM,
TRUE, { NULL }, TRUE },
{ OPEN_DIR, S_, "fetch-props", FETCH_PROPS,
FALSE, { NULL }, FALSE },
{ OPEN_FILE, S_, "fetch-props", FETCH_PROPS,
FALSE, { NULL }, FALSE },
{ PROP, D_, "version-name", VERSION_NAME,
TRUE, { NULL }, TRUE },
{ PROP, D_, "creationdate", CREATIONDATE,
TRUE, { NULL }, TRUE },
{ PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME,
TRUE, { NULL }, TRUE },
{ 0 }
};
#define REQUEST_COUNT_TO_PAUSE 50
#define REQUEST_COUNT_TO_RESUME 40
#define SPILLBUF_BLOCKSIZE 4096
#define SPILLBUF_MAXBUFFSIZE 131072
#define PARSE_CHUNK_SIZE 8000
typedef struct report_context_t report_context_t;
typedef struct body_create_baton_t body_create_baton_t;
typedef struct dir_baton_t
{
struct dir_baton_t *parent_dir;
apr_pool_t *pool;
report_context_t *ctx;
const char *relpath;
const char *base_name;
const char *url;
const char *repos_relpath;
svn_revnum_t base_rev;
const char *copyfrom_path;
svn_revnum_t copyfrom_rev;
svn_boolean_t dir_opened;
void *dir_baton;
apr_size_t ref_count;
svn_boolean_t fetch_props;
svn_ra_serf__handler_t *propfind_handler;
apr_hash_t *remove_props;
} dir_baton_t;
typedef struct file_baton_t
{
dir_baton_t *parent_dir;
apr_pool_t *pool;
const char *relpath;
const char *base_name;
const char *url;
const char *repos_relpath;
const char *lock_token;
svn_revnum_t base_rev;
const char *copyfrom_path;
svn_revnum_t copyfrom_rev;
svn_boolean_t file_opened;
void *file_baton;
svn_boolean_t fetch_props;
svn_ra_serf__handler_t *propfind_handler;
svn_boolean_t found_lock_prop;
apr_hash_t *remove_props;
svn_boolean_t fetch_file;
svn_txdelta_window_handler_t txdelta;
void *txdelta_baton;
svn_checksum_t *base_md5_checksum;
svn_checksum_t *final_md5_checksum;
svn_checksum_t *final_sha1_checksum;
svn_stream_t *txdelta_stream;
} file_baton_t;
typedef struct fetch_ctx_t {
svn_ra_serf__handler_t *handler;
svn_ra_serf__session_t *session;
file_baton_t *file;
svn_boolean_t read_headers;
svn_boolean_t aborted_read;
apr_off_t aborted_read_size;
apr_off_t read_size;
svn_stream_t *result_stream;
const char *delta_base;
} fetch_ctx_t;
struct report_context_t {
apr_pool_t *pool;
svn_ra_serf__session_t *sess;
const char *source;
const char *destination;
const char *update_target;
svn_revnum_t target_rev;
dir_baton_t *cur_dir;
file_baton_t *cur_file;
svn_boolean_t ignore_ancestry;
svn_boolean_t text_deltas;
svn_boolean_t send_copyfrom_args;
svn_boolean_t send_all_mode;
svn_boolean_t add_props_included;
apr_hash_t *switched_paths;
const svn_delta_editor_t *editor;
void *editor_baton;
svn_stream_t *body_template;
svn_ra_serf__request_body_t *body;
unsigned int num_active_fetches;
unsigned int num_active_propfinds;
svn_boolean_t done;
svn_boolean_t report_received;
svn_boolean_t closed_root;
};
static svn_error_t *
create_dir_baton(dir_baton_t **new_dir,
report_context_t *ctx,
const char *name,
apr_pool_t *scratch_pool)
{
dir_baton_t *parent = ctx->cur_dir;
apr_pool_t *dir_pool;
dir_baton_t *dir;
if (parent)
dir_pool = svn_pool_create(parent->pool);
else
dir_pool = svn_pool_create(ctx->pool);
dir = apr_pcalloc(dir_pool, sizeof(*dir));
dir->pool = dir_pool;
dir->ctx = ctx;
if (parent)
{
dir->parent_dir = parent;
parent->ref_count++;
}
dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool)
: apr_pstrdup(dir_pool, name);
dir->base_name = svn_relpath_basename(dir->relpath, NULL);
dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
if (!dir->repos_relpath)
{
if (parent)
dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
dir_pool);
else
dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
ctx->sess->session_url_str,
dir_pool);
}
dir->base_rev = SVN_INVALID_REVNUM;
dir->copyfrom_rev = SVN_INVALID_REVNUM;
dir->ref_count = 1;
ctx->cur_dir = dir;
*new_dir = dir;
return SVN_NO_ERROR;
}
static svn_error_t *
create_file_baton(file_baton_t **new_file,
report_context_t *ctx,
const char *name,
apr_pool_t *scratch_pool)
{
dir_baton_t *parent = ctx->cur_dir;
apr_pool_t *file_pool;
file_baton_t *file;
file_pool = svn_pool_create(parent->pool);
file = apr_pcalloc(file_pool, sizeof(*file));
file->pool = file_pool;
file->parent_dir = parent;
parent->ref_count++;
file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
file->base_name = svn_relpath_basename(file->relpath, NULL);
file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath);
if (!file->repos_relpath)
file->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
file_pool);
file->base_rev = SVN_INVALID_REVNUM;
file->copyfrom_rev = SVN_INVALID_REVNUM;
*new_file = file;
ctx->cur_file = file;
return SVN_NO_ERROR;
}
#define REQS_PER_CONN 8
static svn_error_t *
open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
{
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;
}
static svn_ra_serf__connection_t *
get_best_connection(report_context_t *ctx)
{
svn_ra_serf__connection_t *conn;
int first_conn = 1;
if (ctx->report_received && (ctx->sess->max_connections > 2))
first_conn = 0;
if (ctx->sess->num_conns - first_conn == 1)
{
conn = ctx->sess->conns[first_conn];
}
else
{
#if SERF_VERSION_AT_LEAST(1, 4, 0)
int i, best_conn = first_conn;
unsigned int min = INT_MAX;
for (i = first_conn; i < ctx->sess->num_conns; i++)
{
serf_connection_t *sc = ctx->sess->conns[i]->conn;
unsigned int pending = serf_connection_pending_requests(sc);
if (pending < min)
{
min = pending;
best_conn = i;
}
}
conn = ctx->sess->conns[best_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;
#endif
}
return conn;
}
static svn_error_t*
ensure_dir_opened(dir_baton_t *dir,
apr_pool_t *scratch_pool)
{
report_context_t *ctx = dir->ctx;
if (dir->dir_opened)
return SVN_NO_ERROR;
if (dir->base_name[0] == '\0')
{
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, scratch_pool));
}
SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
dir->pool,
&dir->dir_baton));
}
else
{
SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
if (SVN_IS_VALID_REVNUM(dir->base_rev))
{
SVN_ERR(ctx->editor->open_directory(dir->relpath,
dir->parent_dir->dir_baton,
dir->base_rev,
dir->pool,
&dir->dir_baton));
}
else
{
SVN_ERR(ctx->editor->add_directory(dir->relpath,
dir->parent_dir->dir_baton,
dir->copyfrom_path,
dir->copyfrom_rev,
dir->pool,
&dir->dir_baton));
}
}
dir->dir_opened = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
maybe_close_dir(dir_baton_t *dir)
{
apr_pool_t *scratch_pool = dir->pool;
dir_baton_t *parent = dir->parent_dir;
report_context_t *ctx = dir->ctx;
if (--dir->ref_count)
{
return SVN_NO_ERROR;
}
SVN_ERR(ensure_dir_opened(dir, dir->pool));
if (dir->remove_props)
{
apr_hash_index_t *hi;
for (hi = apr_hash_first(scratch_pool, dir->remove_props);
hi;
hi = apr_hash_next(hi))
{
SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
apr_hash_this_key(hi),
NULL ,
scratch_pool));
}
}
SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
svn_pool_destroy(dir->pool );
if (parent)
return svn_error_trace(maybe_close_dir(parent));
else
return SVN_NO_ERROR;
}
static svn_error_t *
ensure_file_opened(file_baton_t *file,
apr_pool_t *scratch_pool)
{
const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
if (file->file_opened)
return SVN_NO_ERROR;
SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
if (SVN_IS_VALID_REVNUM(file->base_rev))
{
SVN_ERR(editor->open_file(file->relpath,
file->parent_dir->dir_baton,
file->base_rev,
file->pool,
&file->file_baton));
}
else
{
SVN_ERR(editor->add_file(file->relpath,
file->parent_dir->dir_baton,
file->copyfrom_path,
file->copyfrom_rev,
file->pool,
&file->file_baton));
}
file->file_opened = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
headers_fetch(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool ,
apr_pool_t *scratch_pool)
{
fetch_ctx_t *fetch_ctx = baton;
if (fetch_ctx->delta_base)
{
serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
fetch_ctx->delta_base);
svn_ra_serf__setup_svndiff_accept_encoding(headers, fetch_ctx->session);
}
else if (fetch_ctx->session->using_compression != svn_tristate_false)
{
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)
{
fetch_ctx_t *fetch_ctx = baton;
if (!response)
{
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;
}
SVN_ERR_MALFUNCTION();
}
static svn_error_t *
open_file_txdelta(file_baton_t *file,
apr_pool_t *scratch_pool)
{
const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
SVN_ERR_ASSERT(file->txdelta == NULL);
SVN_ERR(ensure_file_opened(file, scratch_pool));
SVN_ERR(editor->apply_textdelta(file->file_baton,
svn_checksum_to_cstring(
file->base_md5_checksum,
scratch_pool),
file->pool,
&file->txdelta,
&file->txdelta_baton));
return SVN_NO_ERROR;
}
static svn_error_t *
close_file(file_baton_t *file,
apr_pool_t *scratch_pool)
{
dir_baton_t *parent_dir = file->parent_dir;
report_context_t *ctx = parent_dir->ctx;
SVN_ERR(ensure_file_opened(file, scratch_pool));
if (file->remove_props)
{
apr_hash_index_t *hi;
for (hi = apr_hash_first(scratch_pool, file->remove_props);
hi;
hi = apr_hash_next(hi))
{
SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
apr_hash_this_key(hi),
NULL ,
scratch_pool));
}
}
if (!ctx->add_props_included
&& file->lock_token && !file->found_lock_prop
&& SVN_IS_VALID_REVNUM(file->base_rev) )
{
SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
SVN_PROP_ENTRY_LOCK_TOKEN,
NULL,
scratch_pool));
}
if (file->url)
{
SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
SVN_RA_SERF__WC_CHECKED_IN_URL,
svn_string_create(file->url,
scratch_pool),
scratch_pool));
}
SVN_ERR(ctx->editor->close_file(file->file_baton,
svn_checksum_to_cstring(
file->final_md5_checksum,
scratch_pool),
scratch_pool));
svn_pool_destroy(file->pool);
SVN_ERR(maybe_close_dir(parent_dir));
return SVN_NO_ERROR;
}
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;
fetch_ctx_t *fetch_ctx = handler_baton;
file_baton_t *file = fetch_ctx->file;
SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
if (!fetch_ctx->read_headers)
{
serf_bucket_t *hdrs;
const char *val;
if (fetch_ctx->handler->sline.code != 200)
{
fetch_ctx->handler->discard_body = TRUE;
return SVN_NO_ERROR;
}
hdrs = serf_bucket_response_get_headers(response);
val = serf_bucket_headers_get(hdrs, "Content-Type");
if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
{
fetch_ctx->result_stream =
svn_txdelta_parse_svndiff(file->txdelta,
file->txdelta_baton,
TRUE, file->pool);
val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
if (val && fetch_ctx->delta_base == NULL)
{
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("GET request returned unexpected "
"delta base: %s"), val);
}
else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
{
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("GET request returned unexpected "
"delta base: %s"), val);
}
}
else
{
fetch_ctx->result_stream = NULL;
}
fetch_ctx->read_headers = TRUE;
}
while (TRUE)
{
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;
if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
{
if (APR_STATUS_IS_EOF(status))
{
SVN_ERR_MALFUNCTION();
}
if (status )
return svn_ra_serf__wrap_err(status, NULL);
continue;
}
fetch_ctx->aborted_read = FALSE;
skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
data += skip;
len -= (apr_size_t)skip;
}
if (fetch_ctx->result_stream)
SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
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;
SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
}
if (APR_STATUS_IS_EOF(status))
{
if (fetch_ctx->result_stream)
SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
else
SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
}
if (status)
return svn_ra_serf__wrap_err(status, NULL);
}
}
static svn_error_t *
set_file_props(void *baton,
const char *path,
const char *ns,
const char *name,
const svn_string_t *val,
apr_pool_t *scratch_pool)
{
file_baton_t *file = baton;
report_context_t *ctx = file->parent_dir->ctx;
const char *prop_name;
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
if (!prop_name)
{
if (file->lock_token
&& !file->found_lock_prop
&& val
&& strcmp(ns, "DAV:") == 0
&& strcmp(name, "lockdiscovery") == 0)
{
char *new_lock;
new_lock = apr_pstrdup(scratch_pool, val->data);
apr_collapse_spaces(new_lock, new_lock);
if (new_lock[0] != '\0')
file->found_lock_prop = TRUE;
}
return SVN_NO_ERROR;
}
SVN_ERR(ensure_file_opened(file, scratch_pool));
SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
prop_name, val,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
file_props_done(serf_request_t *request,
void *baton,
apr_pool_t *scratch_pool)
{
file_baton_t *file = baton;
svn_ra_serf__handler_t *handler = file->propfind_handler;
if (handler->server_error)
return svn_error_trace(svn_ra_serf__server_error_create(handler,
scratch_pool));
if (handler->sline.code != 207)
return svn_error_trace(svn_ra_serf__unexpected_status(handler));
file->parent_dir->ctx->num_active_propfinds--;
file->fetch_props = FALSE;
if (file->fetch_file)
return SVN_NO_ERROR;
return svn_error_trace(close_file(file, scratch_pool));
}
static svn_error_t *
file_fetch_done(serf_request_t *request,
void *baton,
apr_pool_t *scratch_pool)
{
fetch_ctx_t *fetch_ctx = baton;
file_baton_t *file = fetch_ctx->file;
svn_ra_serf__handler_t *handler = fetch_ctx->handler;
if (handler->server_error)
return svn_error_trace(svn_ra_serf__server_error_create(handler,
scratch_pool));
if (handler->sline.code != 200)
return svn_error_trace(svn_ra_serf__unexpected_status(handler));
file->parent_dir->ctx->num_active_fetches--;
file->fetch_file = FALSE;
if (file->fetch_props)
return SVN_NO_ERROR;
return svn_error_trace(close_file(file, scratch_pool));
}
static svn_error_t *
fetch_for_file(file_baton_t *file,
apr_pool_t *scratch_pool)
{
report_context_t *ctx = file->parent_dir->ctx;
svn_ra_serf__connection_t *conn;
svn_ra_serf__handler_t *handler;
if (ctx->sess->num_conns < ctx->sess->max_connections)
SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
ctx->num_active_propfinds));
conn = get_best_connection(ctx);
if (file->fetch_file)
{
SVN_ERR(open_file_txdelta(file, scratch_pool));
if (!ctx->text_deltas
|| file->txdelta == svn_delta_noop_window_handler)
{
SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
file->fetch_file = FALSE;
}
if (file->fetch_file
&& file->final_sha1_checksum
&& ctx->sess->wc_callbacks->get_wc_contents)
{
svn_error_t *err;
svn_stream_t *cached_contents = NULL;
err = ctx->sess->wc_callbacks->get_wc_contents(
ctx->sess->wc_callback_baton,
&cached_contents,
file->final_sha1_checksum,
scratch_pool);
if (err || !cached_contents)
svn_error_clear(err);
else
{
SVN_ERR(svn_txdelta_send_stream(cached_contents,
file->txdelta,
file->txdelta_baton,
NULL, scratch_pool));
SVN_ERR(svn_stream_close(cached_contents));
file->fetch_file = FALSE;
}
}
if (file->fetch_file)
{
fetch_ctx_t *fetch_ctx;
SVN_ERR_ASSERT(file->url && file->repos_relpath);
fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
fetch_ctx->file = file;
fetch_ctx->session = ctx->sess;
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
{
if (SVN_IS_VALID_REVNUM(file->base_rev))
{
fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
ctx->sess->rev_root_stub,
file->base_rev,
svn_path_uri_encode(
file->repos_relpath,
scratch_pool));
}
else if (file->copyfrom_path)
{
SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
ctx->sess->rev_root_stub,
file->copyfrom_rev,
svn_path_uri_encode(
file->copyfrom_path+1,
scratch_pool));
}
}
else if (ctx->sess->wc_callbacks->get_wc_prop)
{
const svn_string_t *value = NULL;
SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
ctx->sess->wc_callback_baton,
file->relpath,
SVN_RA_SERF__WC_CHECKED_IN_URL,
&value, scratch_pool));
fetch_ctx->delta_base = value
? apr_pstrdup(file->pool, value->data)
: NULL;
}
handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
handler->method = "GET";
handler->path = file->url;
handler->conn = conn;
handler->custom_accept_encoding = TRUE;
handler->no_dav_headers = 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;
handler->done_delegate = file_fetch_done;
handler->done_delegate_baton = fetch_ctx;
fetch_ctx->handler = handler;
svn_ra_serf__request_create(handler);
ctx->num_active_fetches++;
}
}
if (file->fetch_props)
{
SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
ctx->sess, file->url,
ctx->target_rev, "0",
all_props,
set_file_props, file,
file->pool));
file->propfind_handler->conn = conn;
file->propfind_handler->done_delegate = file_props_done;
file->propfind_handler->done_delegate_baton = file;
svn_ra_serf__request_create(file->propfind_handler);
ctx->num_active_propfinds++;
}
if (file->fetch_props || file->fetch_file)
return SVN_NO_ERROR;
return svn_error_trace(close_file(file, scratch_pool));
}
static svn_error_t *
set_dir_prop(void *baton,
const char *path,
const char *ns,
const char *name,
const svn_string_t *val,
apr_pool_t *scratch_pool)
{
dir_baton_t *dir = baton;
report_context_t *ctx = dir->ctx;
const char *prop_name;
prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
if (prop_name == NULL)
return SVN_NO_ERROR;
SVN_ERR(ensure_dir_opened(dir, scratch_pool));
SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
prop_name, val,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
dir_props_done(serf_request_t *request,
void *baton,
apr_pool_t *scratch_pool)
{
dir_baton_t *dir = baton;
svn_ra_serf__handler_t *handler = dir->propfind_handler;
if (handler->server_error)
return svn_ra_serf__server_error_create(handler, scratch_pool);
if (handler->sline.code != 207)
return svn_error_trace(svn_ra_serf__unexpected_status(handler));
dir->ctx->num_active_propfinds--;
return svn_error_trace(maybe_close_dir(dir));
}
static svn_error_t *
fetch_for_dir(dir_baton_t *dir,
apr_pool_t *scratch)
{
report_context_t *ctx = dir->ctx;
svn_ra_serf__connection_t *conn;
if (ctx->sess->num_conns < ctx->sess->max_connections)
SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
ctx->num_active_propfinds));
conn = get_best_connection(ctx);
if (dir->fetch_props)
{
SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
ctx->sess, dir->url,
ctx->target_rev, "0",
all_props,
set_dir_prop, dir,
dir->pool));
dir->propfind_handler->conn = conn;
dir->propfind_handler->done_delegate = dir_props_done;
dir->propfind_handler->done_delegate_baton = dir;
svn_ra_serf__request_create(dir->propfind_handler);
ctx->num_active_propfinds++;
}
else
SVN_ERR_MALFUNCTION();
return SVN_NO_ERROR;
}
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;
apr_hash_t *attrs;
switch (entered_state)
{
case UPDATE_REPORT:
{
const char *val;
attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
val = svn_hash_gets(attrs, "inline-props");
if (val && (strcmp(val, "true") == 0))
ctx->add_props_included = TRUE;
val = svn_hash_gets(attrs, "send-all");
if (val && (strcmp(val, "true") == 0))
{
ctx->send_all_mode = TRUE;
ctx->add_props_included = TRUE;
}
}
break;
case OPEN_DIR:
case ADD_DIR:
{
dir_baton_t *dir;
const char *name;
attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
name = svn_hash_gets(attrs, "name");
if (!name)
name = "";
SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
if (entered_state == OPEN_DIR)
{
apr_int64_t base_rev;
SVN_ERR(svn_cstring_atoi64(&base_rev,
svn_hash_gets(attrs, "rev")));
dir->base_rev = (svn_revnum_t)base_rev;
}
else
{
dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
if (dir->copyfrom_path)
{
apr_int64_t copyfrom_rev;
const char *copyfrom_rev_str;
dir->copyfrom_path = svn_fspath__canonicalize(
dir->copyfrom_path,
dir->pool);
copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
if (!copyfrom_rev_str)
return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
NULL,
_("Missing '%s' attribute"),
"copyfrom-rev");
SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
}
if (! ctx->add_props_included)
dir->fetch_props = TRUE;
}
}
break;
case OPEN_FILE:
case ADD_FILE:
{
file_baton_t *file;
attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
scratch_pool));
if (entered_state == OPEN_FILE)
{
apr_int64_t base_rev;
SVN_ERR(svn_cstring_atoi64(&base_rev,
svn_hash_gets(attrs, "rev")));
file->base_rev = (svn_revnum_t)base_rev;
}
else
{
const char *sha1_checksum;
file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
if (file->copyfrom_path)
{
apr_int64_t copyfrom_rev;
const char *copyfrom_rev_str;
file->copyfrom_path = svn_fspath__canonicalize(
file->copyfrom_path,
file->pool);
copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
if (!copyfrom_rev_str)
return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
NULL,
_("Missing '%s' attribute"),
"copyfrom-rev");
SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str));
file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
}
sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
if (sha1_checksum)
{
SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
svn_checksum_sha1,
sha1_checksum,
file->pool));
}
if (! ctx->send_all_mode)
file->fetch_file = TRUE;
if (! ctx->add_props_included)
file->fetch_props = TRUE;
}
}
break;
case TXDELTA:
{
file_baton_t *file = ctx->cur_file;
const char *base_checksum;
if (! ctx->send_all_mode)
break;
file->fetch_file = FALSE;
attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
base_checksum = svn_hash_gets(attrs, "base-checksum");
if (base_checksum)
SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
svn_checksum_md5, base_checksum,
file->pool));
SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
{
svn_stream_t *decoder;
decoder = svn_txdelta_parse_svndiff(file->txdelta,
file->txdelta_baton,
TRUE ,
file->pool);
file->txdelta_stream = svn_base64_decode(decoder, file->pool);
}
}
break;
case FETCH_PROPS:
{
if (ctx->cur_file)
ctx->cur_file->fetch_props = TRUE;
else if (ctx->cur_dir)
ctx->cur_dir->fetch_props = TRUE;
}
break;
}
return SVN_NO_ERROR;
}
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;
switch (leaving_state)
{
case UPDATE_REPORT:
ctx->done = TRUE;
break;
case TARGET_REVISION:
{
const char *revstr = svn_hash_gets(attrs, "rev");
apr_int64_t rev;
SVN_ERR(svn_cstring_atoi64(&rev, revstr));
SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
(svn_revnum_t)rev,
scratch_pool));
}
break;
case CHECKED_IN_HREF:
if (ctx->cur_file)
ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
else
ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
break;
case SET_PROP:
case REMOVE_PROP:
{
const char *name = svn_hash_gets(attrs, "name");
const char *encoding;
const svn_string_t *value;
if (leaving_state == REMOVE_PROP)
value = NULL;
else if ((encoding = svn_hash_gets(attrs, "encoding")))
{
if (strcmp(encoding, "base64") != 0)
return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
_("Got unrecognized encoding '%s'"),
encoding);
value = svn_base64_decode_string(cdata, scratch_pool);
}
else
value = cdata;
if (ctx->cur_file)
{
file_baton_t *file = ctx->cur_file;
if (value
|| ctx->add_props_included
|| SVN_IS_VALID_REVNUM(file->base_rev))
{
SVN_ERR(ensure_file_opened(file, scratch_pool));
SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
name,
value,
scratch_pool));
}
else
{
if (!file->remove_props)
file->remove_props = apr_hash_make(file->pool);
svn_hash_sets(file->remove_props,
apr_pstrdup(file->pool, name),
"");
}
}
else
{
dir_baton_t *dir = ctx->cur_dir;
if (value
|| ctx->add_props_included
|| SVN_IS_VALID_REVNUM(dir->base_rev))
{
SVN_ERR(ensure_dir_opened(dir, scratch_pool));
SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
name,
value,
scratch_pool));
}
else
{
if (!dir->remove_props)
dir->remove_props = apr_hash_make(dir->pool);
svn_hash_sets(dir->remove_props,
apr_pstrdup(dir->pool, name),
"");
}
}
}
break;
case OPEN_DIR:
case ADD_DIR:
{
dir_baton_t *dir = ctx->cur_dir;
ctx->cur_dir = ctx->cur_dir->parent_dir;
if (dir->fetch_props && ! dir->url)
{
return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
_("The REPORT response did not "
"include the requested checked-in "
"value"));
}
if (!dir->fetch_props)
{
SVN_ERR(maybe_close_dir(dir));
break;
}
else
{
SVN_ERR(fetch_for_dir(dir, scratch_pool));
}
}
break;
case OPEN_FILE:
case ADD_FILE:
{
file_baton_t *file = ctx->cur_file;
ctx->cur_file = NULL;
if ((file->fetch_file || file->fetch_props) && ! file->url)
{
return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
_("The REPORT response did not "
"include the requested checked-in "
"value"));
}
if (! file->fetch_file && ! file->fetch_props)
{
SVN_ERR(close_file(file, scratch_pool));
break;
}
else
{
SVN_ERR(fetch_for_file(file, scratch_pool));
}
}
break;
case MD5_CHECKSUM:
SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
svn_checksum_md5,
cdata->data,
ctx->cur_file->pool));
break;
case FETCH_FILE:
{
file_baton_t *file = ctx->cur_file;
const char *base_checksum = svn_hash_gets(attrs, "base-checksum");
const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
if (base_checksum)
SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
svn_checksum_md5, base_checksum,
file->pool));
if (sha1_checksum && !file->final_sha1_checksum)
SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
svn_checksum_sha1,
sha1_checksum,
file->pool));
if (! ctx->send_all_mode)
file->fetch_file = TRUE;
}
break;
case DELETE_ENTRY:
{
const char *name = svn_hash_gets(attrs, "name");
const char *revstr;
apr_int64_t delete_rev;
SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
revstr = svn_hash_gets(attrs, "rev");
if (revstr)
SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
else
delete_rev = SVN_INVALID_REVNUM;
SVN_ERR(ctx->editor->delete_entry(
svn_relpath_join(ctx->cur_dir->relpath,
name,
scratch_pool),
(svn_revnum_t)delete_rev,
ctx->cur_dir->dir_baton,
scratch_pool));
}
break;
case ABSENT_DIR:
{
const char *name = svn_hash_gets(attrs, "name");
SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
SVN_ERR(ctx->editor->absent_directory(
svn_relpath_join(ctx->cur_dir->relpath,
name, scratch_pool),
ctx->cur_dir->dir_baton,
scratch_pool));
}
break;
case ABSENT_FILE:
{
const char *name = svn_hash_gets(attrs, "name");
SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
SVN_ERR(ctx->editor->absent_file(
svn_relpath_join(ctx->cur_dir->relpath,
name, scratch_pool),
ctx->cur_dir->dir_baton,
scratch_pool));
}
break;
case TXDELTA:
{
file_baton_t *file = ctx->cur_file;
if (file->txdelta_stream)
{
SVN_ERR(svn_stream_close(file->txdelta_stream));
file->txdelta_stream = NULL;
}
}
break;
case VERSION_NAME:
case CREATIONDATE:
case CREATOR_DISPLAYNAME:
{
const char *propname;
if (ctx->cur_file)
SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool));
else if (ctx->cur_dir)
SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
else
break;
switch (leaving_state)
{
case VERSION_NAME:
propname = SVN_PROP_ENTRY_COMMITTED_REV;
break;
case CREATIONDATE:
propname = SVN_PROP_ENTRY_COMMITTED_DATE;
break;
case CREATOR_DISPLAYNAME:
propname = SVN_PROP_ENTRY_LAST_AUTHOR;
break;
default:
SVN_ERR_MALFUNCTION();
}
if (ctx->cur_file)
SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
propname, cdata,
scratch_pool));
else
SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
propname, cdata,
scratch_pool));
}
break;
}
return SVN_NO_ERROR;
}
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;
if (current_state == TXDELTA && ctx->cur_file
&& ctx->cur_file->txdelta_stream)
{
SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
}
return SVN_NO_ERROR;
}
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,
SVN_VA_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,
SVN_VA_NULL);
svn_xml_escape_cdata_cstring(&buf, path, pool);
svn_xml_make_close_tag(&buf, pool, "S:entry");
SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
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_stream_write(report->body_template, buf->data, &buf->len));
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;
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, pool));
SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool));
link = apr_pstrcat(pool, "/", link, SVN_VA_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,
SVN_VA_NULL);
svn_xml_escape_cdata_cstring(&buf, path, pool);
svn_xml_make_close_tag(&buf, pool, "S:entry");
SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
path = apr_pstrdup(report->pool, path);
link = apr_pstrdup(report->pool, link + 1);
svn_hash_sets(report->switched_paths, path, link);
if (!path[0] && report->update_target[0])
{
svn_hash_sets(report->switched_paths, report->update_target, link);
}
return APR_SUCCESS;
}
static svn_error_t *
setup_update_report_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool ,
apr_pool_t *scratch_pool)
{
report_context_t *report = baton;
svn_ra_serf__setup_svndiff_accept_encoding(headers, report->sess);
return SVN_NO_ERROR;
}
typedef struct update_delay_baton_t
{
report_context_t *report;
svn_spillbuf_t *spillbuf;
svn_ra_serf__response_handler_t inner_handler;
void *inner_handler_baton;
} update_delay_baton_t;
static svn_error_t *
process_buffer(update_delay_baton_t *udb,
serf_request_t *request,
const void *data,
apr_size_t len,
svn_boolean_t at_eof,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
serf_bucket_t *tmp_bucket;
svn_error_t *err;
if (at_eof)
{
tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
alloc);
}
else
{
tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
alloc);
}
err = udb->inner_handler(request, tmp_bucket,
udb->inner_handler_baton, pool);
serf_bucket_destroy(tmp_bucket);
return svn_error_trace(err);
}
static svn_error_t *
update_delay_handler(serf_request_t *request,
serf_bucket_t *response,
void *handler_baton,
apr_pool_t *scratch_pool)
{
update_delay_baton_t *udb = handler_baton;
apr_status_t status;
apr_pool_t *iterpool = NULL;
if (! udb->spillbuf)
{
if (udb->report->send_all_mode)
{
return svn_error_trace(udb->inner_handler(request, response,
udb->inner_handler_baton,
scratch_pool));
}
while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
< REQUEST_COUNT_TO_RESUME)
{
const char *data;
apr_size_t len;
svn_boolean_t at_eof = FALSE;
svn_error_t *err;
status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
return svn_ra_serf__wrap_err(status, NULL);
else if (APR_STATUS_IS_EOF(status))
udb->report->report_received = at_eof = TRUE;
if (!iterpool)
iterpool = svn_pool_create(scratch_pool);
else
svn_pool_clear(iterpool);
if (len == 0 && !at_eof)
return svn_ra_serf__wrap_err(status, NULL);
err = process_buffer(udb, request, data, len, at_eof,
serf_request_get_alloc(request),
iterpool);
if (err && SERF_BUCKET_READ_ERROR(err->apr_err))
return svn_error_trace(err);
else if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
{
svn_error_clear(err);
}
else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
{
svn_pool_destroy(iterpool);
return svn_error_trace(err);
}
else
{
return svn_error_trace(err);
}
}
udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
SPILLBUF_MAXBUFFSIZE,
udb->report->pool);
}
do
{
const char *data;
apr_size_t len;
status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
if (!SERF_BUCKET_READ_ERROR(status))
SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
}
while (status == APR_SUCCESS);
if (APR_STATUS_IS_EOF(status))
udb->report->report_received = TRUE;
if (status)
return svn_ra_serf__wrap_err(status, NULL);
else
return SVN_NO_ERROR;
}
static svn_error_t *
process_pending(update_delay_baton_t *udb,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = NULL;
serf_bucket_alloc_t *alloc = NULL;
while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
< REQUEST_COUNT_TO_RESUME)
{
const char *data;
apr_size_t len;
svn_boolean_t at_eof;
svn_error_t *err;
if (!iterpool)
{
iterpool = svn_pool_create(scratch_pool);
alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
}
else
svn_pool_clear(iterpool);
SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
if (data == NULL && !udb->report->report_received)
break;
else if (data == NULL)
at_eof = TRUE;
else
at_eof = FALSE;
err = process_buffer(udb, NULL , data, len,
at_eof, alloc, iterpool);
if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
{
svn_error_clear(err);
}
else if (err && APR_STATUS_IS_EOF(err->apr_err))
{
svn_error_clear(err);
svn_pool_destroy(iterpool);
udb->spillbuf = NULL;
return SVN_NO_ERROR;
}
else if (err)
return svn_error_trace(err);
}
if (iterpool)
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
static svn_error_t *
process_editor_report(report_context_t *ctx,
svn_ra_serf__handler_t *handler,
apr_pool_t *scratch_pool)
{
svn_ra_serf__session_t *sess = ctx->sess;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_interval_time_t waittime_left = sess->timeout;
update_delay_baton_t *ud;
ud = apr_pcalloc(scratch_pool, sizeof(*ud));
ud->report = ctx;
ud->inner_handler = handler->response_handler;
ud->inner_handler_baton = handler->response_baton;
handler->response_handler = update_delay_handler;
handler->response_baton = ud;
SVN_ERR(open_connection_if_needed(sess, 0));
sess->cur_conn = 1;
while (!handler->done
|| ctx->num_active_fetches
|| ctx->num_active_propfinds
|| !ctx->done)
{
svn_error_t *err;
int i;
svn_pool_clear(iterpool);
err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
if (handler->done && handler->server_error)
{
svn_error_clear(err);
err = svn_ra_serf__server_error_create(handler, iterpool);
SVN_ERR_ASSERT(err != NULL);
}
SVN_ERR(err);
if (ud->spillbuf)
SVN_ERR(process_pending(ud, iterpool));
for (i = 0; i < sess->num_conns; i++)
{
serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
}
}
svn_pool_clear(iterpool);
if (ctx->done)
SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
else
return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
_("Missing update-report close tag"));
svn_pool_destroy(iterpool);
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_context_t *xmlctx;
const char *report_target;
svn_stringbuf_t *buf = NULL;
apr_pool_t *scratch_pool = svn_pool_create(pool);
svn_error_t *err;
svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report");
SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
SVN_ERR(svn_stream_close(report->body_template));
SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool));
xmlctx = svn_ra_serf__xml_context_create(update_ttable,
update_opened, update_closed,
update_cdata,
report,
scratch_pool);
handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
scratch_pool);
svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
&handler->body_delegate_baton,
report->body);
handler->method = "REPORT";
handler->path = report_target;
handler->body_type = "text/xml";
handler->custom_accept_encoding = TRUE;
handler->header_delegate = setup_update_report_headers;
handler->header_delegate_baton = report;
svn_ra_serf__request_create(handler);
err = process_editor_report(report, handler, scratch_pool);
if (err)
{
err = svn_error_trace(err);
err = svn_error_compose_create(
err,
svn_error_trace(
report->editor->abort_edit(report->editor_baton,
scratch_pool)));
}
svn_pool_destroy(scratch_pool);
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
return SVN_NO_ERROR;
}
static const svn_ra_reporter3_t ra_serf_reporter = {
set_path,
delete_path,
link_path,
finish_report,
abort_report
};
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));
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,
result_pool));
update_editor = filter_editor;
update_baton = filter_baton;
}
report = apr_pcalloc(result_pool, sizeof(*report));
report->pool = result_pool;
report->sess = sess;
report->target_rev = revision;
report->ignore_ancestry = ignore_ancestry;
report->send_copyfrom_args = send_copyfrom_args;
report->text_deltas = text_deltas;
report->switched_paths = apr_hash_make(report->pool);
report->source = src_path;
report->destination = dest_path;
report->update_target = update_target;
report->editor = update_editor;
report->editor_baton = update_baton;
report->done = FALSE;
*reporter = &ra_serf_reporter;
*report_baton = report;
report->body =
svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
report->pool);
report->body_template = svn_ra_serf__request_body_get_stream(report->body);
if (sess->bulk_updates == svn_tristate_true)
{
use_bulk_updates = TRUE;
}
else if (sess->bulk_updates == svn_tristate_false)
{
use_bulk_updates = FALSE;
}
else
{
if (sess->server_allows_bulk)
{
if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
{
use_bulk_updates = FALSE;
}
else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
{
use_bulk_updates = TRUE;
}
else
{
use_bulk_updates = FALSE;
}
}
else
{
if (sess->supports_inline_props)
{
use_bulk_updates = FALSE;
}
else
{
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",
SVN_VA_NULL);
}
else
{
svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
"S:update-report",
"xmlns:S", SVN_XML_NAMESPACE,
SVN_VA_NULL);
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);
}
if (depth == svn_depth_files || depth == svn_depth_empty)
{
make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
}
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_stream_write(report->body_template, buf->data, &buf->len));
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 ,
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 ,
send_copyfrom_args,
switch_editor, switch_baton,
result_pool, scratch_pool);
}