#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_uuid.h>
#define APR_WANT_STDIO
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_delta.h"
#include "svn_io.h"
#include "svn_ra.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_xml.h"
#include "svn_dav.h"
#include "svn_props.h"
#include "svn_private_config.h"
#include "ra_neon.h"
#define APPLY_TO_VERSION "<D:apply-to-version/>"
typedef struct version_rsrc_t
{
svn_revnum_t revision;
const char *url;
const char *vsn_url;
const char *wr_url;
const char *name;
apr_pool_t *pool;
} version_rsrc_t;
typedef struct commit_ctx_t
{
apr_pool_t *pool;
svn_ra_neon__session_t *ras;
const char *anchor_relpath;
apr_hash_t *revprop_table;
apr_hash_t *valid_targets;
const char *user;
svn_commit_callback2_t callback;
void *callback_baton;
apr_hash_t *lock_tokens;
svn_boolean_t keep_locks;
const char *txn_url;
const char *txn_root_url;
const char *activity_url;
} commit_ctx_t;
#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
typedef struct put_baton_t
{
apr_file_t *tmpfile;
svn_stringbuf_t *fname;
const char *base_checksum;
apr_off_t progress;
svn_ra_neon__session_t *ras;
apr_pool_t *pool;
} put_baton_t;
typedef struct resource_baton_t
{
commit_ctx_t *cc;
version_rsrc_t *rsrc;
svn_revnum_t base_revision;
apr_hash_t *prop_changes;
apr_array_header_t *prop_deletes;
svn_boolean_t created;
svn_boolean_t copied;
apr_pool_t *pool;
put_baton_t *put_baton;
const char *token;
const char *txn_root_url;
const char *local_relpath;
} resource_baton_t;
static const ne_propname fetch_props[] =
{
{ "DAV:", "checked-in" },
{ NULL }
};
static const ne_propname log_message_prop = { SVN_DAV_PROP_NS_SVN, "log" };
static version_rsrc_t * dup_resource(version_rsrc_t *base, apr_pool_t *pool)
{
version_rsrc_t *rsrc = apr_pcalloc(pool, sizeof(*rsrc));
rsrc->revision = base->revision;
rsrc->url = base->url ?
apr_pstrdup(pool, base->url) : NULL;
rsrc->vsn_url = base->vsn_url ?
apr_pstrdup(pool, base->vsn_url) : NULL;
rsrc->wr_url = base->wr_url ?
apr_pstrdup(pool, base->wr_url) : NULL;
rsrc->name = base->name ?
apr_pstrdup(pool, base->name) : NULL;
rsrc->pool = pool;
return rsrc;
}
static svn_error_t * get_version_url(commit_ctx_t *cc,
const char *local_relpath,
const version_rsrc_t *parent,
version_rsrc_t *rsrc,
svn_boolean_t force,
apr_pool_t *pool)
{
svn_ra_neon__resource_t *propres;
const char *url;
const svn_string_t *url_str;
if (!force)
{
if (cc->ras->callbacks->get_wc_prop != NULL)
{
const svn_string_t *vsn_url_value;
SVN_ERR(cc->ras->callbacks->get_wc_prop(cc->ras->callback_baton,
local_relpath,
SVN_RA_NEON__LP_VSN_URL,
&vsn_url_value,
pool));
if (vsn_url_value != NULL)
{
rsrc->vsn_url = apr_pstrdup(rsrc->pool, vsn_url_value->data);
return SVN_NO_ERROR;
}
}
if (parent && parent->vsn_url && parent->revision == rsrc->revision)
{
rsrc->vsn_url = svn_path_url_add_component2(parent->vsn_url,
rsrc->name,
rsrc->pool);
return SVN_NO_ERROR;
}
}
if (rsrc->revision == SVN_INVALID_REVNUM)
{
url = rsrc->url;
}
else
{
const char *bc_url;
const char *bc_relative;
SVN_ERR(svn_ra_neon__get_baseline_info(&bc_url, &bc_relative, NULL,
cc->ras, rsrc->url,
rsrc->revision, pool));
url = svn_path_url_add_component2(bc_url, bc_relative, pool);
}
SVN_ERR(svn_ra_neon__get_props_resource(&propres, cc->ras, url,
NULL, fetch_props, pool));
url_str = apr_hash_get(propres->propset,
SVN_RA_NEON__PROP_CHECKED_IN,
APR_HASH_KEY_STRING);
if (url_str == NULL)
{
return svn_error_create(APR_EGENERAL, NULL,
_("Could not fetch the Version Resource URL "
"(needed during an import or when it is "
"missing from the local, cached props)"));
}
rsrc->vsn_url = apr_pstrdup(rsrc->pool, url_str->data);
if (cc->ras->callbacks->push_wc_prop != NULL)
{
SVN_ERR(cc->ras->callbacks->push_wc_prop(cc->ras->callback_baton,
local_relpath,
SVN_RA_NEON__LP_VSN_URL,
url_str,
pool));
}
return SVN_NO_ERROR;
}
static svn_error_t * get_activity_collection(commit_ctx_t *cc,
const svn_string_t **collection,
svn_boolean_t force,
apr_pool_t *pool)
{
if (!force && cc->ras->callbacks->get_wc_prop != NULL)
{
SVN_ERR(cc->ras->callbacks->get_wc_prop(cc->ras->callback_baton,
"",
SVN_RA_NEON__LP_ACTIVITY_COLL,
collection,
pool));
if (*collection != NULL)
{
return SVN_NO_ERROR;
}
}
SVN_ERR(svn_ra_neon__get_activity_collection(collection,
cc->ras,
pool));
if (cc->ras->callbacks->push_wc_prop != NULL)
{
SVN_ERR(cc->ras->callbacks->push_wc_prop(cc->ras->callback_baton,
"",
SVN_RA_NEON__LP_ACTIVITY_COLL,
*collection,
pool));
}
return SVN_NO_ERROR;
}
static svn_error_t * create_activity(commit_ctx_t *cc,
apr_pool_t *pool)
{
const svn_string_t * activity_collection;
const char *uuid_buf = svn_uuid_generate(pool);
int code;
const char *url;
SVN_ERR(get_activity_collection(cc, &activity_collection, FALSE, pool));
url = svn_path_url_add_component2(activity_collection->data,
uuid_buf, pool);
SVN_ERR(svn_ra_neon__simple_request(&code, cc->ras,
"MKACTIVITY", url, NULL, NULL,
201 ,
404 , pool));
if (code == 404)
{
SVN_ERR(get_activity_collection(cc, &activity_collection, TRUE, pool));
url = svn_path_url_add_component2(activity_collection->data,
uuid_buf, pool);
SVN_ERR(svn_ra_neon__simple_request(&code, cc->ras,
"MKACTIVITY", url, NULL, NULL,
201, 0, pool));
}
cc->activity_url = apr_pstrdup(cc->pool, url);
return SVN_NO_ERROR;
}
static svn_error_t * add_child(version_rsrc_t **child,
commit_ctx_t *cc,
const version_rsrc_t *parent,
const char *parent_local_relpath,
const char *name,
int created,
svn_revnum_t revision,
apr_pool_t *pool)
{
version_rsrc_t *rsrc;
rsrc = apr_pcalloc(pool, sizeof(*rsrc));
rsrc->pool = pool;
rsrc->revision = revision;
rsrc->name = name;
rsrc->url = svn_path_url_add_component2(parent->url, name, pool);
if (created || (parent->vsn_url == NULL))
{
rsrc->wr_url = svn_path_url_add_component2(parent->wr_url, name, pool);
}
else
{
SVN_ERR(get_version_url(cc, svn_relpath_join(parent_local_relpath,
name, pool),
parent, rsrc, FALSE, pool));
}
*child = rsrc;
return SVN_NO_ERROR;
}
static svn_error_t * do_checkout(commit_ctx_t *cc,
const char *vsn_url,
svn_boolean_t allow_404,
const char *token,
svn_boolean_t is_vcc,
int *code,
const char **locn,
apr_pool_t *pool)
{
svn_ra_neon__request_t *request;
const char *body;
apr_hash_t *extra_headers = NULL;
svn_error_t *err = SVN_NO_ERROR;
SVN_ERR(svn_ra_neon__request_create(&request, cc->ras, "CHECKOUT", vsn_url,
pool));
body = apr_psprintf(request->pool,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<D:checkout xmlns:D=\"DAV:\">"
"<D:activity-set>"
"<D:href>%s</D:href>"
"</D:activity-set>%s</D:checkout>",
cc->activity_url,
is_vcc ? APPLY_TO_VERSION: "");
if (token)
{
extra_headers = apr_hash_make(request->pool);
svn_ra_neon__set_header(extra_headers, "If",
apr_psprintf(request->pool, "(<%s>)", token));
}
err = svn_ra_neon__request_dispatch(code, request, extra_headers, body,
201 ,
allow_404 ? 404 : 0,
pool);
if (err)
goto cleanup;
if (allow_404 && *code == 404 && request->err)
{
svn_error_clear(request->err);
request->err = SVN_NO_ERROR;
}
*locn = svn_ra_neon__request_get_location(request, pool);
cleanup:
svn_ra_neon__request_destroy(request);
return err;
}
static svn_error_t * checkout_resource(commit_ctx_t *cc,
const char *local_relpath,
version_rsrc_t *rsrc,
svn_boolean_t allow_404,
const char *token,
svn_boolean_t is_vcc,
apr_pool_t *pool)
{
int code;
const char *locn = NULL;
ne_uri parse;
svn_error_t *err;
if (rsrc->wr_url != NULL)
{
return NULL;
}
err = do_checkout(cc, rsrc->vsn_url, allow_404, token,
is_vcc, &code, &locn, pool);
if (err == NULL && allow_404 && code == 404)
{
locn = NULL;
SVN_ERR(get_version_url(cc, local_relpath, NULL, rsrc, TRUE, pool));
err = do_checkout(cc, rsrc->vsn_url, FALSE, token,
is_vcc, &code, &locn, pool);
}
if (err)
{
if (err->apr_err == SVN_ERR_FS_CONFLICT)
return svn_error_createf
(SVN_ERR_FS_OUT_OF_DATE, err,
_("File or directory '%s' is out of date; try updating"),
local_relpath);
return err;
}
if (locn == NULL)
return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("The CHECKOUT response did not contain a "
"'Location:' header"));
if (ne_uri_parse(locn, &parse) != 0)
{
ne_uri_free(&parse);
return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
_("Unable to parse URL '%s'"), locn);
}
rsrc->wr_url = apr_pstrdup(rsrc->pool, parse.path);
ne_uri_free(&parse);
return SVN_NO_ERROR;
}
static void record_prop_change(apr_pool_t *pool,
resource_baton_t *r,
const char *name,
const svn_string_t *value)
{
name = apr_pstrdup(pool, name);
if (value)
{
if (r->prop_changes == NULL)
r->prop_changes = apr_hash_make(pool);
apr_hash_set(r->prop_changes, name, APR_HASH_KEY_STRING,
svn_string_dup(value, pool));
}
else
{
if (r->prop_deletes == NULL)
r->prop_deletes = apr_array_make(pool, 5, sizeof(char *));
APR_ARRAY_PUSH(r->prop_deletes, const char *) = name;
}
}
static svn_error_t * copy_resource(svn_ra_neon__session_t *ras,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
const char *copy_dst_url,
apr_pool_t *scratch_pool)
{
const char *bc_url;
const char *bc_relative;
const char *copy_src_url;
SVN_ERR(svn_ra_neon__get_baseline_info(&bc_url, &bc_relative, NULL,
ras, copyfrom_path,
copyfrom_revision, scratch_pool));
copy_src_url = svn_path_url_add_component2(bc_url, bc_relative,
scratch_pool);
SVN_ERR(svn_ra_neon__copy(ras, 1 , SVN_RA_NEON__DEPTH_INFINITE,
copy_src_url, copy_dst_url, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t * do_proppatch(resource_baton_t *rb,
apr_pool_t *pool)
{
apr_hash_t *extra_headers = apr_hash_make(pool);
const char *proppatch_target =
USING_HTTPV2_COMMIT_SUPPORT(rb->cc) ? rb->txn_root_url : rb->rsrc->wr_url;
if (SVN_IS_VALID_REVNUM(rb->base_revision))
svn_ra_neon__set_header(extra_headers, SVN_DAV_VERSION_NAME_HEADER,
apr_psprintf(pool, "%ld", rb->base_revision));
if (rb->token)
apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
apr_psprintf(pool, "(<%s>)", rb->token));
return svn_error_trace(svn_ra_neon__do_proppatch(rb->cc->ras,
proppatch_target,
rb->prop_changes,
rb->prop_deletes,
NULL, extra_headers,
pool));
}
static void
add_valid_target(commit_ctx_t *cc,
const char *path,
enum svn_recurse_kind kind)
{
apr_hash_t *hash = cc->valid_targets;
svn_string_t *path_str = svn_string_create(path, apr_hash_pool_get(hash));
apr_hash_set(hash, path_str->data, path_str->len, (void*)kind);
}
static apr_hash_t *get_child_tokens(apr_hash_t *lock_tokens,
const char *dir,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
apr_hash_t *tokens = apr_hash_make(pool);
apr_pool_t *subpool = svn_pool_create(pool);
for (hi = apr_hash_first(pool, lock_tokens); hi; hi = apr_hash_next(hi))
{
const void *key;
apr_ssize_t klen;
void *val;
svn_pool_clear(subpool);
apr_hash_this(hi, &key, &klen, &val);
if (svn_relpath__is_child(dir, key, subpool))
apr_hash_set(tokens, key, klen, val);
}
svn_pool_destroy(subpool);
return tokens;
}
static svn_error_t *
apply_revprops(commit_ctx_t *cc,
apr_pool_t *pool)
{
const char *proppatch_url;
if (USING_HTTPV2_COMMIT_SUPPORT(cc))
{
proppatch_url = cc->txn_url;
}
else
{
const char *vcc;
version_rsrc_t vcc_rsrc = { SVN_INVALID_REVNUM };
svn_error_t *err = NULL;
int retry_count = 5;
SVN_ERR(svn_ra_neon__get_vcc(&vcc, cc->ras, cc->ras->root.path, pool));
do {
svn_error_clear(err);
vcc_rsrc.pool = pool;
vcc_rsrc.vsn_url = vcc;
err = checkout_resource(cc, "", &vcc_rsrc, FALSE, NULL, TRUE, pool);
if (err && err->apr_err != SVN_ERR_APMOD_BAD_BASELINE)
return err;
} while (err && (--retry_count > 0));
if (err)
return err;
proppatch_url = vcc_rsrc.wr_url;
}
return svn_error_trace(svn_ra_neon__do_proppatch(cc->ras, proppatch_url,
cc->revprop_table,
NULL, NULL, NULL, pool));
}
static svn_error_t * commit_open_root(void *edit_baton,
svn_revnum_t base_revision,
apr_pool_t *dir_pool,
void **root_baton)
{
commit_ctx_t *cc = edit_baton;
resource_baton_t *root;
version_rsrc_t *rsrc = NULL;
root = apr_pcalloc(dir_pool, sizeof(*root));
root->pool = dir_pool;
root->cc = cc;
root->base_revision = base_revision;
root->created = FALSE;
root->local_relpath = "";
if (SVN_RA_NEON__HAVE_HTTPV2_SUPPORT(cc->ras))
{
svn_ra_neon__request_t *req;
const char *header_val;
svn_error_t *err;
SVN_ERR(svn_ra_neon__request_create(&req, cc->ras, "POST",
cc->ras->me_resource, dir_pool));
ne_add_request_header(req->ne_req, "Content-Type", SVN_SKEL_MIME_TYPE);
#ifdef SVN_DAV_SEND_VTXN_NAME
ne_add_request_header(req->ne_req, SVN_DAV_VTXN_NAME_HEADER,
svn_uuid_generate(dir_pool));
#endif
err = svn_ra_neon__request_dispatch(NULL, req, NULL, "( create-txn )",
201, 0, dir_pool);
if (!err)
{
if ((header_val = ne_get_response_header(req->ne_req,
SVN_DAV_VTXN_NAME_HEADER)))
{
cc->txn_url = svn_path_url_add_component2(cc->ras->vtxn_stub,
header_val, cc->pool);
cc->txn_root_url
= svn_path_url_add_component2(cc->ras->vtxn_root_stub,
header_val, cc->pool);
}
else if ((header_val
= ne_get_response_header(req->ne_req,
SVN_DAV_TXN_NAME_HEADER)))
{
cc->txn_url = svn_path_url_add_component2(cc->ras->txn_stub,
header_val, cc->pool);
cc->txn_root_url
= svn_path_url_add_component2(cc->ras->txn_root_stub,
header_val, cc->pool);
}
else
err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
_("POST request did not return transaction "
"information"));
}
svn_ra_neon__request_destroy(req);
SVN_ERR(err);
root->rsrc = NULL;
root->txn_root_url = svn_path_url_add_component2(cc->txn_root_url,
cc->anchor_relpath,
dir_pool);
}
else
{
SVN_ERR(create_activity(cc, dir_pool));
rsrc = apr_pcalloc(dir_pool, sizeof(*rsrc));
rsrc->pool = dir_pool;
rsrc->revision = base_revision;
rsrc->url = cc->ras->root.path;
SVN_ERR(get_version_url(cc, root->local_relpath, NULL, rsrc,
FALSE, dir_pool));
root->rsrc = rsrc;
root->txn_root_url = NULL;
}
SVN_ERR(apply_revprops(cc, dir_pool));
*root_baton = root;
return SVN_NO_ERROR;
}
static svn_error_t * commit_delete_entry(const char *path,
svn_revnum_t revision,
void *parent_baton,
apr_pool_t *pool)
{
resource_baton_t *parent = parent_baton;
const char *name = svn_relpath_basename(path, NULL);
apr_hash_t *extra_headers = apr_hash_make(pool);
int code;
svn_error_t *serr;
const char *delete_target;
if (USING_HTTPV2_COMMIT_SUPPORT(parent->cc))
{
delete_target = svn_path_url_add_component2(parent->txn_root_url,
name, pool);
}
else
{
SVN_ERR(checkout_resource(parent->cc, parent->local_relpath,
parent->rsrc, TRUE, NULL, FALSE, pool));
delete_target = svn_path_url_add_component2(parent->rsrc->wr_url,
name, pool);
}
if (SVN_IS_VALID_REVNUM(revision))
svn_ra_neon__set_header(extra_headers, SVN_DAV_VERSION_NAME_HEADER,
apr_psprintf(pool, "%ld", revision));
if (parent->cc->lock_tokens)
{
const char *token =
apr_hash_get(parent->cc->lock_tokens, path, APR_HASH_KEY_STRING);
if (token)
{
const char *token_header_val;
const char *token_uri;
token_uri = svn_path_url_add_component2(parent->cc->ras->url->data,
path, pool);
token_header_val = apr_psprintf(pool, "<%s> (<%s>)",
token_uri, token);
extra_headers = apr_hash_make(pool);
apr_hash_set(extra_headers, "If", APR_HASH_KEY_STRING,
token_header_val);
}
}
if (parent->cc->keep_locks)
{
apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER,
APR_HASH_KEY_STRING, SVN_DAV_OPTION_KEEP_LOCKS);
}
serr = svn_ra_neon__simple_request(&code, parent->cc->ras,
"DELETE", delete_target,
extra_headers, NULL,
204 ,
0, pool);
if (serr && ((serr->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
|| (serr->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN)
|| (serr->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH)
|| (serr->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED)))
{
apr_hash_t *child_tokens = NULL;
svn_ra_neon__request_t *request;
const char *body;
const char *token;
svn_stringbuf_t *locks_list;
svn_error_t *err = SVN_NO_ERROR;
if (parent->cc->lock_tokens)
child_tokens = get_child_tokens(parent->cc->lock_tokens, path, pool);
if ((! child_tokens) || (apr_hash_count(child_tokens) == 0))
return serr;
else
svn_error_clear(serr);
if ((token = apr_hash_get(parent->cc->lock_tokens, path,
APR_HASH_KEY_STRING)))
{
apr_hash_set(child_tokens, path, APR_HASH_KEY_STRING, token);
}
SVN_ERR(svn_ra_neon__request_create(&request, parent->cc->ras, "DELETE",
delete_target, pool));
err = svn_ra_neon__assemble_locktoken_body(&locks_list,
child_tokens, request->pool);
if (err)
goto cleanup;
body = apr_psprintf(request->pool,
"<?xml version=\"1.0\" encoding=\"utf-8\"?> %s",
locks_list->data);
err = svn_ra_neon__request_dispatch(&code, request, NULL, body,
204 ,
404 ,
pool);
cleanup:
svn_ra_neon__request_destroy(request);
SVN_ERR(err);
}
else if (serr)
return serr;
add_valid_target(parent->cc, path, svn_nonrecursive);
return SVN_NO_ERROR;
}
static svn_error_t * commit_add_dir(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *dir_pool,
void **child_baton)
{
resource_baton_t *parent = parent_baton;
resource_baton_t *child;
int code;
const char *name = svn_relpath_basename(path, dir_pool);
apr_pool_t *workpool = svn_pool_create(dir_pool);
version_rsrc_t *rsrc = NULL;
const char *mkcol_target = NULL;
child = apr_pcalloc(dir_pool, sizeof(*child));
child->pool = dir_pool;
child->base_revision = SVN_INVALID_REVNUM;
child->cc = parent->cc;
child->created = TRUE;
child->local_relpath = svn_relpath_join(parent->local_relpath,
name, dir_pool);
if (USING_HTTPV2_COMMIT_SUPPORT(parent->cc))
{
child->rsrc = NULL;
child->txn_root_url = svn_path_url_add_component2(parent->txn_root_url,
name, dir_pool);
mkcol_target = child->txn_root_url;
}
else
{
SVN_ERR(checkout_resource(parent->cc, parent->local_relpath,
parent->rsrc, TRUE, NULL, FALSE, dir_pool));
SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc, parent->local_relpath,
name, 1, SVN_INVALID_REVNUM, workpool));
child->rsrc = dup_resource(rsrc, dir_pool);
child->txn_root_url = NULL;
mkcol_target = child->rsrc->wr_url;
}
if (! copyfrom_path)
{
SVN_ERR(svn_ra_neon__simple_request(&code, parent->cc->ras, "MKCOL",
mkcol_target, NULL, NULL,
201 , 0, workpool));
}
else
{
SVN_ERR(copy_resource(parent->cc->ras, copyfrom_path, copyfrom_revision,
mkcol_target, workpool));
child->copied = TRUE;
}
add_valid_target(parent->cc, path,
copyfrom_path ? svn_recursive : svn_nonrecursive);
svn_pool_destroy(workpool);
*child_baton = child;
return SVN_NO_ERROR;
}
static svn_error_t * commit_open_dir(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *dir_pool,
void **child_baton)
{
resource_baton_t *parent = parent_baton;
resource_baton_t *child = apr_pcalloc(dir_pool, sizeof(*child));
const char *name = svn_relpath_basename(path, dir_pool);
version_rsrc_t *rsrc = NULL;
child->pool = dir_pool;
child->base_revision = base_revision;
child->cc = parent->cc;
child->created = FALSE;
child->local_relpath = svn_relpath_join(parent->local_relpath,
name, dir_pool);
if (USING_HTTPV2_COMMIT_SUPPORT(parent->cc))
{
child->rsrc = NULL;
child->txn_root_url = svn_path_url_add_component2(parent->txn_root_url,
name, dir_pool);
}
else
{
apr_pool_t *workpool = svn_pool_create(dir_pool);
SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc, parent->local_relpath,
name, 0, base_revision, workpool));
child->rsrc = dup_resource(rsrc, dir_pool);
child->txn_root_url = NULL;
svn_pool_destroy(workpool);
}
*child_baton = child;
return SVN_NO_ERROR;
}
static svn_error_t * commit_change_dir_prop(void *dir_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
resource_baton_t *dir = dir_baton;
record_prop_change(dir->pool, dir, name, value);
if (! USING_HTTPV2_COMMIT_SUPPORT(dir->cc))
{
SVN_ERR(checkout_resource(dir->cc, dir->local_relpath, dir->rsrc,
TRUE, NULL, FALSE, pool));
}
add_valid_target(dir->cc, dir->local_relpath, svn_nonrecursive);
return SVN_NO_ERROR;
}
static svn_error_t * commit_close_dir(void *dir_baton,
apr_pool_t *pool)
{
resource_baton_t *dir = dir_baton;
return do_proppatch(dir, pool);
}
static svn_error_t * commit_add_file(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *file_pool,
void **file_baton)
{
resource_baton_t *parent = parent_baton;
resource_baton_t *file;
const char *name = svn_relpath_basename(path, file_pool);
apr_pool_t *workpool = svn_pool_create(file_pool);
const char *put_target = NULL;
file = apr_pcalloc(file_pool, sizeof(*file));
file->base_revision = SVN_INVALID_REVNUM;
file->pool = file_pool;
file->cc = parent->cc;
file->created = TRUE;
file->local_relpath = svn_relpath_join(parent->local_relpath,
name, file_pool);
if (USING_HTTPV2_COMMIT_SUPPORT(parent->cc))
{
file->rsrc = NULL;
file->txn_root_url = svn_path_url_add_component2(parent->txn_root_url,
name, file_pool);
put_target = file->txn_root_url;
}
else
{
version_rsrc_t *rsrc = NULL;
SVN_ERR(checkout_resource(parent->cc, parent->local_relpath, parent->rsrc,
TRUE, NULL, FALSE, workpool));
SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc, parent->local_relpath,
name, 1, SVN_INVALID_REVNUM, workpool));
file->rsrc = dup_resource(rsrc, file_pool);
file->txn_root_url = NULL;
put_target = file->rsrc->wr_url;
}
if (parent->cc->lock_tokens)
file->token = apr_hash_get(parent->cc->lock_tokens, path,
APR_HASH_KEY_STRING);
if ((! parent->created)
&& (! apr_hash_get(file->cc->valid_targets, path, APR_HASH_KEY_STRING)))
{
static const ne_propname restype_props[] =
{
{ "DAV:", "resourcetype" },
{ NULL }
};
svn_ra_neon__resource_t *res;
const char *public_url;
svn_error_t *err1, *err2;
public_url = svn_path_url_add_component2(file->cc->ras->url->data,
path, workpool);
err1 = svn_ra_neon__get_props_resource(&res, parent->cc->ras, put_target,
NULL, restype_props, workpool);
err2 = svn_ra_neon__get_props_resource(&res, parent->cc->ras, public_url,
NULL, restype_props, workpool);
if (! err1 && ! err2)
{
return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
_("File '%s' already exists"), path);
}
else if ((err1 && (err1->apr_err == SVN_ERR_FS_NOT_FOUND))
|| (err2 && (err2->apr_err == SVN_ERR_FS_NOT_FOUND)))
{
svn_error_clear(err1);
svn_error_clear(err2);
}
else
{
return svn_error_compose_create(err1, err2);
}
}
if (! copyfrom_path)
{
}
else
{
SVN_ERR(copy_resource(parent->cc->ras, copyfrom_path, copyfrom_revision,
put_target, workpool));
file->copied = TRUE;
}
add_valid_target(parent->cc, path, svn_nonrecursive);
svn_pool_destroy(workpool);
*file_baton = file;
return SVN_NO_ERROR;
}
static svn_error_t * commit_open_file(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *file_pool,
void **file_baton)
{
resource_baton_t *parent = parent_baton;
resource_baton_t *file;
const char *name = svn_relpath_basename(path, file_pool);
file = apr_pcalloc(file_pool, sizeof(*file));
file->pool = file_pool;
file->base_revision = base_revision;
file->cc = parent->cc;
file->created = FALSE;
file->local_relpath = svn_relpath_join(parent->local_relpath,
name, file_pool);
if (parent->cc->lock_tokens)
file->token = apr_hash_get(parent->cc->lock_tokens, path,
APR_HASH_KEY_STRING);
if (USING_HTTPV2_COMMIT_SUPPORT(parent->cc))
{
file->rsrc = NULL;
file->txn_root_url = svn_path_url_add_component2(parent->txn_root_url,
name, file_pool);
}
else
{
version_rsrc_t *rsrc = NULL;
apr_pool_t *workpool = svn_pool_create(file_pool);
SVN_ERR(add_child(&rsrc, parent->cc, parent->rsrc, parent->local_relpath,
name, 0, base_revision, workpool));
file->rsrc = dup_resource(rsrc, file_pool);
file->txn_root_url = NULL;
SVN_ERR(checkout_resource(parent->cc, file->local_relpath, file->rsrc,
TRUE, file->token, FALSE, workpool));
svn_pool_destroy(workpool);
}
*file_baton = file;
return SVN_NO_ERROR;
}
static svn_error_t * commit_stream_write(void *baton,
const char *data,
apr_size_t *len)
{
put_baton_t *pb = baton;
svn_ra_neon__session_t *ras = pb->ras;
apr_status_t status;
if (ras->callbacks && ras->callbacks->cancel_func)
SVN_ERR(ras->callbacks->cancel_func(ras->callback_baton));
status = apr_file_write_full(pb->tmpfile, data, *len, NULL);
if (status)
return svn_error_wrap_apr(status,
_("Could not write svndiff to temp file"));
if (ras->progress_func)
{
pb->progress += *len;
ras->progress_func(pb->progress, -1, ras->progress_baton, pb->pool);
}
return SVN_NO_ERROR;
}
static svn_error_t *
commit_apply_txdelta(void *file_baton,
const char *base_checksum,
apr_pool_t *pool,
svn_txdelta_window_handler_t *handler,
void **handler_baton)
{
resource_baton_t *file = file_baton;
put_baton_t *baton;
svn_stream_t *stream;
baton = apr_pcalloc(file->pool, sizeof(*baton));
baton->ras = file->cc->ras;
baton->pool = file->pool;
file->put_baton = baton;
if (base_checksum)
baton->base_checksum = apr_pstrdup(file->pool, base_checksum);
else
baton->base_checksum = NULL;
SVN_ERR(svn_io_open_unique_file3(&baton->tmpfile, NULL, NULL,
svn_io_file_del_on_pool_cleanup,
file->pool, pool));
stream = svn_stream_create(baton, pool);
svn_stream_set_write(stream, commit_stream_write);
svn_txdelta_to_svndiff2(handler, handler_baton, stream, 0, pool);
add_valid_target(file->cc, file->local_relpath, svn_nonrecursive);
return SVN_NO_ERROR;
}
static svn_error_t * commit_change_file_prop(void *file_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
resource_baton_t *file = file_baton;
record_prop_change(file->pool, file, name, value);
if (! USING_HTTPV2_COMMIT_SUPPORT(file->cc))
{
SVN_ERR(checkout_resource(file->cc, file->local_relpath, file->rsrc,
TRUE, file->token, FALSE, pool));
}
add_valid_target(file->cc, file->local_relpath, svn_nonrecursive);
return SVN_NO_ERROR;
}
static svn_error_t * commit_close_file(void *file_baton,
const char *text_checksum,
apr_pool_t *pool)
{
resource_baton_t *file = file_baton;
commit_ctx_t *cc = file->cc;
if ((! file->put_baton) && file->created && (! file->copied))
{
file->put_baton = apr_pcalloc(file->pool, sizeof(*(file->put_baton)));
}
if (file->put_baton)
{
svn_error_t *err = SVN_NO_ERROR;
put_baton_t *pb = file->put_baton;
apr_hash_t *extra_headers;
svn_ra_neon__request_t *request;
int code;
const char *public_url =
svn_path_url_add_component2(file->cc->ras->url->data,
file->local_relpath, pool);
const char *put_target =
USING_HTTPV2_COMMIT_SUPPORT(cc) ? file->txn_root_url
: file->rsrc->wr_url;
SVN_ERR(svn_ra_neon__request_create(&request, cc->ras, "PUT",
put_target, pool));
extra_headers = apr_hash_make(request->pool);
if (file->token)
svn_ra_neon__set_header(extra_headers, "If",
apr_psprintf(pool, "<%s> (<%s>)",
public_url, file->token));
if (pb->base_checksum)
svn_ra_neon__set_header(extra_headers,
SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
pb->base_checksum);
if (text_checksum)
svn_ra_neon__set_header(extra_headers,
SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
text_checksum);
if (SVN_IS_VALID_REVNUM(file->base_revision))
svn_ra_neon__set_header(extra_headers,
SVN_DAV_VERSION_NAME_HEADER,
apr_psprintf(pool, "%ld", file->base_revision));
if (pb->tmpfile)
{
svn_ra_neon__set_header(extra_headers, "Content-Type",
SVN_SVNDIFF_MIME_TYPE);
err = svn_ra_neon__set_neon_body_provider(request, pb->tmpfile);
if (err)
goto cleanup;
}
else
{
ne_set_request_body_buffer(request->ne_req, "", 0);
}
err = svn_ra_neon__request_dispatch(&code, request, extra_headers, NULL,
201 ,
204 ,
pool);
if (err && (err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED))
{
switch (code)
{
case 423:
err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
_("No lock on path '%s'"
" (Status %d on PUT Request)"),
put_target, code);
default:
break;
}
}
cleanup:
svn_ra_neon__request_destroy(request);
SVN_ERR(err);
if (pb->tmpfile)
{
(void) apr_file_close(pb->tmpfile);
}
}
return do_proppatch(file, pool);
}
static svn_error_t * commit_close_edit(void *edit_baton,
apr_pool_t *pool)
{
commit_ctx_t *cc = edit_baton;
svn_commit_info_t *commit_info = svn_create_commit_info(pool);
const char *merge_resource_url =
USING_HTTPV2_COMMIT_SUPPORT(cc) ? cc->txn_url : cc->activity_url;
SVN_ERR(svn_ra_neon__merge_activity(&(commit_info->revision),
&(commit_info->date),
&(commit_info->author),
&(commit_info->post_commit_err),
cc->ras,
cc->ras->root.path,
merge_resource_url,
cc->valid_targets,
cc->lock_tokens,
cc->keep_locks,
cc->ras->callbacks->push_wc_prop == NULL,
pool));
if (cc->activity_url)
{
SVN_ERR(svn_ra_neon__simple_request(NULL, cc->ras, "DELETE",
cc->activity_url, NULL, NULL,
204 ,
404 , pool));
}
if (cc->callback && commit_info->revision != SVN_INVALID_REVNUM)
SVN_ERR(cc->callback(commit_info, cc->callback_baton, pool));
return SVN_NO_ERROR;
}
static svn_error_t * commit_abort_edit(void *edit_baton,
apr_pool_t *pool)
{
commit_ctx_t *cc = edit_baton;
const char *delete_target = NULL;
if (USING_HTTPV2_COMMIT_SUPPORT(cc))
delete_target = cc->txn_url;
else
delete_target = cc->activity_url;
if (delete_target)
SVN_ERR(svn_ra_neon__simple_request(NULL, cc->ras, "DELETE",
delete_target, NULL, NULL,
204 ,
404 , pool));
return SVN_NO_ERROR;
}
svn_error_t * svn_ra_neon__get_commit_editor(svn_ra_session_t *session,
const svn_delta_editor_t **editor,
void **edit_baton,
apr_hash_t *revprop_table,
svn_commit_callback2_t callback,
void *callback_baton,
apr_hash_t *lock_tokens,
svn_boolean_t keep_locks,
apr_pool_t *pool)
{
svn_ra_neon__session_t *ras = session->priv;
svn_delta_editor_t *commit_editor;
commit_ctx_t *cc;
apr_hash_index_t *hi;
cc = apr_pcalloc(pool, sizeof(*cc));
cc->pool = pool;
cc->ras = ras;
cc->valid_targets = apr_hash_make(pool);
cc->callback = callback;
cc->callback_baton = callback_baton;
cc->lock_tokens = lock_tokens;
cc->keep_locks = keep_locks;
cc->revprop_table = apr_hash_make(pool);
for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
{
const void *key;
apr_ssize_t klen;
void *val;
apr_hash_this(hi, &key, &klen, &val);
apr_hash_set(cc->revprop_table, apr_pstrdup(pool, key), klen,
svn_string_dup(val, pool));
}
SVN_ERR(svn_ra_neon__get_path_relative_to_root(session,
&(cc->anchor_relpath),
ras->url->data, pool));
commit_editor = svn_delta_default_editor(pool);
commit_editor->open_root = commit_open_root;
commit_editor->delete_entry = commit_delete_entry;
commit_editor->add_directory = commit_add_dir;
commit_editor->open_directory = commit_open_dir;
commit_editor->change_dir_prop = commit_change_dir_prop;
commit_editor->close_directory = commit_close_dir;
commit_editor->add_file = commit_add_file;
commit_editor->open_file = commit_open_file;
commit_editor->apply_textdelta = commit_apply_txdelta;
commit_editor->change_file_prop = commit_change_file_prop;
commit_editor->close_file = commit_close_file;
commit_editor->close_edit = commit_close_edit;
commit_editor->abort_edit = commit_abort_edit;
*editor = commit_editor;
*edit_baton = cc;
return SVN_NO_ERROR;
}