| /* |
| * 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, or was created " |
| "by an earlier operation"), |
| 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; |
| } |
| |
| /* Check if this operation contains at least one change that is not a |
| plain delete */ |
| static svn_boolean_t |
| mtcc_op_contains_non_delete(const mtcc_op_t *op) |
| { |
| if (op->kind != OP_OPEN_DIR && op->kind != OP_OPEN_FILE |
| && op->kind != OP_DELETE) |
| { |
| return TRUE; |
| } |
| |
| if (op->prop_mods && op->prop_mods->nelts) |
| return TRUE; |
| |
| if (op->src_stream) |
| return TRUE; |
| |
| if (op->children) |
| { |
| int i; |
| |
| for (i = 0; i < op->children->nelts; i++) |
| { |
| const mtcc_op_t *c_op = APR_ARRAY_IDX(op->children, i, |
| const mtcc_op_t *); |
| |
| if (mtcc_op_contains_non_delete(c_op)) |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| static svn_error_t * |
| mtcc_add_delete(const char *relpath, |
| svn_boolean_t for_move, |
| 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 (!for_move && !op && !created) |
| { |
| /* Allow deleting directories, that are unmodified except for |
| one or more deleted descendants */ |
| |
| SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, |
| FALSE, FALSE, mtcc->pool, scratch_pool)); |
| |
| if (op && mtcc_op_contains_non_delete(op)) |
| op = NULL; |
| else |
| created = TRUE; |
| } |
| |
| 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_delete(const char *relpath, |
| svn_client__mtcc_t *mtcc, |
| apr_pool_t *scratch_pool) |
| { |
| return svn_error_trace(mtcc_add_delete(relpath, FALSE, mtcc, scratch_pool)); |
| } |
| |
| 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(mtcc_add_delete(src_relpath, TRUE, 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; |
| break; |
| } |
| } |
| } |
| |
| 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); |
| } |