/*
* commit.c : entry point for commit RA functions for ra_serf
*
* ====================================================================
* Copyright (c) 2006 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <apr_uri.h>
#include <expat.h>
#include <serf.h>
#include "svn_pools.h"
#include "svn_ra.h"
#include "svn_dav.h"
#include "svn_xml.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_config.h"
#include "svn_delta.h"
#include "svn_base64.h"
#include "svn_version.h"
#include "svn_path.h"
#include "svn_private_config.h"
#include "ra_serf.h"
/* Structure associated with a CHECKOUT request. */
typedef struct {
apr_pool_t *pool;
const char *activity_url;
apr_size_t activity_url_len;
const char *checkout_url;
const char *resource_url;
svn_ra_serf__simple_request_context_t progress;
} checkout_context_t;
/* Baton passed back with the commit editor. */
typedef struct {
/* Pool for our commit. */
apr_pool_t *pool;
svn_ra_serf__session_t *session;
svn_ra_serf__connection_t *conn;
svn_string_t *log_msg;
svn_commit_callback2_t callback;
void *callback_baton;
apr_hash_t *lock_tokens;
svn_boolean_t keep_locks;
const char *uuid;
const char *activity_url;
apr_size_t activity_url_len;
/* The checkout for the baseline. */
checkout_context_t *baseline;
/* The checked-in root to base CHECKOUTs from */
const char *checked_in_url;
/* The root baseline collection */
const char *baseline_url;
/* Deleted files - so we can detect delete+add (replace) ops. */
apr_hash_t *deleted_entries;
/* Copied entries - so we do not checkout these resources. */
apr_hash_t *copied_entries;
} commit_context_t;
/* Structure associated with a PROPPATCH request. */
typedef struct {
apr_pool_t *pool;
const char *name;
const char *path;
commit_context_t *commit;
/* Changed and removed properties. */
apr_hash_t *changed_props;
apr_hash_t *removed_props;
svn_ra_serf__simple_request_context_t progress;
} proppatch_context_t;
typedef struct {
const char *path;
svn_revnum_t revision;
const char *lock_token;
apr_hash_t *lock_token_hash;
svn_boolean_t keep_locks;
svn_ra_serf__simple_request_context_t progress;
} delete_context_t;
/* Represents a directory. */
typedef struct dir_context_t {
/* Pool for our directory. */
apr_pool_t *pool;
/* The root commit we're in progress for. */
commit_context_t *commit;
/* The checked out context for this directory.
*
* May be NULL; if so call checkout_dir() first.
*/
checkout_context_t *checkout;
/* Our URL to CHECKOUT */
const char *checked_in_url;
/* How many pending changes we have left in this directory. */
unsigned int ref_count;
/* Our parent */
struct dir_context_t *parent_dir;
/* The directory name; if NULL, we're the 'root' */
const char *name;
/* The base revision of the dir. */
svn_revnum_t base_revision;
const char *copy_path;
svn_revnum_t copy_revision;
/* Changed and removed properties */
apr_hash_t *changed_props;
apr_hash_t *removed_props;
} dir_context_t;
/* Represents a file to be committed. */
typedef struct {
/* Pool for our file. */
apr_pool_t *pool;
/* The root commit we're in progress for. */
commit_context_t *commit;
dir_context_t *parent_dir;
const char *name;
/* The checked out context for this file. */
checkout_context_t *checkout;
/* The base revision of the file. */
svn_revnum_t base_revision;
/* Copy path and revision */
const char *copy_path;
svn_revnum_t copy_revision;
/* stream */
svn_stream_t *stream;
/* Temporary file containing the svndiff. */
apr_file_t *svndiff;
/* Our base checksum as reported by the WC. */
const char *base_checksum;
/* Our resulting checksum as reported by the WC. */
const char *result_checksum;
/* Changed and removed properties. */
apr_hash_t *changed_props;
apr_hash_t *removed_props;
/* URL to PUT the file at. */
const char *put_url;
} file_context_t;
/* Setup routines and handlers for various requests we'll invoke. */
static svn_error_t *
return_response_err(svn_ra_serf__handler_t *handler,
svn_ra_serf__simple_request_context_t *ctx)
{
SVN_ERR(ctx->server_error.error);
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
"%s of '%s': %d %s",
handler->method, handler->path,
ctx->status, ctx->reason);
}
#define CHECKOUT_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><D:checkout xmlns:D=\"DAV:\"><D:activity-set><D:href>"
#define CHECKOUT_TRAILER "</D:href></D:activity-set></D:checkout>"
static serf_bucket_t *
create_checkout_body(void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
checkout_context_t *ctx = baton;
serf_bucket_t *body_bkt, *tmp_bkt;
body_bkt = serf_bucket_aggregate_create(alloc);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(CHECKOUT_HEADER,
sizeof(CHECKOUT_HEADER) - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(ctx->activity_url,
ctx->activity_url_len,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(CHECKOUT_TRAILER,
sizeof(CHECKOUT_TRAILER) - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
return body_bkt;
}
static apr_status_t
handle_checkout(serf_request_t *request,
serf_bucket_t *response,
void *handler_baton,
apr_pool_t *pool)
{
checkout_context_t *ctx = handler_baton;
apr_status_t status;
status = svn_ra_serf__handle_status_only(request, response, &ctx->progress,
pool);
/* Get the resulting location. */
if (ctx->progress.done && ctx->progress.status == 201)
{
serf_bucket_t *hdrs;
apr_uri_t uri;
const char *location;
hdrs = serf_bucket_response_get_headers(response);
location = serf_bucket_headers_get(hdrs, "Location");
if (!location)
{
abort();
}
apr_uri_parse(pool, location, &uri);
ctx->resource_url = apr_pstrdup(ctx->pool, uri.path);
}
return status;
}
static svn_error_t *
checkout_dir(dir_context_t *dir)
{
checkout_context_t *checkout_ctx;
svn_ra_serf__handler_t *handler;
if (dir->checkout)
{
return SVN_NO_ERROR;
}
if (dir->parent_dir)
{
/* Is our parent a copy? If so, we're already implicitly checked out. */
if (apr_hash_get(dir->commit->copied_entries,
dir->parent_dir->name, APR_HASH_KEY_STRING))
{
/* Implicitly checkout this dir now. */
dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
dir->checkout->pool = dir->pool;
dir->checkout->activity_url = dir->commit->activity_url;
dir->checkout->activity_url_len = dir->commit->activity_url_len;
dir->checkout->resource_url =
svn_path_url_add_component(dir->parent_dir->checkout->resource_url,
svn_path_basename(dir->name, dir->pool),
dir->pool);
apr_hash_set(dir->commit->copied_entries,
apr_pstrdup(dir->commit->pool, dir->name),
APR_HASH_KEY_STRING, (void*)1);
return SVN_NO_ERROR;
}
}
/* Checkout our directory into the activity URL now. */
handler = apr_pcalloc(dir->pool, sizeof(*handler));
handler->session = dir->commit->session;
handler->conn = dir->commit->conn;
checkout_ctx = apr_pcalloc(dir->pool, sizeof(*checkout_ctx));
checkout_ctx->pool = dir->pool;
checkout_ctx->activity_url = dir->commit->activity_url;
checkout_ctx->activity_url_len = dir->commit->activity_url_len;
/* We could be called twice for the root: once to checkout the baseline;
* once to checkout the directory itself if we need to do so.
*/
if (!dir->parent_dir && !dir->commit->baseline)
{
checkout_ctx->checkout_url = dir->commit->baseline_url;
dir->commit->baseline = checkout_ctx;
}
else
{
checkout_ctx->checkout_url = dir->checked_in_url;
dir->checkout = checkout_ctx;
}
handler->body_delegate = create_checkout_body;
handler->body_delegate_baton = checkout_ctx;
handler->body_type = "text/xml";
handler->response_handler = handle_checkout;
handler->response_baton = checkout_ctx;
handler->method = "CHECKOUT";
handler->path = checkout_ctx->checkout_url;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&checkout_ctx->progress.done,
dir->commit->session,
dir->pool));
if (checkout_ctx->progress.status != 201)
{
if (checkout_ctx->progress.status == 404)
{
return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
return_response_err(handler,
&checkout_ctx->progress),
_("Path '%s' not present"),
dir->name);
}
return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
return_response_err(handler,
&checkout_ctx->progress),
_("Your file or directory '%s' is probably out-of-date"),
svn_path_local_style(dir->name, dir->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
checkout_file(file_context_t *file)
{
svn_ra_serf__handler_t *handler;
/* Checkout our file into the activity URL now. */
handler = apr_pcalloc(file->pool, sizeof(*handler));
handler->session = file->commit->session;
handler->conn = file->commit->conn;
file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
file->checkout->pool = file->pool;
file->checkout->activity_url = file->commit->activity_url;
file->checkout->activity_url_len = file->commit->activity_url_len;
/* Append our file name to the baseline to get the resulting checkout. */
file->checkout->checkout_url =
svn_path_url_add_component(file->commit->checked_in_url,
file->name, file->pool);
handler->body_delegate = create_checkout_body;
handler->body_delegate_baton = file->checkout;
handler->body_type = "text/xml";
handler->response_handler = handle_checkout;
handler->response_baton = file->checkout;
handler->method = "CHECKOUT";
handler->path = file->checkout->checkout_url;
svn_ra_serf__request_create(handler);
/* There's no need to wait here as we only need this when we start the
* PROPPATCH or PUT of the file.
*/
SVN_ERR(svn_ra_serf__context_run_wait(&file->checkout->progress.done,
file->commit->session,
file->pool));
if (file->checkout->progress.status != 201)
{
if (file->checkout->progress.status == 404)
{
return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
return_response_err(handler,
&file->checkout->progress),
_("Path '%s' not present"),
file->name);
}
return svn_error_createf(SVN_ERR_RA_DAV_PATH_NOT_FOUND,
return_response_err(handler,
&file->checkout->progress),
_("Your file or directory '%s' is probably out-of-date"),
svn_path_local_style(file->name, file->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
get_version_url(dir_context_t *dir)
{
svn_ra_serf__session_t *session = dir->commit->session;
const char *root_checkout;
if (dir->commit->session->wc_callbacks->get_wc_prop)
{
const svn_string_t *current_version;
SVN_ERR(session->wc_callbacks->get_wc_prop(session->wc_callback_baton,
dir->name,
SVN_RA_SERF__WC_CHECKED_IN_URL,
¤t_version, dir->pool));
if (current_version)
{
dir->checked_in_url = current_version->data;
return SVN_NO_ERROR;
}
}
if (dir->commit->checked_in_url)
{
root_checkout = dir->commit->checked_in_url;
}
else
{
svn_ra_serf__propfind_context_t *propfind_ctx;
apr_hash_t *props;
props = apr_hash_make(dir->pool);
propfind_ctx = NULL;
svn_ra_serf__deliver_props(&propfind_ctx, props, session,
dir->commit->conn, session->repos_url.path,
dir->base_revision, "0",
checked_in_props, FALSE, NULL, dir->pool);
SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, session, dir->pool));
root_checkout =
svn_ra_serf__get_ver_prop(props, session->repos_url.path,
dir->base_revision, "DAV:", "checked-in");
}
dir->checked_in_url = svn_path_url_add_component(root_checkout,
dir->name, dir->pool);
return SVN_NO_ERROR;
}
static svn_error_t *
proppatch_walker(void *baton,
const char *ns, apr_ssize_t ns_len,
const char *name, apr_ssize_t name_len,
const svn_string_t *val,
apr_pool_t *pool)
{
serf_bucket_t *body_bkt = baton;
serf_bucket_t *tmp_bkt;
serf_bucket_alloc_t *alloc;
svn_boolean_t binary_prop;
if (svn_xml_is_xml_safe(val->data, val->len))
{
binary_prop = FALSE;
}
else
{
binary_prop = TRUE;
}
alloc = body_bkt->allocator;
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<",
sizeof("<") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(name, name_len, alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
sizeof(" xmlns=\"") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(ns, ns_len, alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
if (binary_prop == TRUE)
{
tmp_bkt =
SERF_BUCKET_SIMPLE_STRING_LEN("\" V:encoding=\"base64",
sizeof("\" V:encoding=\"base64") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
}
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("\">",
sizeof("\">") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
if (binary_prop == TRUE)
{
val = svn_base64_encode_string(val, pool);
}
else
{
svn_stringbuf_t *prop_buf = svn_stringbuf_create("", pool);
svn_xml_escape_cdata_string(&prop_buf, val, pool);
val = svn_string_create_from_buf(prop_buf, pool);
}
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(val->data, val->len, alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</",
sizeof("</") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(name, name_len, alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(">",
sizeof(">") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
return SVN_NO_ERROR;
}
static apr_status_t
setup_proppatch_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
proppatch_context_t *proppatch = baton;
if (proppatch->name && proppatch->commit->lock_tokens)
{
const char *token;
token = apr_hash_get(proppatch->commit->lock_tokens, proppatch->name,
APR_HASH_KEY_STRING);
if (token)
{
const char *token_header;
token_header = apr_pstrcat(pool, "(<", token, ">)", NULL);
serf_bucket_headers_set(headers, "If", token_header);
}
}
return APR_SUCCESS;
}
#define PROPPATCH_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><D:propertyupdate xmlns:D=\"DAV:\" xmlns:V=\"" SVN_DAV_PROP_NS_DAV "\">"
#define PROPPATCH_TRAILER "</D:propertyupdate>"
static serf_bucket_t *
create_proppatch_body(void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
proppatch_context_t *ctx = baton;
serf_bucket_t *body_bkt, *tmp_bkt;
body_bkt = serf_bucket_aggregate_create(alloc);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(PROPPATCH_HEADER,
sizeof(PROPPATCH_HEADER) - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
if (apr_hash_count(ctx->changed_props) > 0)
{
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:set>",
sizeof("<D:set>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
sizeof("<D:prop>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
SVN_INVALID_REVNUM,
proppatch_walker, body_bkt, pool);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
sizeof("</D:prop>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:set>",
sizeof("</D:set>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
}
if (apr_hash_count(ctx->removed_props) > 0)
{
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:remove>",
sizeof("<D:remove>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("<D:prop>",
sizeof("<D:prop>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
SVN_INVALID_REVNUM,
proppatch_walker, body_bkt, pool);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:prop>",
sizeof("</D:prop>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("</D:remove>",
sizeof("</D:remove>") - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
}
tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(PROPPATCH_TRAILER,
sizeof(PROPPATCH_TRAILER) - 1,
alloc);
serf_bucket_aggregate_append(body_bkt, tmp_bkt);
return body_bkt;
}
static svn_error_t*
proppatch_resource(proppatch_context_t *proppatch,
commit_context_t *commit,
apr_pool_t *pool)
{
svn_ra_serf__handler_t *handler;
handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "PROPPATCH";
handler->path = proppatch->path;
handler->conn = commit->conn;
handler->session = commit->session;
handler->header_delegate = setup_proppatch_headers;
handler->header_delegate_baton = proppatch;
handler->body_delegate = create_proppatch_body;
handler->body_delegate_baton = proppatch;
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = &proppatch->progress;
svn_ra_serf__request_create(handler);
/* If we don't wait for the response, our pool will be gone! */
SVN_ERR(svn_ra_serf__context_run_wait(&proppatch->progress.done,
commit->session, pool));
if (proppatch->progress.status != 207)
{
svn_error_t *err;
err = return_response_err(handler, &proppatch->progress);
return svn_error_create(SVN_ERR_RA_DAV_PROPPATCH_FAILED, err,
_("At least one property change failed; repository is unchanged"));
}
return SVN_NO_ERROR;
}
static serf_bucket_t *
create_put_body(void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
file_context_t *ctx = baton;
apr_off_t offset;
/* 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. 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(ctx->svndiff);
offset = 0;
apr_file_seek(ctx->svndiff, APR_SET, &offset);
return serf_bucket_file_create(ctx->svndiff, alloc);
}
static apr_status_t
setup_put_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
file_context_t *ctx = baton;
if (ctx->base_checksum)
{
serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
ctx->base_checksum);
}
if (ctx->result_checksum)
{
serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
ctx->result_checksum);
}
if (ctx->commit->lock_tokens)
{
const char *token;
token = apr_hash_get(ctx->commit->lock_tokens, ctx->name,
APR_HASH_KEY_STRING);
if (token)
{
const char *token_header;
token_header = apr_pstrcat(pool, "(<", token, ">)", NULL);
serf_bucket_headers_set(headers, "If", token_header);
}
}
return APR_SUCCESS;
}
static apr_status_t
setup_copy_file_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
file_context_t *file = baton;
apr_uri_t uri;
const char *absolute_uri;
/* The Dest URI must be absolute. Bummer. */
uri = file->commit->session->repos_url;
uri.path = (char*)file->put_url;
absolute_uri = apr_uri_unparse(pool, &uri, 0);
serf_bucket_headers_set(headers, "Destination", absolute_uri);
serf_bucket_headers_set(headers, "Depth", "0");
serf_bucket_headers_set(headers, "Overwrite", "T");
return APR_SUCCESS;
}
static apr_status_t
setup_copy_dir_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
dir_context_t *dir = baton;
apr_uri_t uri;
const char *absolute_uri;
/* The Dest URI must be absolute. Bummer. */
uri = dir->commit->session->repos_url;
uri.path =
(char*)svn_path_url_add_component(dir->parent_dir->checkout->resource_url,
svn_path_basename(dir->name, pool),
pool);
absolute_uri = apr_uri_unparse(pool, &uri, 0);
serf_bucket_headers_set(headers, "Destination", absolute_uri);
serf_bucket_headers_set(headers, "Depth", "infinity");
serf_bucket_headers_set(headers, "Overwrite", "T");
/* Implicitly checkout this dir now. */
dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
dir->checkout->pool = dir->pool;
dir->checkout->activity_url = dir->commit->activity_url;
dir->checkout->activity_url_len = dir->commit->activity_url_len;
dir->checkout->resource_url = apr_pstrdup(dir->checkout->pool, uri.path);
apr_hash_set(dir->commit->copied_entries,
apr_pstrdup(dir->commit->pool, dir->name), APR_HASH_KEY_STRING,
(void*)1);
return APR_SUCCESS;
}
static apr_status_t
setup_delete_headers(serf_bucket_t *headers,
void *baton,
apr_pool_t *pool)
{
delete_context_t *ctx = baton;
serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
apr_ltoa(pool, ctx->revision));
if (ctx->lock_token_hash)
{
ctx->lock_token = apr_hash_get(ctx->lock_token_hash, ctx->path,
APR_HASH_KEY_STRING);
if (ctx->lock_token)
{
const char *token_header;
token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
ctx->lock_token, ">)", NULL);
serf_bucket_headers_set(headers, "If", token_header);
if (ctx->keep_locks)
serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
SVN_DAV_OPTION_KEEP_LOCKS);
}
}
return APR_SUCCESS;
}
#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
static serf_bucket_t *
create_delete_body(void *baton,
serf_bucket_alloc_t *alloc,
apr_pool_t *pool)
{
delete_context_t *ctx = baton;
serf_bucket_t *body, *tmp;
body = serf_bucket_aggregate_create(alloc);
tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
alloc);
serf_bucket_aggregate_append(body, tmp);
svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
body, alloc, pool);
return body;
}
/* Helper function to write the svndiff stream to temporary file. */
static svn_error_t *
svndiff_stream_write(void *file_baton,
const char *data,
apr_size_t *len)
{
file_context_t *ctx = file_baton;
apr_status_t status;
status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
if (status)
return svn_error_wrap_apr(status, _("Failed writing updated file"));
return SVN_NO_ERROR;
}
/* Commit baton callbacks */
static svn_error_t *
open_root(void *edit_baton,
svn_revnum_t base_revision,
apr_pool_t *dir_pool,
void **root_baton)
{
commit_context_t *ctx = edit_baton;
svn_ra_serf__options_context_t *opt_ctx;
svn_ra_serf__propfind_context_t *propfind_ctx;
svn_ra_serf__handler_t *handler;
svn_ra_serf__simple_request_context_t *mkact_ctx;
proppatch_context_t *proppatch_ctx;
dir_context_t *dir;
const char *activity_str;
const char *vcc_url;
apr_hash_t *props;
/* Create a UUID for this commit. */
ctx->uuid = svn_uuid_generate(ctx->pool);
svn_ra_serf__create_options_req(&opt_ctx, ctx->session,
ctx->session->conns[0],
ctx->session->repos_url.path, ctx->pool);
SVN_ERR(svn_ra_serf__context_run_wait(
svn_ra_serf__get_options_done_ptr(opt_ctx),
ctx->session, ctx->pool));
activity_str = svn_ra_serf__options_get_activity_collection(opt_ctx);
if (!activity_str)
{
abort();
}
ctx->activity_url = svn_path_url_add_component(activity_str,
ctx->uuid, ctx->pool);
ctx->activity_url_len = strlen(ctx->activity_url);
/* Create our activity URL now on the server. */
handler = apr_pcalloc(ctx->pool, sizeof(*handler));
handler->method = "MKACTIVITY";
handler->path = ctx->activity_url;
handler->conn = ctx->session->conns[0];
handler->session = ctx->session;
mkact_ctx = apr_pcalloc(ctx->pool, sizeof(*mkact_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = mkact_ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&mkact_ctx->done, ctx->session,
ctx->pool));
if (mkact_ctx->status != 201)
{
abort();
}
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
ctx->session, ctx->conn,
ctx->session->repos_url.path,
ctx->pool));
/* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
props = apr_hash_make(ctx->pool);
propfind_ctx = NULL;
svn_ra_serf__deliver_props(&propfind_ctx, props, ctx->session,
ctx->conn, vcc_url, SVN_INVALID_REVNUM, "0",
checked_in_props, FALSE, NULL, ctx->pool);
SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, ctx->session, ctx->pool));
ctx->baseline_url = svn_ra_serf__get_ver_prop(props, vcc_url,
SVN_INVALID_REVNUM,
"DAV:", "checked-in");
if (!ctx->baseline_url)
{
abort();
}
dir = apr_pcalloc(dir_pool, sizeof(*dir));
dir->pool = dir_pool;
dir->commit = ctx;
dir->base_revision = base_revision;
dir->name = "";
dir->changed_props = apr_hash_make(dir->pool);
dir->removed_props = apr_hash_make(dir->pool);
SVN_ERR(get_version_url(dir));
ctx->checked_in_url = dir->checked_in_url;
/* Checkout our root dir */
SVN_ERR(checkout_dir(dir));
/* PROPPATCH our log message and pass it along. */
proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
proppatch_ctx->pool = dir_pool;
proppatch_ctx->commit = ctx;
proppatch_ctx->path = ctx->baseline->resource_url;
proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
SVN_DAV_PROP_NS_SVN, "log",
dir->commit->log_msg, proppatch_ctx->pool);
SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
*root_baton = dir;
return SVN_NO_ERROR;
}
static svn_error_t *
delete_entry(const char *path,
svn_revnum_t revision,
void *parent_baton,
apr_pool_t *pool)
{
dir_context_t *dir = parent_baton;
delete_context_t *delete_ctx;
svn_ra_serf__handler_t *handler;
svn_error_t *err;
/* Ensure our directory has been checked out */
SVN_ERR(checkout_dir(dir));
/* DELETE our entry */
delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
delete_ctx->path = path;
delete_ctx->revision = revision;
delete_ctx->lock_token_hash = dir->commit->lock_tokens;
delete_ctx->keep_locks = dir->commit->keep_locks;
handler = apr_pcalloc(pool, sizeof(*handler));
handler->session = dir->commit->session;
handler->conn = dir->commit->conn;
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = &delete_ctx->progress;
handler->header_delegate = setup_delete_headers;
handler->header_delegate_baton = delete_ctx;
handler->method = "DELETE";
handler->path =
svn_path_url_add_component(dir->checkout->resource_url,
svn_path_basename(path, pool),
pool);
svn_ra_serf__request_create(handler);
err = svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
dir->commit->session, pool);
if (err &&
(err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
{
svn_error_clear(err);
handler->body_delegate = create_delete_body;
handler->body_delegate_baton = delete_ctx;
handler->body_type = "text/xml";
svn_ra_serf__request_create(handler);
delete_ctx->progress.done = 0;
SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
dir->commit->session, pool));
}
else if (err)
{
return err;
}
if (delete_ctx->progress.status != 204)
{
return return_response_err(handler, &delete_ctx->progress);
}
apr_hash_set(dir->commit->deleted_entries,
apr_pstrdup(dir->commit->pool, path), APR_HASH_KEY_STRING,
(void*)1);
return SVN_NO_ERROR;
}
static svn_error_t *
add_directory(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *dir_pool,
void **child_baton)
{
dir_context_t *parent = parent_baton;
dir_context_t *dir;
svn_ra_serf__handler_t *handler;
svn_ra_serf__simple_request_context_t *add_dir_ctx;
/* Ensure our parent is checked out. */
SVN_ERR(checkout_dir(parent));
dir = apr_pcalloc(dir_pool, sizeof(*dir));
dir->pool = dir_pool;
dir->parent_dir = parent;
dir->commit = parent->commit;
dir->base_revision = SVN_INVALID_REVNUM;
dir->copy_revision = copyfrom_revision;
dir->copy_path = copyfrom_path;
dir->name = path;
dir->checked_in_url =
svn_path_url_add_component(parent->commit->checked_in_url,
path, dir->pool);
dir->changed_props = apr_hash_make(dir->pool);
dir->removed_props = apr_hash_make(dir->pool);
handler = apr_pcalloc(dir->pool, sizeof(*handler));
handler->conn = dir->commit->conn;
handler->session = dir->commit->session;
add_dir_ctx = apr_pcalloc(dir->pool, sizeof(*add_dir_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = add_dir_ctx;
if (!dir->copy_path)
{
handler->method = "MKCOL";
handler->path = svn_path_url_add_component(parent->checkout->resource_url,
svn_path_basename(path,
dir->pool),
dir->pool);
}
else
{
apr_uri_t uri;
apr_hash_t *props;
const char *vcc_url, *rel_copy_path, *basecoll_url, *req_url;
props = apr_hash_make(dir->pool);
apr_uri_parse(dir->pool, dir->copy_path, &uri);
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &rel_copy_path,
dir->commit->session,
dir->commit->conn,
uri.path, dir->pool));
SVN_ERR(svn_ra_serf__retrieve_props(props,
dir->commit->session,
dir->commit->conn,
vcc_url, dir->copy_revision, "0",
baseline_props, dir->pool));
basecoll_url = svn_ra_serf__get_ver_prop(props,
vcc_url, dir->copy_revision,
"DAV:", "baseline-collection");
if (!basecoll_url)
{
abort();
}
req_url = svn_path_url_add_component(basecoll_url, rel_copy_path,
dir->pool);
handler->method = "COPY";
handler->path = req_url;
handler->header_delegate = setup_copy_dir_headers;
handler->header_delegate_baton = dir;
}
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&add_dir_ctx->done,
dir->commit->session, dir->pool));
if (add_dir_ctx->status != 201)
{
SVN_ERR(add_dir_ctx->server_error.error);
return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("Add directory failed: %s on %s (%d)"),
handler->method, handler->path,
add_dir_ctx->status);
}
*child_baton = dir;
return SVN_NO_ERROR;
}
static svn_error_t *
open_directory(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *dir_pool,
void **child_baton)
{
dir_context_t *parent = parent_baton;
dir_context_t *dir;
dir = apr_pcalloc(dir_pool, sizeof(*dir));
dir->pool = dir_pool;
dir->parent_dir = parent;
dir->commit = parent->commit;
dir->base_revision = base_revision;
dir->name = path;
dir->changed_props = apr_hash_make(dir->pool);
dir->removed_props = apr_hash_make(dir->pool);
SVN_ERR(get_version_url(dir));
*child_baton = dir;
return SVN_NO_ERROR;
}
static svn_error_t *
change_dir_prop(void *dir_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
dir_context_t *dir = dir_baton;
const char *ns;
/* Ensure we have a checked out dir. */
SVN_ERR(checkout_dir(dir));
name = apr_pstrdup(dir->pool, name);
if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
{
ns = SVN_DAV_PROP_NS_SVN;
name += sizeof(SVN_PROP_PREFIX) - 1;
}
else
{
ns = SVN_DAV_PROP_NS_CUSTOM;
}
if (value)
{
value = svn_string_dup(value, dir->pool);
svn_ra_serf__set_prop(dir->changed_props, dir->checkout->resource_url,
ns, name, value, dir->pool);
}
else
{
value = svn_string_create("", dir->pool);
svn_ra_serf__set_prop(dir->removed_props, dir->checkout->resource_url,
ns, name, value, dir->pool);
}
return SVN_NO_ERROR;
}
static svn_error_t *
close_directory(void *dir_baton,
apr_pool_t *pool)
{
dir_context_t *dir = dir_baton;
/* Huh? We're going to be called before the texts are sent. Ugh.
* Therefore, just wave politely at our caller.
*/
/* PROPPATCH our prop change and pass it along. */
if (apr_hash_count(dir->changed_props) ||
apr_hash_count(dir->removed_props))
{
proppatch_context_t *proppatch_ctx;
proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
proppatch_ctx->pool = pool;
proppatch_ctx->commit = dir->commit;
proppatch_ctx->name = dir->name;
proppatch_ctx->path = dir->checkout->resource_url;
proppatch_ctx->changed_props = dir->changed_props;
proppatch_ctx->removed_props = dir->removed_props;
SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
absent_directory(const char *path,
void *parent_baton,
apr_pool_t *pool)
{
#if 0
dir_context_t *ctx = parent_baton;
#endif
abort();
}
static svn_error_t *
add_file(const char *path,
void *parent_baton,
const char *copy_path,
svn_revnum_t copy_revision,
apr_pool_t *file_pool,
void **file_baton)
{
dir_context_t *dir = parent_baton;
file_context_t *new_file;
/* Ensure our directory has been checked out */
SVN_ERR(checkout_dir(dir));
new_file = apr_pcalloc(file_pool, sizeof(*new_file));
new_file->pool = file_pool;
dir->ref_count++;
new_file->parent_dir = dir;
new_file->commit = dir->commit;
new_file->name = path;
new_file->base_revision = SVN_INVALID_REVNUM;
new_file->copy_path = copy_path;
new_file->copy_revision = copy_revision;
new_file->changed_props = apr_hash_make(new_file->pool);
new_file->removed_props = apr_hash_make(new_file->pool);
/* Ensure that the file doesn't exist by doing a HEAD on the resource -
* only if we haven't deleted it in this commit already.
*/
if (!apr_hash_get(dir->commit->deleted_entries,
new_file->name, APR_HASH_KEY_STRING))
{
svn_ra_serf__simple_request_context_t *head_ctx;
svn_ra_serf__handler_t *handler;
handler = apr_pcalloc(new_file->pool, sizeof(*handler));
handler->session = new_file->commit->session;
handler->conn = new_file->commit->conn;
handler->method = "HEAD";
handler->path =
svn_path_url_add_component(new_file->commit->session->repos_url.path,
path, new_file->pool);
head_ctx = apr_pcalloc(new_file->pool, sizeof(*head_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = head_ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&head_ctx->done,
new_file->commit->session,
new_file->pool));
if (head_ctx->status != 404)
{
return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
_("File '%s' already exists"), path);
}
}
new_file->put_url =
svn_path_url_add_component(dir->checkout->resource_url,
svn_path_basename(path, new_file->pool),
new_file->pool);
*file_baton = new_file;
return SVN_NO_ERROR;
}
static svn_error_t *
open_file(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *file_pool,
void **file_baton)
{
dir_context_t *ctx = parent_baton;
file_context_t *new_file;
new_file = apr_pcalloc(file_pool, sizeof(*new_file));
new_file->pool = file_pool;
ctx->ref_count++;
new_file->parent_dir = ctx;
new_file->commit = ctx->commit;
/* TODO: Remove directory names? */
new_file->name = path;
new_file->base_revision = base_revision;
new_file->changed_props = apr_hash_make(new_file->pool);
new_file->removed_props = apr_hash_make(new_file->pool);
/* CHECKOUT the file into our activity. */
SVN_ERR(checkout_file(new_file));
new_file->put_url = new_file->checkout->resource_url;
*file_baton = new_file;
return SVN_NO_ERROR;
}
static svn_error_t *
apply_textdelta(void *file_baton,
const char *base_checksum,
apr_pool_t *pool,
svn_txdelta_window_handler_t *handler,
void **handler_baton)
{
file_context_t *ctx = file_baton;
const svn_ra_callbacks2_t *wc_callbacks;
void *wc_callback_baton;
/* Store the stream in a temporary file; we'll give it to serf when we
* close this file.
*
* TODO: There should be a way we can stream the request body instead of
* writing to a temporary file (ugh). A special svn stream serf bucket
* that returns EAGAIN until we receive the done call? But, when
* would we run through the serf context? Grr.
*/
wc_callbacks = ctx->commit->session->wc_callbacks;
wc_callback_baton = ctx->commit->session->wc_callback_baton;
SVN_ERR(wc_callbacks->open_tmp_file(&ctx->svndiff,
wc_callback_baton,
ctx->pool));
ctx->stream = svn_stream_create(ctx, pool);
svn_stream_set_write(ctx->stream, svndiff_stream_write);
svn_txdelta_to_svndiff(ctx->stream, pool, handler, handler_baton);
ctx->base_checksum = base_checksum;
return SVN_NO_ERROR;
}
static svn_error_t *
change_file_prop(void *file_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
file_context_t *file = file_baton;
const char *ns;
name = apr_pstrdup(file->pool, name);
if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
{
ns = SVN_DAV_PROP_NS_SVN;
name += sizeof(SVN_PROP_PREFIX) - 1;
}
else
{
ns = SVN_DAV_PROP_NS_CUSTOM;
}
if (value)
{
value = svn_string_dup(value, file->pool);
svn_ra_serf__set_prop(file->changed_props, file->put_url,
ns, name, value, file->pool);
}
else
{
value = svn_string_create("", file->pool);
svn_ra_serf__set_prop(file->removed_props, file->put_url,
ns, name, value, file->pool);
}
return SVN_NO_ERROR;
}
static svn_error_t *
close_file(void *file_baton,
const char *text_checksum,
apr_pool_t *pool)
{
file_context_t *ctx = file_baton;
ctx->result_checksum = text_checksum;
if (ctx->copy_path)
{
svn_ra_serf__handler_t *handler;
svn_ra_serf__simple_request_context_t *copy_ctx;
apr_uri_t uri;
apr_hash_t *props;
const char *vcc_url, *rel_copy_path, *basecoll_url, *req_url;
props = apr_hash_make(pool);
apr_uri_parse(pool, ctx->copy_path, &uri);
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &rel_copy_path,
ctx->commit->session,
ctx->commit->conn,
uri.path, pool));
SVN_ERR(svn_ra_serf__retrieve_props(props,
ctx->commit->session,
ctx->commit->conn,
vcc_url, ctx->copy_revision, "0",
baseline_props, pool));
basecoll_url = svn_ra_serf__get_ver_prop(props,
vcc_url, ctx->copy_revision,
"DAV:", "baseline-collection");
if (!basecoll_url)
{
abort();
}
req_url = svn_path_url_add_component(basecoll_url, rel_copy_path, pool);
handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "COPY";
handler->path = req_url;
handler->conn = ctx->commit->conn;
handler->session = ctx->commit->session;
copy_ctx = apr_pcalloc(pool, sizeof(*copy_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = copy_ctx;
handler->header_delegate = setup_copy_file_headers;
handler->header_delegate_baton = ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(©_ctx->done,
ctx->commit->session, pool));
if (copy_ctx->status != 201 && copy_ctx->status != 204)
{
return return_response_err(handler, copy_ctx);
}
}
/* If we had a stream of changes, push them to the server... */
if (ctx->stream)
{
svn_ra_serf__handler_t *handler;
svn_ra_serf__simple_request_context_t *put_ctx;
handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "PUT";
handler->path = ctx->put_url;
handler->conn = ctx->commit->conn;
handler->session = ctx->commit->session;
put_ctx = apr_pcalloc(pool, sizeof(*put_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = put_ctx;
handler->body_delegate = create_put_body;
handler->body_delegate_baton = ctx;
handler->body_type = "application/vnd.svn-svndiff";
handler->header_delegate = setup_put_headers;
handler->header_delegate_baton = ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&put_ctx->done,
ctx->commit->session, pool));
if (put_ctx->status != 204 && put_ctx->status != 201)
{
return return_response_err(handler, put_ctx);
}
}
/* If we had any prop changes, push them via PROPPATCH. */
if (apr_hash_count(ctx->changed_props) ||
apr_hash_count(ctx->removed_props))
{
proppatch_context_t *proppatch;
proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
proppatch->pool = ctx->pool;
proppatch->name = ctx->name;
proppatch->path = ctx->put_url;
proppatch->commit = ctx->commit;
proppatch->changed_props = ctx->changed_props;
proppatch->removed_props = ctx->removed_props;
SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
absent_file(const char *path,
void *parent_baton,
apr_pool_t *pool)
{
#if 0
dir_context_t *ctx = parent_baton;
#endif
abort();
}
static svn_error_t *
close_edit(void *edit_baton,
apr_pool_t *pool)
{
commit_context_t *ctx = edit_baton;
svn_ra_serf__merge_context_t *merge_ctx;
svn_ra_serf__simple_request_context_t *delete_ctx;
svn_ra_serf__handler_t *handler;
svn_boolean_t *merge_done;
/* MERGE our activity */
SVN_ERR(svn_ra_serf__merge_create_req(&merge_ctx, ctx->session,
ctx->session->conns[0],
ctx->session->repos_url.path,
ctx->activity_url,
ctx->activity_url_len,
ctx->lock_tokens,
ctx->keep_locks,
pool));
merge_done = svn_ra_serf__merge_get_done_ptr(merge_ctx);
SVN_ERR(svn_ra_serf__context_run_wait(merge_done, ctx->session, pool));
if (svn_ra_serf__merge_get_status(merge_ctx) != 200)
{
abort();
}
/* Inform the WC that we did a commit. */
SVN_ERR(ctx->callback(svn_ra_serf__merge_get_commit_info(merge_ctx),
ctx->callback_baton, pool));
/* DELETE our completed activity */
handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "DELETE";
handler->path = ctx->activity_url;
handler->conn = ctx->conn;
handler->session = ctx->session;
delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = delete_ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
pool));
if (delete_ctx->status != 204)
{
abort();
}
return SVN_NO_ERROR;
}
static svn_error_t *
abort_edit(void *edit_baton,
apr_pool_t *pool)
{
commit_context_t *ctx = edit_baton;
svn_ra_serf__handler_t *handler;
svn_ra_serf__simple_request_context_t *delete_ctx;
/* DELETE our aborted activity */
handler = apr_pcalloc(pool, sizeof(*handler));
handler->method = "DELETE";
handler->path = ctx->activity_url;
handler->conn = ctx->session->conns[0];
handler->session = ctx->session;
delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
handler->response_handler = svn_ra_serf__handle_status_only;
handler->response_baton = delete_ctx;
svn_ra_serf__request_create(handler);
SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
pool));
if (delete_ctx->status != 204)
{
abort();
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
const svn_delta_editor_t **ret_editor,
void **edit_baton,
const char *log_msg,
svn_commit_callback2_t callback,
void *callback_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
svn_delta_editor_t *editor;
commit_context_t *ctx;
ctx = apr_pcalloc(pool, sizeof(*ctx));
ctx->pool = pool;
ctx->session = session;
ctx->conn = session->conns[0];
ctx->log_msg = svn_string_create(log_msg, pool);
ctx->callback = callback;
ctx->callback_baton = callback_baton;
ctx->lock_tokens = lock_tokens;
ctx->keep_locks = keep_locks;
ctx->deleted_entries = apr_hash_make(ctx->pool);
ctx->copied_entries = apr_hash_make(ctx->pool);
editor = svn_delta_default_editor(pool);
editor->open_root = open_root;
editor->delete_entry = delete_entry;
editor->add_directory = add_directory;
editor->open_directory = open_directory;
editor->change_dir_prop = change_dir_prop;
editor->close_directory = close_directory;
editor->absent_directory = absent_directory;
editor->add_file = add_file;
editor->open_file = open_file;
editor->apply_textdelta = apply_textdelta;
editor->change_file_prop = change_file_prop;
editor->close_file = close_file;
editor->absent_file = absent_file;
editor->close_edit = close_edit;
editor->abort_edit = abort_edit;
*ret_editor = editor;
*edit_baton = ctx;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
svn_revnum_t rev,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
svn_ra_serf__propfind_context_t *propfind_ctx;
proppatch_context_t *proppatch_ctx;
commit_context_t *commit;
const char *vcc_url, *checked_in_href, *ns;
apr_hash_t *props;
commit = apr_pcalloc(pool, sizeof(*commit));
commit->pool = pool;
commit->session = session;
commit->conn = session->conns[0];
SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
commit->session,
commit->conn,
commit->session->repos_url.path, pool));
props = apr_hash_make(pool);
propfind_ctx = NULL;
svn_ra_serf__deliver_props(&propfind_ctx, props, commit->session,
commit->conn, vcc_url, rev, "0",
checked_in_props, FALSE, NULL, pool);
SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, commit->session, pool));
checked_in_href = svn_ra_serf__get_ver_prop(props, vcc_url, rev,
"DAV:", "href");
if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
{
ns = SVN_DAV_PROP_NS_SVN;
name += sizeof(SVN_PROP_PREFIX) - 1;
}
else
{
ns = SVN_DAV_PROP_NS_CUSTOM;
}
/* PROPPATCH our log message and pass it along. */
proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
proppatch_ctx->pool = pool;
proppatch_ctx->commit = commit;
proppatch_ctx->path = checked_in_href;
proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
if (value)
{
svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
ns, name, value, proppatch_ctx->pool);
}
else
{
value = svn_string_create("", proppatch_ctx->pool);
svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
ns, name, value, proppatch_ctx->pool);
}
SVN_ERR(proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool));
return SVN_NO_ERROR;
}
syntax highlighted by Code2HTML, v. 0.9.1