diff options
Diffstat (limited to 'subversion/libsvn_client/mtcc.c')
-rw-r--r-- | subversion/libsvn_client/mtcc.c | 1429 |
1 files changed, 1429 insertions, 0 deletions
diff --git a/subversion/libsvn_client/mtcc.c b/subversion/libsvn_client/mtcc.c new file mode 100644 index 000000000000..e0fc1e9441b0 --- /dev/null +++ b/subversion/libsvn_client/mtcc.c @@ -0,0 +1,1429 @@ +/* + * mtcc.c -- Multi Command Context implementation. This allows + * performing many operations without a working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "svn_subst.h" + +#include "private/svn_client_mtcc.h" + + +#include "svn_private_config.h" + +#include "client.h" + +#include <assert.h> + +#define SVN_PATH_IS_EMPTY(s) ((s)[0] == '\0') + +/* The kind of operation to perform in an mtcc_op_t */ +typedef enum mtcc_kind_t +{ + OP_OPEN_DIR, + OP_OPEN_FILE, + OP_ADD_DIR, + OP_ADD_FILE, + OP_DELETE +} mtcc_kind_t; + +typedef struct mtcc_op_t +{ + const char *name; /* basename of operation */ + mtcc_kind_t kind; /* editor operation */ + + apr_array_header_t *children; /* List of mtcc_op_t * */ + + const char *src_relpath; /* For ADD_DIR, ADD_FILE */ + svn_revnum_t src_rev; /* For ADD_DIR, ADD_FILE */ + svn_stream_t *src_stream; /* For ADD_FILE, OPEN_FILE */ + svn_checksum_t *src_checksum; /* For ADD_FILE, OPEN_FILE */ + svn_stream_t *base_stream; /* For ADD_FILE, OPEN_FILE */ + const svn_checksum_t *base_checksum; /* For ADD_FILE, OPEN_FILE */ + + apr_array_header_t *prop_mods; /* For all except DELETE + List of svn_prop_t */ + + svn_boolean_t performed_stat; /* Verified kind with repository */ +} mtcc_op_t; + +/* Check if the mtcc doesn't contain any modifications yet */ +#define MTCC_UNMODIFIED(mtcc) \ + ((mtcc->root_op->kind == OP_OPEN_DIR \ + || mtcc->root_op->kind == OP_OPEN_FILE) \ + && (mtcc->root_op->prop_mods == NULL \ + || !mtcc->root_op->prop_mods->nelts) \ + && (mtcc->root_op->children == NULL \ + || !mtcc->root_op->children->nelts)) + +struct svn_client__mtcc_t +{ + apr_pool_t *pool; + svn_revnum_t head_revision; + svn_revnum_t base_revision; + + svn_ra_session_t *ra_session; + svn_client_ctx_t *ctx; + + mtcc_op_t *root_op; +}; + +static mtcc_op_t * +mtcc_op_create(const char *name, + svn_boolean_t add, + svn_boolean_t directory, + apr_pool_t *result_pool) +{ + mtcc_op_t *op; + + op = apr_pcalloc(result_pool, sizeof(*op)); + op->name = name ? apr_pstrdup(result_pool, name) : ""; + + if (add) + op->kind = directory ? OP_ADD_DIR : OP_ADD_FILE; + else + op->kind = directory ? OP_OPEN_DIR : OP_OPEN_FILE; + + if (directory) + op->children = apr_array_make(result_pool, 4, sizeof(mtcc_op_t *)); + + op->src_rev = SVN_INVALID_REVNUM; + + return op; +} + +static svn_error_t * +mtcc_op_find(mtcc_op_t **op, + svn_boolean_t *created, + const char *relpath, + mtcc_op_t *base_op, + svn_boolean_t find_existing, + svn_boolean_t find_deletes, + svn_boolean_t create_file, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *name; + const char *child; + int i; + + assert(svn_relpath_is_canonical(relpath)); + if (created) + *created = FALSE; + + if (SVN_PATH_IS_EMPTY(relpath)) + { + if (find_existing) + *op = base_op; + else + *op = NULL; + + return SVN_NO_ERROR; + } + + child = strchr(relpath, '/'); + + if (child) + { + name = apr_pstrmemdup(scratch_pool, relpath, (child-relpath)); + child++; /* Skip '/' */ + } + else + name = relpath; + + if (!base_op->children) + { + if (!created) + { + *op = NULL; + return SVN_NO_ERROR; + } + else + return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't operate on '%s' because '%s' is not a " + "directory"), + name, base_op->name); + } + + for (i = base_op->children->nelts-1; i >= 0 ; i--) + { + mtcc_op_t *cop; + + cop = APR_ARRAY_IDX(base_op->children, i, mtcc_op_t *); + + if (! strcmp(cop->name, name) + && (find_deletes || cop->kind != OP_DELETE)) + { + return svn_error_trace( + mtcc_op_find(op, created, child ? child : "", cop, + find_existing, find_deletes, create_file, + result_pool, scratch_pool)); + } + } + + if (!created) + { + *op = NULL; + return SVN_NO_ERROR; + } + + { + mtcc_op_t *cop; + + cop = mtcc_op_create(name, FALSE, child || !create_file, result_pool); + + APR_ARRAY_PUSH(base_op->children, mtcc_op_t *) = cop; + + if (!child) + { + *op = cop; + *created = TRUE; + return SVN_NO_ERROR; + } + + return svn_error_trace( + mtcc_op_find(op, created, child, cop, find_existing, + find_deletes, create_file, + result_pool, scratch_pool)); + } +} + +/* Gets the original repository location of RELPATH, checking things + like copies, moves, etc. */ +static svn_error_t * +get_origin(svn_boolean_t *done, + const char **origin_relpath, + svn_revnum_t *rev, + mtcc_op_t *op, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *child; + const char *name; + if (SVN_PATH_IS_EMPTY(relpath)) + { + if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) + *done = TRUE; + *origin_relpath = op->src_relpath + ? apr_pstrdup(result_pool, op->src_relpath) + : NULL; + *rev = op->src_rev; + return SVN_NO_ERROR; + } + + child = strchr(relpath, '/'); + if (child) + { + name = apr_pstrmemdup(scratch_pool, relpath, child-relpath); + child++; /* Skip '/' */ + } + else + name = relpath; + + if (op->children && op->children->nelts) + { + int i; + + for (i = op->children->nelts-1; i >= 0; i--) + { + mtcc_op_t *cop; + + cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); + + if (! strcmp(cop->name, name)) + { + if (cop->kind == OP_DELETE) + { + *done = TRUE; + return SVN_NO_ERROR; + } + + SVN_ERR(get_origin(done, origin_relpath, rev, + cop, child ? child : "", + result_pool, scratch_pool)); + + if (*origin_relpath || *done) + return SVN_NO_ERROR; + + break; + } + } + } + + if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) + { + *done = TRUE; + if (op->src_relpath) + { + *origin_relpath = svn_relpath_join(op->src_relpath, relpath, + result_pool); + *rev = op->src_rev; + } + } + + return SVN_NO_ERROR; +} + +/* Obtains the original repository location for an mtcc relpath as + *ORIGIN_RELPATH @ *REV, if it has one. If it has not and IGNORE_ENOENT + is TRUE report *ORIGIN_RELPATH as NULL, otherwise return an error */ +static svn_error_t * +mtcc_get_origin(const char **origin_relpath, + svn_revnum_t *rev, + const char *relpath, + svn_boolean_t ignore_enoent, + svn_client__mtcc_t *mtcc, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t done = FALSE; + + *origin_relpath = NULL; + *rev = SVN_INVALID_REVNUM; + + SVN_ERR(get_origin(&done, origin_relpath, rev, mtcc->root_op, relpath, + result_pool, scratch_pool)); + + if (!*origin_relpath && !done) + { + *origin_relpath = apr_pstrdup(result_pool, relpath); + *rev = mtcc->base_revision; + } + else if (!ignore_enoent) + { + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("No origin found for node at '%s'"), + relpath); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_create(svn_client__mtcc_t **mtcc, + const char *anchor_url, + svn_revnum_t base_revision, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *mtcc_pool; + + mtcc_pool = svn_pool_create(result_pool); + + *mtcc = apr_pcalloc(mtcc_pool, sizeof(**mtcc)); + (*mtcc)->pool = mtcc_pool; + + (*mtcc)->root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc_pool); + + (*mtcc)->ctx = ctx; + + SVN_ERR(svn_client_open_ra_session2(&(*mtcc)->ra_session, anchor_url, + NULL /* wri_abspath */, ctx, + mtcc_pool, scratch_pool)); + + SVN_ERR(svn_ra_get_latest_revnum((*mtcc)->ra_session, &(*mtcc)->head_revision, + scratch_pool)); + + if (SVN_IS_VALID_REVNUM(base_revision)) + (*mtcc)->base_revision = base_revision; + else + (*mtcc)->base_revision = (*mtcc)->head_revision; + + if ((*mtcc)->base_revision > (*mtcc)->head_revision) + return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, + _("No such revision %ld (HEAD is %ld)"), + base_revision, (*mtcc)->head_revision); + + return SVN_NO_ERROR; +} + +static svn_error_t * +update_copy_src(mtcc_op_t *op, + const char *add_relpath, + apr_pool_t *result_pool) +{ + int i; + + if (op->src_relpath) + op->src_relpath = svn_relpath_join(add_relpath, op->src_relpath, + result_pool); + + if (!op->children) + return SVN_NO_ERROR; + + for (i = 0; i < op->children->nelts; i++) + { + mtcc_op_t *cop; + + cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); + + SVN_ERR(update_copy_src(cop, add_relpath, result_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +mtcc_reparent(const char *new_anchor_url, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + const char *session_url; + const char *up; + + SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, + scratch_pool)); + + up = svn_uri_skip_ancestor(new_anchor_url, session_url, scratch_pool); + + if (! up) + { + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("'%s' is not an ancestor of '%s'"), + new_anchor_url, session_url); + } + else if (!*up) + { + return SVN_NO_ERROR; /* Same url */ + } + + /* Update copy origins recursively...:( */ + SVN_ERR(update_copy_src(mtcc->root_op, up, mtcc->pool)); + + SVN_ERR(svn_ra_reparent(mtcc->ra_session, new_anchor_url, scratch_pool)); + + /* Create directory open operations for new ancestors */ + while (*up) + { + mtcc_op_t *root_op; + + mtcc->root_op->name = svn_relpath_basename(up, mtcc->pool); + up = svn_relpath_dirname(up, scratch_pool); + + root_op = mtcc_op_create(NULL, FALSE, TRUE, mtcc->pool); + + APR_ARRAY_PUSH(root_op->children, mtcc_op_t *) = mtcc->root_op; + + mtcc->root_op = root_op; + } + + return SVN_NO_ERROR; +} + +/* Check if it is safe to create a new node at NEW_RELPATH. Return a proper + error if it is not */ +static svn_error_t * +mtcc_verify_create(svn_client__mtcc_t *mtcc, + const char *new_relpath, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + + if (*new_relpath || !MTCC_UNMODIFIED(mtcc)) + { + mtcc_op_t *op; + + SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, FALSE, + FALSE, mtcc->pool, scratch_pool)); + + if (op) + return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Path '%s' already exists"), + new_relpath); + + SVN_ERR(mtcc_op_find(&op, NULL, new_relpath, mtcc->root_op, TRUE, TRUE, + FALSE, mtcc->pool, scratch_pool)); + + if (op) + return SVN_NO_ERROR; /* Node is explicitly deleted. We can replace */ + } + + /* mod_dav_svn used to allow overwriting existing directories. Let's hide + that for users of this api */ + SVN_ERR(svn_client__mtcc_check_path(&kind, new_relpath, FALSE, + mtcc, scratch_pool)); + + if (kind != svn_node_none) + return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Path '%s' already exists"), + new_relpath); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__mtcc_add_add_file(const char *relpath, + svn_stream_t *src_stream, + const svn_checksum_t *src_checksum, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + svn_boolean_t created; + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); + + SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); + + if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) + { + /* Turn the root operation into a file addition */ + op = mtcc->root_op; + } + else + { + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, + TRUE, mtcc->pool, scratch_pool)); + + if (!op || !created) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't add file at '%s'"), + relpath); + } + } + + op->kind = OP_ADD_FILE; + op->src_stream = src_stream; + op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) + : NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_copy(const char *src_relpath, + svn_revnum_t revision, + const char *dst_relpath, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + svn_boolean_t created; + svn_node_kind_t kind; + + SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath) + && svn_relpath_is_canonical(dst_relpath)); + + if (! SVN_IS_VALID_REVNUM(revision)) + revision = mtcc->head_revision; + else if (revision > mtcc->head_revision) + { + return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, + _("No such revision %ld"), revision); + } + + SVN_ERR(mtcc_verify_create(mtcc, dst_relpath, scratch_pool)); + + /* Subversion requires the kind of a copy */ + SVN_ERR(svn_ra_check_path(mtcc->ra_session, src_relpath, revision, &kind, + scratch_pool)); + + if (kind != svn_node_dir && kind != svn_node_file) + { + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Path '%s' not found in revision %ld"), + src_relpath, revision); + } + + SVN_ERR(mtcc_op_find(&op, &created, dst_relpath, mtcc->root_op, FALSE, FALSE, + (kind == svn_node_file), mtcc->pool, scratch_pool)); + + if (!op || !created) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't add node at '%s'"), + dst_relpath); + } + + op->kind = (kind == svn_node_file) ? OP_ADD_FILE : OP_ADD_DIR; + op->src_relpath = apr_pstrdup(mtcc->pool, src_relpath); + op->src_rev = revision; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_delete(const char *relpath, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + svn_boolean_t created; + svn_node_kind_t kind; + + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); + + SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, + mtcc, scratch_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Can't delete node at '%s' as it " + "does not exist"), + relpath); + + if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) + { + /* Turn root operation into delete */ + op = mtcc->root_op; + } + else + { + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, TRUE, + TRUE, mtcc->pool, scratch_pool)); + + if (!op || !created) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't delete node at '%s'"), + relpath); + } + } + + op->kind = OP_DELETE; + op->children = NULL; + op->prop_mods = NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_mkdir(const char *relpath, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + svn_boolean_t created; + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); + + SVN_ERR(mtcc_verify_create(mtcc, relpath, scratch_pool)); + + if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) + { + /* Turn the root of the operation in an MKDIR */ + mtcc->root_op->kind = OP_ADD_DIR; + + return SVN_NO_ERROR; + } + + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, FALSE, FALSE, + FALSE, mtcc->pool, scratch_pool)); + + if (!op || !created) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't create directory at '%s'"), + relpath); + } + + op->kind = OP_ADD_DIR; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_move(const char *src_relpath, + const char *dst_relpath, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + const char *origin_relpath; + svn_revnum_t origin_rev; + + SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, + src_relpath, FALSE, mtcc, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_client__mtcc_add_copy(src_relpath, mtcc->base_revision, + dst_relpath, mtcc, scratch_pool)); + SVN_ERR(svn_client__mtcc_add_delete(src_relpath, mtcc, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Baton for mtcc_prop_getter */ +struct mtcc_prop_get_baton +{ + svn_client__mtcc_t *mtcc; + const char *relpath; + svn_cancel_func_t cancel_func; + void *cancel_baton; +}; + +/* Implements svn_wc_canonicalize_svn_prop_get_file_t */ +static svn_error_t * +mtcc_prop_getter(const svn_string_t **mime_type, + svn_stream_t *stream, + void *baton, + apr_pool_t *pool) +{ + struct mtcc_prop_get_baton *mpgb = baton; + const char *origin_relpath; + svn_revnum_t origin_rev; + apr_hash_t *props = NULL; + + mtcc_op_t *op; + + if (mime_type) + *mime_type = NULL; + + /* Check if we have the information locally */ + SVN_ERR(mtcc_op_find(&op, NULL, mpgb->relpath, mpgb->mtcc->root_op, TRUE, + FALSE, FALSE, pool, pool)); + + if (op) + { + if (mime_type) + { + int i; + + for (i = 0; op->prop_mods && i < op->prop_mods->nelts; i++) + { + const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, + svn_prop_t); + + if (! strcmp(mod->name, SVN_PROP_MIME_TYPE)) + { + *mime_type = svn_string_dup(mod->value, pool); + mime_type = NULL; + } + } + } + + if (stream && op->src_stream) + { + svn_stream_mark_t *mark; + svn_error_t *err; + + /* Is the source stream capable of being read multiple times? */ + err = svn_stream_mark(op->src_stream, &mark, pool); + + if (err && err->apr_err != SVN_ERR_STREAM_SEEK_NOT_SUPPORTED) + return svn_error_trace(err); + svn_error_clear(err); + + if (!err) + { + err = svn_stream_copy3(svn_stream_disown(op->src_stream, pool), + svn_stream_disown(stream, pool), + mpgb->cancel_func, mpgb->cancel_baton, + pool); + + SVN_ERR(svn_error_compose_create( + err, + svn_stream_seek(op->src_stream, mark))); + } + /* else: ### Create tempfile? */ + + stream = NULL; /* Stream is handled */ + } + } + + if (!stream && !mime_type) + return SVN_NO_ERROR; + + SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, mpgb->relpath, TRUE, + mpgb->mtcc, pool, pool)); + + if (!origin_relpath) + return SVN_NO_ERROR; /* Nothing to fetch at repository */ + + SVN_ERR(svn_ra_get_file(mpgb->mtcc->ra_session, origin_relpath, origin_rev, + stream, NULL, mime_type ? &props : NULL, pool)); + + if (mime_type && props) + *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_propset(const char *relpath, + const char *propname, + const svn_string_t *propval, + svn_boolean_t skip_checks, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); + + if (! svn_prop_name_is_valid(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("Bad property name: '%s'"), propname); + + if (svn_prop_is_known_svn_rev_prop(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("Revision property '%s' not allowed " + "in this context"), propname); + + if (svn_property_kind2(propname) == svn_prop_wc_kind) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is a wcprop, thus not accessible " + "to clients"), propname); + + if (!skip_checks && svn_prop_needs_translation(propname)) + { + svn_string_t *translated_value; + SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL, + NULL, propval, + NULL, FALSE, + scratch_pool, scratch_pool), + _("Error normalizing property value")); + + propval = translated_value; + } + + if (propval && svn_prop_is_svn_prop(propname)) + { + struct mtcc_prop_get_baton mpbg; + svn_node_kind_t kind; + SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, mtcc, + scratch_pool)); + + mpbg.mtcc = mtcc; + mpbg.relpath = relpath; + mpbg.cancel_func = mtcc->ctx->cancel_func; + mpbg.cancel_baton = mtcc->ctx->cancel_baton; + + SVN_ERR(svn_wc_canonicalize_svn_prop(&propval, propname, propval, + relpath, kind, skip_checks, + mtcc_prop_getter, &mpbg, + scratch_pool)); + } + + if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc)) + { + svn_node_kind_t kind; + + /* Probing the node for an unmodified root will fix the node type to + a file if necessary */ + + SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, + mtcc, scratch_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't set properties at not existing '%s'"), + relpath); + + op = mtcc->root_op; + } + else + { + SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, + FALSE, mtcc->pool, scratch_pool)); + + if (!op) + { + svn_node_kind_t kind; + svn_boolean_t created; + + /* ### TODO: Check if this node is within a newly copied directory, + and update origin values accordingly */ + + SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, + mtcc, scratch_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't set properties at not existing '%s'"), + relpath); + + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, + (kind != svn_node_dir), + mtcc->pool, scratch_pool)); + + SVN_ERR_ASSERT(op != NULL); + } + } + + if (!op->prop_mods) + op->prop_mods = apr_array_make(mtcc->pool, 4, sizeof(svn_prop_t)); + + { + svn_prop_t propchange; + propchange.name = apr_pstrdup(mtcc->pool, propname); + + if (propval) + propchange.value = svn_string_dup(propval, mtcc->pool); + else + propchange.value = NULL; + + APR_ARRAY_PUSH(op->prop_mods, svn_prop_t) = propchange; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_add_update_file(const char *relpath, + svn_stream_t *src_stream, + const svn_checksum_t *src_checksum, + svn_stream_t *base_stream, + const svn_checksum_t *base_checksum, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + mtcc_op_t *op; + svn_boolean_t created; + svn_node_kind_t kind; + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath) && src_stream); + + SVN_ERR(svn_client__mtcc_check_path(&kind, relpath, FALSE, + mtcc, scratch_pool)); + + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, + _("Can't update '%s' because it is not a file"), + relpath); + + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, + TRUE, mtcc->pool, scratch_pool)); + + if (!op + || (op->kind != OP_OPEN_FILE && op->kind != OP_ADD_FILE) + || (op->src_stream != NULL)) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't update file at '%s'"), relpath); + } + + op->src_stream = src_stream; + op->src_checksum = src_checksum ? svn_checksum_dup(src_checksum, mtcc->pool) + : NULL; + + op->base_stream = base_stream; + op->base_checksum = base_checksum ? svn_checksum_dup(base_checksum, + mtcc->pool) + : NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_check_path(svn_node_kind_t *kind, + const char *relpath, + svn_boolean_t check_repository, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + const char *origin_relpath; + svn_revnum_t origin_rev; + mtcc_op_t *op; + + SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); + + if (SVN_PATH_IS_EMPTY(relpath) && MTCC_UNMODIFIED(mtcc) + && !mtcc->root_op->performed_stat) + { + /* We know nothing about the root. Perhaps it is a file? */ + SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, + kind, scratch_pool)); + + mtcc->root_op->performed_stat = TRUE; + if (*kind == svn_node_file) + { + mtcc->root_op->kind = OP_OPEN_FILE; + mtcc->root_op->children = NULL; + } + return SVN_NO_ERROR; + } + + SVN_ERR(mtcc_op_find(&op, NULL, relpath, mtcc->root_op, TRUE, FALSE, + FALSE, mtcc->pool, scratch_pool)); + + if (!op || (check_repository && !op->performed_stat)) + { + SVN_ERR(mtcc_get_origin(&origin_relpath, &origin_rev, + relpath, TRUE, mtcc, + scratch_pool, scratch_pool)); + + if (!origin_relpath) + *kind = svn_node_none; + else + SVN_ERR(svn_ra_check_path(mtcc->ra_session, origin_relpath, + origin_rev, kind, scratch_pool)); + + if (op && *kind == svn_node_dir) + { + if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) + op->performed_stat = TRUE; + else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) + return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, + _("Can't perform file operation " + "on '%s' as it is not a file"), + relpath); + } + else if (op && *kind == svn_node_file) + { + if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) + op->performed_stat = TRUE; + else if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) + return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't perform directory operation " + "on '%s' as it is not a directory"), + relpath); + } + else if (op && (op->kind == OP_OPEN_DIR || op->kind == OP_OPEN_FILE)) + { + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Can't open '%s' as it does not exist"), + relpath); + } + + return SVN_NO_ERROR; + } + + /* op != NULL */ + if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) + { + *kind = svn_node_dir; + return SVN_NO_ERROR; + } + else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) + { + *kind = svn_node_file; + return SVN_NO_ERROR; + } + SVN_ERR_MALFUNCTION(); /* No other kinds defined as delete is filtered */ +} + +static svn_error_t * +commit_properties(const svn_delta_editor_t *editor, + const mtcc_op_t *op, + void *node_baton, + apr_pool_t *scratch_pool) +{ + int i; + apr_pool_t *iterpool; + + if (!op->prop_mods || op->prop_mods->nelts == 0) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < op->prop_mods->nelts; i++) + { + const svn_prop_t *mod = &APR_ARRAY_IDX(op->prop_mods, i, svn_prop_t); + + svn_pool_clear(iterpool); + + if (op->kind == OP_ADD_DIR || op->kind == OP_OPEN_DIR) + SVN_ERR(editor->change_dir_prop(node_baton, mod->name, mod->value, + iterpool)); + else if (op->kind == OP_ADD_FILE || op->kind == OP_OPEN_FILE) + SVN_ERR(editor->change_file_prop(node_baton, mod->name, mod->value, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +/* Handles updating a file to a delta editor and then closes it */ +static svn_error_t * +commit_file(const svn_delta_editor_t *editor, + mtcc_op_t *op, + void *file_baton, + const char *session_url, + const char *relpath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *text_checksum = NULL; + svn_checksum_t *src_checksum = op->src_checksum; + SVN_ERR(commit_properties(editor, op, file_baton, scratch_pool)); + + if (op->src_stream) + { + const char *base_checksum = NULL; + apr_pool_t *txdelta_pool = scratch_pool; + svn_txdelta_window_handler_t window_handler; + void *handler_baton; + svn_stream_t *src_stream = op->src_stream; + + if (op->base_checksum && op->base_checksum->kind == svn_checksum_md5) + base_checksum = svn_checksum_to_cstring(op->base_checksum, scratch_pool); + + /* ### TODO: Future enhancement: Allocate in special pool and send + files after the true edit operation, like a wc commit */ + SVN_ERR(editor->apply_textdelta(file_baton, base_checksum, txdelta_pool, + &window_handler, &handler_baton)); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify_url( + svn_path_url_add_component2(session_url, relpath, + scratch_pool), + svn_wc_notify_commit_postfix_txdelta, + scratch_pool); + + notify->path = relpath; + notify->kind = svn_node_file; + + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + if (window_handler != svn_delta_noop_window_handler) + { + if (!src_checksum || src_checksum->kind != svn_checksum_md5) + src_stream = svn_stream_checksummed2(src_stream, &src_checksum, NULL, + svn_checksum_md5, + TRUE, scratch_pool); + + if (!op->base_stream) + SVN_ERR(svn_txdelta_send_stream(src_stream, + window_handler, handler_baton, NULL, + scratch_pool)); + else + SVN_ERR(svn_txdelta_run(op->base_stream, src_stream, + window_handler, handler_baton, + svn_checksum_md5, NULL, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_stream_close(src_stream)); + if (op->base_stream) + SVN_ERR(svn_stream_close(op->base_stream)); + } + + if (src_checksum && src_checksum->kind == svn_checksum_md5) + text_checksum = svn_checksum_to_cstring(src_checksum, scratch_pool); + + return svn_error_trace(editor->close_file(file_baton, text_checksum, + scratch_pool)); +} + +/* Handles updating a directory to a delta editor and then closes it */ +static svn_error_t * +commit_directory(const svn_delta_editor_t *editor, + mtcc_op_t *op, + const char *relpath, + svn_revnum_t base_rev, + void *dir_baton, + const char *session_url, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + SVN_ERR(commit_properties(editor, op, dir_baton, scratch_pool)); + + if (op->children && op->children->nelts > 0) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + for (i = 0; i < op->children->nelts; i++) + { + mtcc_op_t *cop; + const char * child_relpath; + void *child_baton; + + cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); + + svn_pool_clear(iterpool); + + if (ctx->cancel_func) + SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); + + child_relpath = svn_relpath_join(relpath, cop->name, iterpool); + + switch (cop->kind) + { + case OP_DELETE: + SVN_ERR(editor->delete_entry(child_relpath, base_rev, + dir_baton, iterpool)); + break; + + case OP_ADD_DIR: + SVN_ERR(editor->add_directory(child_relpath, dir_baton, + cop->src_relpath + ? svn_path_url_add_component2( + session_url, + cop->src_relpath, + iterpool) + : NULL, + cop->src_rev, + iterpool, &child_baton)); + SVN_ERR(commit_directory(editor, cop, child_relpath, + SVN_INVALID_REVNUM, child_baton, + session_url, ctx, iterpool)); + break; + case OP_OPEN_DIR: + SVN_ERR(editor->open_directory(child_relpath, dir_baton, + base_rev, iterpool, &child_baton)); + SVN_ERR(commit_directory(editor, cop, child_relpath, + base_rev, child_baton, + session_url, ctx, iterpool)); + break; + + case OP_ADD_FILE: + SVN_ERR(editor->add_file(child_relpath, dir_baton, + cop->src_relpath + ? svn_path_url_add_component2( + session_url, + cop->src_relpath, + iterpool) + : NULL, + cop->src_rev, + iterpool, &child_baton)); + SVN_ERR(commit_file(editor, cop, child_baton, + session_url, child_relpath, ctx, iterpool)); + break; + case OP_OPEN_FILE: + SVN_ERR(editor->open_file(child_relpath, dir_baton, base_rev, + iterpool, &child_baton)); + SVN_ERR(commit_file(editor, cop, child_baton, + session_url, child_relpath, ctx, iterpool)); + break; + + default: + SVN_ERR_MALFUNCTION(); + } + } + } + + return svn_error_trace(editor->close_directory(dir_baton, scratch_pool)); +} + + +/* Helper function to recursively create svn_client_commit_item3_t items + to provide to the log message callback */ +static svn_error_t * +add_commit_items(mtcc_op_t *op, + const char *session_url, + const char *url, + apr_array_header_t *commit_items, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if ((op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE) + || (op->prop_mods && op->prop_mods->nelts) + || (op->src_stream)) + { + svn_client_commit_item3_t *item; + + item = svn_client_commit_item3_create(result_pool); + + item->path = NULL; + if (op->kind == OP_OPEN_DIR || op->kind == OP_ADD_DIR) + item->kind = svn_node_dir; + else if (op->kind == OP_OPEN_FILE || op->kind == OP_ADD_FILE) + item->kind = svn_node_file; + else + item->kind = svn_node_unknown; + + item->url = apr_pstrdup(result_pool, url); + item->session_relpath = svn_uri_skip_ancestor(session_url, item->url, + result_pool); + + if (op->src_relpath) + { + item->copyfrom_url = svn_path_url_add_component2(session_url, + op->src_relpath, + result_pool); + item->copyfrom_rev = op->src_rev; + item->state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; + } + else + item->copyfrom_rev = SVN_INVALID_REVNUM; + + if (op->kind == OP_ADD_DIR || op->kind == OP_ADD_FILE) + item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; + else if (op->kind == OP_DELETE) + item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; + /* else item->state_flags = 0; */ + + if (op->prop_mods && op->prop_mods->nelts) + item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; + + if (op->src_stream) + item->state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; + + APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; + } + + if (op->children && op->children->nelts) + { + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < op->children->nelts; i++) + { + mtcc_op_t *cop; + const char * child_url; + + cop = APR_ARRAY_IDX(op->children, i, mtcc_op_t *); + + svn_pool_clear(iterpool); + + child_url = svn_path_url_add_component2(url, cop->name, iterpool); + + SVN_ERR(add_commit_items(cop, session_url, child_url, commit_items, + result_pool, iterpool)); + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mtcc_commit(apr_hash_t *revprop_table, + svn_commit_callback2_t commit_callback, + void *commit_baton, + svn_client__mtcc_t *mtcc, + apr_pool_t *scratch_pool) +{ + const svn_delta_editor_t *editor; + void *edit_baton; + void *root_baton; + apr_hash_t *commit_revprops; + svn_node_kind_t kind; + svn_error_t *err; + const char *session_url; + const char *log_msg; + + if (MTCC_UNMODIFIED(mtcc)) + { + /* No changes -> no revision. Easy out */ + svn_pool_destroy(mtcc->pool); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool)); + + if (mtcc->root_op->kind != OP_OPEN_DIR) + { + const char *name; + + svn_uri_split(&session_url, &name, session_url, scratch_pool); + + if (*name) + { + SVN_ERR(mtcc_reparent(session_url, mtcc, scratch_pool)); + + SVN_ERR(svn_ra_reparent(mtcc->ra_session, session_url, scratch_pool)); + } + } + + /* Create new commit items and add them to the array. */ + if (SVN_CLIENT__HAS_LOG_MSG_FUNC(mtcc->ctx)) + { + svn_client_commit_item3_t *item; + const char *tmp_file; + apr_array_header_t *commit_items + = apr_array_make(scratch_pool, 32, sizeof(item)); + + SVN_ERR(add_commit_items(mtcc->root_op, session_url, session_url, + commit_items, scratch_pool, scratch_pool)); + + SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, + mtcc->ctx, scratch_pool)); + + if (! log_msg) + return SVN_NO_ERROR; + } + else + log_msg = ""; + + SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, + log_msg, mtcc->ctx, scratch_pool)); + + /* Ugly corner case: The ra session might have died while we were waiting + for the callback */ + + err = svn_ra_check_path(mtcc->ra_session, "", mtcc->base_revision, &kind, + scratch_pool); + + if (err) + { + svn_error_t *err2 = svn_client_open_ra_session2(&mtcc->ra_session, + session_url, + NULL, mtcc->ctx, + mtcc->pool, + scratch_pool); + + if (err2) + { + svn_pool_destroy(mtcc->pool); + return svn_error_trace(svn_error_compose_create(err, err2)); + } + svn_error_clear(err); + + SVN_ERR(svn_ra_check_path(mtcc->ra_session, "", + mtcc->base_revision, &kind, scratch_pool)); + } + + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't commit to '%s' because it " + "is not a directory"), + session_url); + + /* Beware that the editor object must not live longer than the MTCC. + Otherwise, txn objects etc. in EDITOR may live longer than their + respective FS objects. So, we can't use SCRATCH_POOL here. */ + SVN_ERR(svn_ra_get_commit_editor3(mtcc->ra_session, &editor, &edit_baton, + commit_revprops, + commit_callback, commit_baton, + NULL /* lock_tokens */, + FALSE /* keep_locks */, + mtcc->pool)); + + err = editor->open_root(edit_baton, mtcc->base_revision, scratch_pool, &root_baton); + + if (!err) + err = commit_directory(editor, mtcc->root_op, "", mtcc->base_revision, + root_baton, session_url, mtcc->ctx, scratch_pool); + + if (!err) + { + if (mtcc->ctx->notify_func2) + { + svn_wc_notify_t *notify; + notify = svn_wc_create_notify_url(session_url, + svn_wc_notify_commit_finalizing, + scratch_pool); + mtcc->ctx->notify_func2(mtcc->ctx->notify_baton2, notify, + scratch_pool); + } + SVN_ERR(editor->close_edit(edit_baton, scratch_pool)); + } + else + err = svn_error_compose_create(err, + editor->abort_edit(edit_baton, scratch_pool)); + + svn_pool_destroy(mtcc->pool); + + return svn_error_trace(err); +} |