summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/commit.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_client/commit.c
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_client/commit.c')
-rw-r--r--subversion/libsvn_client/commit.c1031
1 files changed, 1031 insertions, 0 deletions
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
new file mode 100644
index 0000000..3f6bfef
--- /dev/null
+++ b/subversion/libsvn_client/commit.c
@@ -0,0 +1,1031 @@
+/*
+ * commit.c: wrappers around wc commit functionality.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_ra.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+
+#include "client.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+
+#include "svn_private_config.h"
+
+struct capture_baton_t {
+ svn_commit_callback2_t original_callback;
+ void *original_baton;
+
+ svn_commit_info_t **info;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+capture_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct capture_baton_t *cb = baton;
+
+ *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
+
+ if (cb->original_callback)
+ SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_ra_editor(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ const char *log_msg,
+ const apr_array_header_t *commit_items,
+ const apr_hash_t *revprop_table,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ apr_pool_t *pool)
+{
+ apr_hash_t *commit_revprops;
+ apr_hash_t *relpath_map = NULL;
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, pool));
+
+#ifdef ENABLE_EV2_SHIMS
+ if (commit_items)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ relpath_map = apr_hash_make(pool);
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
+ svn_client_commit_item3_t *);
+ const char *relpath;
+
+ if (!item->path)
+ continue;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
+ ctx->wc_ctx, item->path, FALSE, pool,
+ iterpool));
+ if (relpath)
+ svn_hash_sets(relpath_map, relpath, item->path);
+ }
+ svn_pool_destroy(iterpool);
+ }
+#endif
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ relpath_map, pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
+ commit_revprops, commit_callback,
+ commit_baton, lock_tokens, keep_locks,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/*** Public Interfaces. ***/
+
+static svn_error_t *
+reconcile_errors(svn_error_t *commit_err,
+ svn_error_t *unlock_err,
+ svn_error_t *bump_err,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ /* Early release (for good behavior). */
+ if (! (commit_err || unlock_err || bump_err))
+ return SVN_NO_ERROR;
+
+ /* If there was a commit error, start off our error chain with
+ that. */
+ if (commit_err)
+ {
+ commit_err = svn_error_quick_wrap
+ (commit_err, _("Commit failed (details follow):"));
+ err = commit_err;
+ }
+
+ /* Else, create a new "general" error that will lead off the errors
+ that follow. */
+ else
+ err = svn_error_create(SVN_ERR_BASE, NULL,
+ _("Commit succeeded, but other errors follow:"));
+
+ /* If there was an unlock error... */
+ if (unlock_err)
+ {
+ /* Wrap the error with some headers. */
+ unlock_err = svn_error_quick_wrap
+ (unlock_err, _("Error unlocking locked dirs (details follow):"));
+
+ /* Append this error to the chain. */
+ svn_error_compose(err, unlock_err);
+ }
+
+ /* If there was a bumping error... */
+ if (bump_err)
+ {
+ /* Wrap the error with some headers. */
+ bump_err = svn_error_quick_wrap
+ (bump_err, _("Error bumping revisions post-commit (details follow):"));
+
+ /* Append this error to the chain. */
+ svn_error_compose(err, bump_err);
+ }
+
+ return err;
+}
+
+/* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
+ to a new hashtable allocated in POOL. *RESULT is set to point to this
+ new hash table. *RESULT will be keyed on const char * URI-decoded paths
+ relative to BASE_URL. The lock tokens will not be duplicated. */
+static svn_error_t *
+collect_lock_tokens(apr_hash_t **result,
+ apr_hash_t *all_tokens,
+ const char *base_url,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ *result = apr_hash_make(pool);
+
+ for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
+ {
+ const char *url = svn__apr_hash_index_key(hi);
+ const char *token = svn__apr_hash_index_val(hi);
+ const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
+
+ if (relpath)
+ {
+ svn_hash_sets(*result, relpath, token);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
+ * If a checksum is provided, it can be the MD5 and/or the SHA1. */
+static svn_error_t *
+post_process_commit_item(svn_wc_committed_queue_t *queue,
+ const svn_client_commit_item3_t *item,
+ svn_wc_context_t *wc_ctx,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t keep_locks,
+ svn_boolean_t commit_as_operations,
+ const svn_checksum_t *sha1_checksum,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t loop_recurse = FALSE;
+ svn_boolean_t remove_lock;
+
+ if (! commit_as_operations
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ && (item->kind == svn_node_dir)
+ && (item->copyfrom_url))
+ loop_recurse = TRUE;
+
+ remove_lock = (! keep_locks && (item->state_flags
+ & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
+
+ return svn_wc_queue_committed3(queue, wc_ctx, item->path,
+ loop_recurse, item->incoming_prop_changes,
+ remove_lock, !keep_changelists,
+ sha1_checksum, scratch_pool);
+}
+
+
+static svn_error_t *
+check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx,
+ const char *target_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(depth != svn_depth_infinity);
+
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath,
+ TRUE, FALSE, scratch_pool));
+
+
+ /* ### TODO(sd): This check is slightly too strict. It should be
+ ### possible to:
+ ###
+ ### * delete a directory containing only files when
+ ### depth==svn_depth_files;
+ ###
+ ### * delete a directory containing only files and empty
+ ### subdirs when depth==svn_depth_immediates.
+ ###
+ ### But for now, we insist on svn_depth_infinity if you're
+ ### going to delete a directory, because we're lazy and
+ ### trying to get depthy commits working in the first place.
+ ###
+ ### This would be fairly easy to fix, though: just, well,
+ ### check the above conditions!
+ ###
+ ### GJS: I think there may be some confusion here. there is
+ ### the depth of the commit, and the depth of a checked-out
+ ### directory in the working copy. Delete, by its nature, will
+ ### always delete all of its children, so it seems a bit
+ ### strange to worry about what is in the working copy.
+ */
+ if (kind == svn_node_dir)
+ {
+ svn_wc_schedule_t schedule;
+
+ /* ### Looking at schedule is probably enough, no need for
+ pristine compare etc. */
+ SVN_ERR(svn_wc__node_get_schedule(&schedule, NULL,
+ wc_ctx, target_abspath,
+ scratch_pool));
+
+ if (schedule == svn_wc_schedule_delete
+ || schedule == svn_wc_schedule_replace)
+ {
+ const apr_array_header_t *children;
+
+ SVN_ERR(svn_wc__node_get_children(&children, wc_ctx,
+ target_abspath, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (children->nelts > 0)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot delete the directory '%s' "
+ "in a non-recursive commit "
+ "because it has children"),
+ svn_dirent_local_style(target_abspath,
+ scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a list of committables described by their common base abspath
+ BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
+ which absolute paths must be locked to commit all these targets and
+ return this as a const char * array in LOCK_TARGETS
+
+ Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
+ storage */
+static svn_error_t *
+determine_lock_targets(apr_array_header_t **lock_targets,
+ svn_wc_context_t *wc_ctx,
+ const char *base_abspath,
+ const apr_array_header_t *target_relpaths,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
+ apr_hash_index_t *hi;
+ int i;
+
+ wc_items = apr_hash_make(scratch_pool);
+
+ /* Create an array of targets for each working copy used */
+ for (i = 0; i < target_relpaths->nelts; i++)
+ {
+ const char *target_abspath;
+ const char *wcroot_abspath;
+ apr_array_header_t *wc_targets;
+ svn_error_t *err;
+ const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
+ const char *);
+
+ svn_pool_clear(iterpool);
+ target_abspath = svn_dirent_join(base_abspath, target_relpath,
+ scratch_pool);
+
+ err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
+ iterpool, iterpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ continue;
+ }
+ return svn_error_trace(err);
+ }
+
+ wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
+
+ if (! wc_targets)
+ {
+ wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
+ svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
+ wc_targets);
+ }
+
+ APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
+ }
+
+ *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
+ sizeof(const char *));
+
+ /* For each working copy determine where to lock */
+ for (hi = apr_hash_first(scratch_pool, wc_items);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *common;
+ const char *wcroot_abspath = svn__apr_hash_index_key(hi);
+ apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ if (wc_targets->nelts == 1)
+ {
+ const char *target_abspath;
+ target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
+
+ if (! strcmp(wcroot_abspath, target_abspath))
+ {
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = apr_pstrdup(result_pool, target_abspath);
+ }
+ else
+ {
+ /* Lock the parent to allow deleting the target */
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = svn_dirent_dirname(target_abspath, result_pool);
+ }
+ }
+ else if (wc_targets->nelts > 1)
+ {
+ SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
+ FALSE, iterpool, iterpool));
+
+ qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size,
+ svn_sort_compare_paths);
+
+ if (wc_targets->nelts == 0
+ || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
+ || !strcmp(common, wcroot_abspath))
+ {
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = apr_pstrdup(result_pool, common);
+ }
+ else
+ {
+ /* Lock the parent to allow deleting the target */
+ APR_ARRAY_PUSH(*lock_targets, const char *)
+ = svn_dirent_dirname(common, result_pool);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Baton for check_url_kind */
+struct check_url_kind_baton
+{
+ apr_pool_t *pool;
+ svn_ra_session_t *session;
+ const char *repos_root_url;
+ svn_client_ctx_t *ctx;
+};
+
+/* Implements svn_client__check_url_kind_t for svn_client_commit5 */
+static svn_error_t *
+check_url_kind(void *baton,
+ svn_node_kind_t *kind,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct check_url_kind_baton *cukb = baton;
+
+ /* If we don't have a session or can't use the session, get one */
+ if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
+ {
+ SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
+ cukb->pool, scratch_pool));
+ SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
+ cukb->pool));
+ }
+ else
+ SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
+
+ return svn_error_trace(
+ svn_ra_check_path(cukb->session, "", revision,
+ kind, scratch_pool));
+}
+
+/* Recurse into every target in REL_TARGETS, finding committable externals
+ * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
+ * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
+ * arguments correspond to those of svn_client_commit6(). */
+static svn_error_t*
+append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
+ const char *base_abspath,
+ svn_boolean_t include_file_externals,
+ svn_boolean_t include_dir_externals,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int rel_targets_nelts_fixed;
+ int i;
+ apr_pool_t *iterpool;
+
+ if (! (include_file_externals || include_dir_externals))
+ return SVN_NO_ERROR;
+
+ /* Easy part of applying DEPTH to externals. */
+ if (depth == svn_depth_empty)
+ {
+ /* Don't recurse. */
+ return SVN_NO_ERROR;
+ }
+
+ /* Iterate *and* grow REL_TARGETS at the same time. */
+ rel_targets_nelts_fixed = rel_targets->nelts;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < rel_targets_nelts_fixed; i++)
+ {
+ int j;
+ const char *target;
+ apr_array_header_t *externals = NULL;
+
+ svn_pool_clear(iterpool);
+
+ target = svn_dirent_join(base_abspath,
+ APR_ARRAY_IDX(rel_targets, i, const char *),
+ iterpool);
+
+ /* ### TODO: Possible optimization: No need to do this for file targets.
+ * ### But what's cheaper, stat'ing the file system or querying the db?
+ * ### --> future. */
+
+ SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
+ target, depth,
+ iterpool, iterpool));
+
+ if (externals != NULL)
+ {
+ const char *rel_target;
+
+ for (j = 0; j < externals->nelts; j++)
+ {
+ svn_wc__committable_external_info_t *xinfo =
+ APR_ARRAY_IDX(externals, j,
+ svn_wc__committable_external_info_t *);
+
+ if ((xinfo->kind == svn_node_file && ! include_file_externals)
+ || (xinfo->kind == svn_node_dir && ! include_dir_externals))
+ continue;
+
+ rel_target = svn_dirent_skip_ancestor(base_abspath,
+ xinfo->local_abspath);
+
+ SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
+
+ APR_ARRAY_PUSH(rel_targets, const char *) =
+ apr_pstrdup(result_pool, rel_target);
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_commit6(const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_boolean_t keep_locks,
+ svn_boolean_t keep_changelists,
+ svn_boolean_t commit_as_operations,
+ svn_boolean_t include_file_externals,
+ svn_boolean_t include_dir_externals,
+ const apr_array_header_t *changelists,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ struct capture_baton_t cb;
+ svn_ra_session_t *ra_session;
+ const char *log_msg;
+ const char *base_abspath;
+ const char *base_url;
+ apr_array_header_t *rel_targets;
+ apr_array_header_t *lock_targets;
+ apr_array_header_t *locks_obtained;
+ svn_client__committables_t *committables;
+ apr_hash_t *lock_tokens;
+ apr_hash_t *sha1_checksums;
+ apr_array_header_t *commit_items;
+ svn_error_t *cmt_err = SVN_NO_ERROR;
+ svn_error_t *bump_err = SVN_NO_ERROR;
+ svn_error_t *unlock_err = SVN_NO_ERROR;
+ svn_boolean_t commit_in_progress = FALSE;
+ svn_boolean_t timestamp_sleep = FALSE;
+ svn_commit_info_t *commit_info = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *current_abspath;
+ const char *notify_prefix;
+ int depth_empty_after = -1;
+ int i;
+
+ SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
+
+ /* Committing URLs doesn't make sense, so error if it's tried. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ if (svn_path_is_url(target))
+ return svn_error_createf
+ (SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is a URL, but URLs cannot be commit targets"), target);
+ }
+
+ /* Condense the target list. This makes all targets absolute. */
+ SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
+ FALSE, pool, iterpool));
+
+ /* No targets means nothing to commit, so just return. */
+ if (base_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(rel_targets != NULL);
+
+ /* If we calculated only a base and no relative targets, this
+ must mean that we are being asked to commit (effectively) a
+ single path. */
+ if (rel_targets->nelts == 0)
+ APR_ARRAY_PUSH(rel_targets, const char *) = "";
+
+ if (include_file_externals || include_dir_externals)
+ {
+ if (depth != svn_depth_unknown && depth != svn_depth_infinity)
+ {
+ /* All targets after this will be handled as depth empty */
+ depth_empty_after = rel_targets->nelts;
+ }
+
+ SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
+ include_file_externals,
+ include_dir_externals,
+ depth, ctx,
+ pool, pool));
+ }
+
+ SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
+ rel_targets, pool, iterpool));
+
+ locks_obtained = apr_array_make(pool, lock_targets->nelts,
+ sizeof(const char *));
+
+ for (i = 0; i < lock_targets->nelts; i++)
+ {
+ const char *lock_root;
+ const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ cmt_err = svn_error_trace(
+ svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
+ FALSE, pool, iterpool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
+ }
+
+ /* Determine prefix to strip from the commit notify messages */
+ SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
+ notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
+ base_abspath,
+ pool);
+
+ /* If a non-recursive commit is desired, do not allow a deleted directory
+ as one of the targets. */
+ if (depth != svn_depth_infinity && ! commit_as_operations)
+ for (i = 0; i < rel_targets->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *);
+ const char *target_abspath;
+
+ svn_pool_clear(iterpool);
+
+ target_abspath = svn_dirent_join(base_abspath, relpath, iterpool);
+
+ cmt_err = svn_error_trace(
+ check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath,
+ depth, iterpool));
+
+ if (cmt_err)
+ goto cleanup;
+ }
+
+ /* Crawl the working copy for commit items. */
+ {
+ struct check_url_kind_baton cukb;
+
+ /* Prepare for when we have a copy containing not-present nodes. */
+ cukb.pool = iterpool;
+ cukb.session = NULL; /* ### Can we somehow reuse session? */
+ cukb.repos_root_url = NULL;
+ cukb.ctx = ctx;
+
+ cmt_err = svn_error_trace(
+ svn_client__harvest_committables(&committables,
+ &lock_tokens,
+ base_abspath,
+ rel_targets,
+ depth_empty_after,
+ depth,
+ ! keep_locks,
+ changelists,
+ check_url_kind,
+ &cukb,
+ ctx,
+ pool,
+ iterpool));
+
+ svn_pool_clear(iterpool);
+ }
+
+ if (cmt_err)
+ goto cleanup;
+
+ if (apr_hash_count(committables->by_repository) == 0)
+ {
+ goto cleanup; /* Nothing to do */
+ }
+ else if (apr_hash_count(committables->by_repository) > 1)
+ {
+ cmt_err = svn_error_create(
+ SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Commit can only commit to a single repository at a time.\n"
+ "Are all targets part of the same working copy?"));
+ goto cleanup;
+ }
+
+ {
+ apr_hash_index_t *hi = apr_hash_first(iterpool,
+ committables->by_repository);
+
+ commit_items = svn__apr_hash_index_val(hi);
+ }
+
+ /* If our array of targets contains only locks (and no actual file
+ or prop modifications), then we return here to avoid committing a
+ revision with no changes. */
+ {
+ svn_boolean_t found_changed_path = FALSE;
+
+ for (i = 0; i < commit_items->nelts; ++i)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
+ {
+ found_changed_path = TRUE;
+ break;
+ }
+ }
+
+ if (!found_changed_path)
+ goto cleanup;
+ }
+
+ /* For every target that was moved verify that both halves of the
+ * move are part of the commit. */
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item =
+ APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
+ {
+ /* ### item->moved_from_abspath contains the move origin */
+ const char *moved_from_abspath;
+ const char *delete_op_root_abspath;
+
+ cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
+ &moved_from_abspath,
+ &delete_op_root_abspath,
+ ctx->wc_ctx, item->path,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (moved_from_abspath && delete_op_root_abspath &&
+ strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
+
+ {
+ svn_boolean_t found_delete_half =
+ (svn_hash_gets(committables->by_path, delete_op_root_abspath)
+ != NULL);
+
+ if (!found_delete_half)
+ {
+ const char *delete_half_parent_abspath;
+
+ /* The delete-half isn't in the commit target list.
+ * However, it might itself be the child of a deleted node,
+ * either because of another move or a deletion.
+ *
+ * For example, consider: mv A/B B; mv B/C C; commit;
+ * C's moved-from A/B/C is a child of the deleted A/B.
+ * A/B/C does not appear in the commit target list, but
+ * A/B does appear.
+ * (Note that moved-from information is always stored
+ * relative to the BASE tree, so we have 'C moved-from
+ * A/B/C', not 'C moved-from B/C'.)
+ *
+ * An example involving a move and a delete would be:
+ * mv A/B C; rm A; commit;
+ * Now C is moved-from A/B which does not appear in the
+ * commit target list, but A does appear.
+ */
+
+ /* Scan upwards for a deletion op-root from the
+ * delete-half's parent directory. */
+ delete_half_parent_abspath =
+ svn_dirent_dirname(delete_op_root_abspath, iterpool);
+ if (strcmp(delete_op_root_abspath,
+ delete_half_parent_abspath) != 0)
+ {
+ const char *parent_delete_op_root_abspath;
+
+ cmt_err = svn_error_trace(
+ svn_wc__node_get_deleted_ancestor(
+ &parent_delete_op_root_abspath,
+ ctx->wc_ctx, delete_half_parent_abspath,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (parent_delete_op_root_abspath)
+ found_delete_half =
+ (svn_hash_gets(committables->by_path,
+ parent_delete_op_root_abspath)
+ != NULL);
+ }
+ }
+
+ if (!found_delete_half)
+ {
+ cmt_err = svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot commit '%s' because it was moved from "
+ "'%s' which is not part of the commit; both "
+ "sides of the move must be committed together"),
+ svn_dirent_local_style(item->path, iterpool),
+ svn_dirent_local_style(delete_op_root_abspath,
+ iterpool));
+ goto cleanup;
+ }
+ }
+ }
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ {
+ const char *moved_to_abspath;
+ const char *copy_op_root_abspath;
+
+ cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
+ &moved_to_abspath,
+ &copy_op_root_abspath,
+ ctx->wc_ctx, item->path,
+ iterpool, iterpool));
+ if (cmt_err)
+ goto cleanup;
+
+ if (moved_to_abspath && copy_op_root_abspath &&
+ strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
+ svn_hash_gets(committables->by_path, copy_op_root_abspath)
+ == NULL)
+ {
+ cmt_err = svn_error_createf(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Cannot commit '%s' because it was moved to '%s' "
+ "which is not part of the commit; both sides of "
+ "the move must be committed together"),
+ svn_dirent_local_style(item->path, iterpool),
+ svn_dirent_local_style(copy_op_root_abspath,
+ iterpool));
+ goto cleanup;
+ }
+ }
+ }
+
+ /* Go get a log message. If an error occurs, or no log message is
+ specified, abort the operation. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ const char *tmp_file;
+ cmt_err = svn_error_trace(
+ svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, pool));
+
+ if (cmt_err || (! log_msg))
+ goto cleanup;
+ }
+ else
+ log_msg = "";
+
+ /* Sort and condense our COMMIT_ITEMS. */
+ cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
+ commit_items,
+ pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ /* Collect our lock tokens with paths relative to base_url. */
+ cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
+ base_url, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ cb.original_callback = commit_callback;
+ cb.original_baton = commit_baton;
+ cb.info = &commit_info;
+ cb.pool = pool;
+
+ /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
+ * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
+ * parent of nested working copies. We don't support commits to multiple
+ * repositories so using the first WC to get the RA session is safe. */
+ cmt_err = svn_error_trace(
+ svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
+ APR_ARRAY_IDX(lock_targets,
+ 0,
+ const char *),
+ commit_items,
+ TRUE, TRUE, ctx,
+ pool, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ cmt_err = svn_error_trace(
+ get_ra_editor(&editor, &edit_baton, ra_session, ctx,
+ log_msg, commit_items, revprop_table,
+ lock_tokens, keep_locks, capture_commit_info,
+ &cb, pool));
+
+ if (cmt_err)
+ goto cleanup;
+
+ /* Make a note that we have a commit-in-progress. */
+ commit_in_progress = TRUE;
+
+ /* We'll assume that, once we pass this point, we are going to need to
+ * sleep for timestamps. Really, we may not need to do unless and until
+ * we reach the point where we post-commit 'bump' the WC metadata. */
+ timestamp_sleep = TRUE;
+
+ /* Perform the commit. */
+ cmt_err = svn_error_trace(
+ svn_client__do_commit(base_url, commit_items, editor, edit_baton,
+ notify_prefix, &sha1_checksums, ctx, pool,
+ iterpool));
+
+ /* Handle a successful commit. */
+ if ((! cmt_err)
+ || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
+ {
+ svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
+
+ /* Make a note that our commit is finished. */
+ commit_in_progress = FALSE;
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+
+ svn_pool_clear(iterpool);
+ bump_err = post_process_commit_item(
+ queue, item, ctx->wc_ctx,
+ keep_changelists, keep_locks, commit_as_operations,
+ svn_hash_gets(sha1_checksums, item->path),
+ iterpool);
+ if (bump_err)
+ goto cleanup;
+ }
+
+ SVN_ERR_ASSERT(commit_info);
+ bump_err = svn_wc_process_committed_queue2(
+ queue, ctx->wc_ctx,
+ commit_info->revision,
+ commit_info->date,
+ commit_info->author,
+ ctx->cancel_func, ctx->cancel_baton,
+ iterpool);
+ }
+
+ cleanup:
+ /* Sleep to ensure timestamp integrity. */
+ if (timestamp_sleep)
+ svn_io_sleep_for_timestamps(base_abspath, pool);
+
+ /* Abort the commit if it is still in progress. */
+ svn_pool_clear(iterpool); /* Close open handles before aborting */
+ if (commit_in_progress)
+ cmt_err = svn_error_compose_create(cmt_err,
+ editor->abort_edit(edit_baton, pool));
+
+ /* A bump error is likely to occur while running a working copy log file,
+ explicitly unlocking and removing temporary files would be wrong in
+ that case. A commit error (cmt_err) should only occur before any
+ attempt to modify the working copy, so it doesn't prevent explicit
+ clean-up. */
+ if (! bump_err)
+ {
+ /* Release all locks we obtained */
+ for (i = 0; i < locks_obtained->nelts; i++)
+ {
+ const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
+ const char *);
+
+ svn_pool_clear(iterpool);
+
+ unlock_err = svn_error_compose_create(
+ svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
+ iterpool),
+ unlock_err);
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
+ pool));
+}
OpenPOWER on IntegriCloud