summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/mtcc.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/mtcc.c')
-rw-r--r--subversion/libsvn_client/mtcc.c1429
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);
+}