2 * commit.c: wrappers around wc commit functionality.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
31 #include <apr_strings.h>
36 #include "svn_client.h"
37 #include "svn_string.h"
38 #include "svn_pools.h"
39 #include "svn_error.h"
40 #include "svn_error_codes.h"
41 #include "svn_dirent_uri.h"
43 #include "svn_sorts.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_ra_private.h"
48 #include "private/svn_sorts_private.h"
50 #include "svn_private_config.h"
52 struct capture_baton_t {
53 svn_commit_callback2_t original_callback;
56 svn_commit_info_t **info;
62 capture_commit_info(const svn_commit_info_t *commit_info,
66 struct capture_baton_t *cb = baton;
68 *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
70 if (cb->original_callback)
71 SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
78 get_ra_editor(const svn_delta_editor_t **editor,
80 svn_ra_session_t *ra_session,
81 svn_client_ctx_t *ctx,
83 const apr_array_header_t *commit_items,
84 const apr_hash_t *revprop_table,
85 apr_hash_t *lock_tokens,
86 svn_boolean_t keep_locks,
87 svn_commit_callback2_t commit_callback,
91 apr_hash_t *commit_revprops;
92 apr_hash_t *relpath_map = NULL;
94 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
97 #ifdef ENABLE_EV2_SHIMS
101 apr_pool_t *iterpool = svn_pool_create(pool);
103 relpath_map = apr_hash_make(pool);
104 for (i = 0; i < commit_items->nelts; i++)
106 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
107 svn_client_commit_item3_t *);
113 svn_pool_clear(iterpool);
114 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
116 ctx->wc_ctx, item->path, FALSE, pool,
119 svn_hash_sets(relpath_map, relpath, item->path);
121 svn_pool_destroy(iterpool);
125 /* Fetch RA commit editor. */
126 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
127 svn_client__get_shim_callbacks(ctx->wc_ctx,
128 relpath_map, pool)));
129 SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
130 commit_revprops, commit_callback,
131 commit_baton, lock_tokens, keep_locks,
138 /*** Public Interfaces. ***/
141 reconcile_errors(svn_error_t *commit_err,
142 svn_error_t *unlock_err,
143 svn_error_t *bump_err,
148 /* Early release (for good behavior). */
149 if (! (commit_err || unlock_err || bump_err))
152 /* If there was a commit error, start off our error chain with
156 commit_err = svn_error_quick_wrap
157 (commit_err, _("Commit failed (details follow):"));
161 /* Else, create a new "general" error that will lead off the errors
164 err = svn_error_create(SVN_ERR_BASE, NULL,
165 _("Commit succeeded, but other errors follow:"));
167 /* If there was an unlock error... */
170 /* Wrap the error with some headers. */
171 unlock_err = svn_error_quick_wrap
172 (unlock_err, _("Error unlocking locked dirs (details follow):"));
174 /* Append this error to the chain. */
175 svn_error_compose(err, unlock_err);
178 /* If there was a bumping error... */
181 /* Wrap the error with some headers. */
182 bump_err = svn_error_quick_wrap
183 (bump_err, _("Error bumping revisions post-commit (details follow):"));
185 /* Append this error to the chain. */
186 svn_error_compose(err, bump_err);
192 /* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
193 to a new hashtable allocated in POOL. *RESULT is set to point to this
194 new hash table. *RESULT will be keyed on const char * URI-decoded paths
195 relative to BASE_URL. The lock tokens will not be duplicated. */
197 collect_lock_tokens(apr_hash_t **result,
198 apr_hash_t *all_tokens,
199 const char *base_url,
202 apr_hash_index_t *hi;
204 *result = apr_hash_make(pool);
206 for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
208 const char *url = apr_hash_this_key(hi);
209 const char *token = apr_hash_this_val(hi);
210 const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
214 svn_hash_sets(*result, relpath, token);
221 /* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
222 * If a checksum is provided, it can be the MD5 and/or the SHA1. */
224 post_process_commit_item(svn_wc_committed_queue_t *queue,
225 const svn_client_commit_item3_t *item,
226 svn_wc_context_t *wc_ctx,
227 svn_boolean_t keep_changelists,
228 svn_boolean_t keep_locks,
229 svn_boolean_t commit_as_operations,
230 const svn_checksum_t *sha1_checksum,
231 apr_pool_t *scratch_pool)
233 svn_boolean_t loop_recurse = FALSE;
234 svn_boolean_t remove_lock;
236 if (! commit_as_operations
237 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
238 && (item->kind == svn_node_dir)
239 && (item->copyfrom_url))
242 remove_lock = (! keep_locks && (item->state_flags
243 & (SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN
244 | SVN_CLIENT_COMMIT_ITEM_ADD
245 | SVN_CLIENT_COMMIT_ITEM_DELETE)));
247 /* When the node was deleted (or replaced), we need to always remove the
248 locks, as they're invalidated on the server. We cannot honor the
249 SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell
250 us whether we have locked children. */
251 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
254 return svn_error_trace(
255 svn_wc_queue_committed4(queue, wc_ctx, item->path,
257 0 != (item->state_flags &
258 (SVN_CLIENT_COMMIT_ITEM_ADD
259 | SVN_CLIENT_COMMIT_ITEM_DELETE
260 | SVN_CLIENT_COMMIT_ITEM_TEXT_MODS
261 | SVN_CLIENT_COMMIT_ITEM_PROP_MODS)),
262 item->incoming_prop_changes,
263 remove_lock, !keep_changelists,
264 sha1_checksum, scratch_pool));
267 /* Given a list of committables described by their common base abspath
268 BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
269 which absolute paths must be locked to commit all these targets and
270 return this as a const char * array in LOCK_TARGETS
272 Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
275 determine_lock_targets(apr_array_header_t **lock_targets,
276 svn_wc_context_t *wc_ctx,
277 const char *base_abspath,
278 const apr_array_header_t *target_relpaths,
279 apr_pool_t *result_pool,
280 apr_pool_t *scratch_pool)
282 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
283 apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
284 apr_hash_index_t *hi;
287 wc_items = apr_hash_make(scratch_pool);
289 /* Create an array of targets for each working copy used */
290 for (i = 0; i < target_relpaths->nelts; i++)
292 const char *target_abspath;
293 const char *wcroot_abspath;
294 apr_array_header_t *wc_targets;
296 const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
299 svn_pool_clear(iterpool);
300 target_abspath = svn_dirent_join(base_abspath, target_relpath,
303 err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
308 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
310 svn_error_clear(err);
313 return svn_error_trace(err);
316 wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
320 wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
321 svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
325 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
328 *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
329 sizeof(const char *));
331 /* For each working copy determine where to lock */
332 for (hi = apr_hash_first(scratch_pool, wc_items);
334 hi = apr_hash_next(hi))
337 const char *wcroot_abspath = apr_hash_this_key(hi);
338 apr_array_header_t *wc_targets = apr_hash_this_val(hi);
340 svn_pool_clear(iterpool);
342 if (wc_targets->nelts == 1)
344 const char *target_abspath;
345 target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
347 if (! strcmp(wcroot_abspath, target_abspath))
349 APR_ARRAY_PUSH(*lock_targets, const char *)
350 = apr_pstrdup(result_pool, target_abspath);
354 /* Lock the parent to allow deleting the target */
355 APR_ARRAY_PUSH(*lock_targets, const char *)
356 = svn_dirent_dirname(target_abspath, result_pool);
359 else if (wc_targets->nelts > 1)
361 SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
362 FALSE, iterpool, iterpool));
364 svn_sort__array(wc_targets, svn_sort_compare_paths);
366 if (wc_targets->nelts == 0
367 || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
368 || !strcmp(common, wcroot_abspath))
370 APR_ARRAY_PUSH(*lock_targets, const char *)
371 = apr_pstrdup(result_pool, common);
375 /* Lock the parent to allow deleting the target */
376 APR_ARRAY_PUSH(*lock_targets, const char *)
377 = svn_dirent_dirname(common, result_pool);
382 svn_pool_destroy(iterpool);
386 /* Baton for check_url_kind */
387 struct check_url_kind_baton
390 svn_ra_session_t *session;
391 const char *repos_root_url;
392 svn_client_ctx_t *ctx;
395 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
397 check_url_kind(void *baton,
398 svn_node_kind_t *kind,
400 svn_revnum_t revision,
401 apr_pool_t *scratch_pool)
403 struct check_url_kind_baton *cukb = baton;
405 /* If we don't have a session or can't use the session, get one */
406 if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
408 SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
409 cukb->pool, scratch_pool));
410 SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
414 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
416 return svn_error_trace(
417 svn_ra_check_path(cukb->session, "", revision,
418 kind, scratch_pool));
421 /* Recurse into every target in REL_TARGETS, finding committable externals
422 * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
423 * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
424 * arguments correspond to those of svn_client_commit6(). */
426 append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
427 const char *base_abspath,
428 svn_boolean_t include_file_externals,
429 svn_boolean_t include_dir_externals,
431 svn_client_ctx_t *ctx,
432 apr_pool_t *result_pool,
433 apr_pool_t *scratch_pool)
435 int rel_targets_nelts_fixed;
437 apr_pool_t *iterpool;
439 if (! (include_file_externals || include_dir_externals))
442 /* Easy part of applying DEPTH to externals. */
443 if (depth == svn_depth_empty)
449 /* Iterate *and* grow REL_TARGETS at the same time. */
450 rel_targets_nelts_fixed = rel_targets->nelts;
452 iterpool = svn_pool_create(scratch_pool);
454 for (i = 0; i < rel_targets_nelts_fixed; i++)
458 apr_array_header_t *externals = NULL;
460 svn_pool_clear(iterpool);
462 target = svn_dirent_join(base_abspath,
463 APR_ARRAY_IDX(rel_targets, i, const char *),
466 /* ### TODO: Possible optimization: No need to do this for file targets.
467 * ### But what's cheaper, stat'ing the file system or querying the db?
470 SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
472 iterpool, iterpool));
474 if (externals != NULL)
476 const char *rel_target;
478 for (j = 0; j < externals->nelts; j++)
480 svn_wc__committable_external_info_t *xinfo =
481 APR_ARRAY_IDX(externals, j,
482 svn_wc__committable_external_info_t *);
484 if ((xinfo->kind == svn_node_file && ! include_file_externals)
485 || (xinfo->kind == svn_node_dir && ! include_dir_externals))
488 rel_target = svn_dirent_skip_ancestor(base_abspath,
489 xinfo->local_abspath);
491 SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
493 APR_ARRAY_PUSH(rel_targets, const char *) =
494 apr_pstrdup(result_pool, rel_target);
499 svn_pool_destroy(iterpool);
504 svn_client_commit6(const apr_array_header_t *targets,
506 svn_boolean_t keep_locks,
507 svn_boolean_t keep_changelists,
508 svn_boolean_t commit_as_operations,
509 svn_boolean_t include_file_externals,
510 svn_boolean_t include_dir_externals,
511 const apr_array_header_t *changelists,
512 const apr_hash_t *revprop_table,
513 svn_commit_callback2_t commit_callback,
515 svn_client_ctx_t *ctx,
518 const svn_delta_editor_t *editor;
520 struct capture_baton_t cb;
521 svn_ra_session_t *ra_session;
523 const char *base_abspath;
524 const char *base_url;
525 apr_array_header_t *rel_targets;
526 apr_array_header_t *lock_targets;
527 apr_array_header_t *locks_obtained;
528 svn_client__committables_t *committables;
529 apr_hash_t *lock_tokens;
530 apr_hash_t *sha1_checksums;
531 apr_array_header_t *commit_items;
532 svn_error_t *cmt_err = SVN_NO_ERROR;
533 svn_error_t *bump_err = SVN_NO_ERROR;
534 svn_error_t *unlock_err = SVN_NO_ERROR;
535 svn_boolean_t commit_in_progress = FALSE;
536 svn_boolean_t timestamp_sleep = FALSE;
537 svn_commit_info_t *commit_info = NULL;
538 apr_pool_t *iterpool = svn_pool_create(pool);
539 const char *current_abspath;
540 const char *notify_prefix;
541 int depth_empty_after = -1;
542 apr_hash_t *move_youngest = NULL;
545 SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
547 /* Committing URLs doesn't make sense, so error if it's tried. */
548 for (i = 0; i < targets->nelts; i++)
550 const char *target = APR_ARRAY_IDX(targets, i, const char *);
551 if (svn_path_is_url(target))
552 return svn_error_createf
553 (SVN_ERR_ILLEGAL_TARGET, NULL,
554 _("'%s' is a URL, but URLs cannot be commit targets"), target);
557 /* Condense the target list. This makes all targets absolute. */
558 SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
559 FALSE, pool, iterpool));
561 /* No targets means nothing to commit, so just return. */
562 if (base_abspath == NULL)
565 SVN_ERR_ASSERT(rel_targets != NULL);
567 /* If we calculated only a base and no relative targets, this
568 must mean that we are being asked to commit (effectively) a
570 if (rel_targets->nelts == 0)
571 APR_ARRAY_PUSH(rel_targets, const char *) = "";
573 if (include_file_externals || include_dir_externals)
575 if (depth != svn_depth_unknown && depth != svn_depth_infinity)
577 /* All targets after this will be handled as depth empty */
578 depth_empty_after = rel_targets->nelts;
581 SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
582 include_file_externals,
583 include_dir_externals,
588 SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
589 rel_targets, pool, iterpool));
591 locks_obtained = apr_array_make(pool, lock_targets->nelts,
592 sizeof(const char *));
594 for (i = 0; i < lock_targets->nelts; i++)
596 const char *lock_root;
597 const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
599 svn_pool_clear(iterpool);
601 cmt_err = svn_error_trace(
602 svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
603 FALSE, pool, iterpool));
608 APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
611 /* Determine prefix to strip from the commit notify messages */
612 SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool));
613 notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
617 /* Crawl the working copy for commit items. */
619 struct check_url_kind_baton cukb;
621 /* Prepare for when we have a copy containing not-present nodes. */
622 cukb.pool = iterpool;
623 cukb.session = NULL; /* ### Can we somehow reuse session? */
624 cukb.repos_root_url = NULL;
627 cmt_err = svn_error_trace(
628 svn_client__harvest_committables(&committables,
642 svn_pool_clear(iterpool);
648 if (apr_hash_count(committables->by_repository) == 0)
650 goto cleanup; /* Nothing to do */
652 else if (apr_hash_count(committables->by_repository) > 1)
654 cmt_err = svn_error_create(
655 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
656 _("Commit can only commit to a single repository at a time.\n"
657 "Are all targets part of the same working copy?"));
662 apr_hash_index_t *hi = apr_hash_first(iterpool,
663 committables->by_repository);
665 commit_items = apr_hash_this_val(hi);
668 /* If our array of targets contains only locks (and no actual file
669 or prop modifications), then we return here to avoid committing a
670 revision with no changes. */
672 svn_boolean_t found_changed_path = FALSE;
674 for (i = 0; i < commit_items->nelts; ++i)
676 svn_client_commit_item3_t *item =
677 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
679 if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
681 found_changed_path = TRUE;
686 if (!found_changed_path)
690 /* For every target that was moved verify that both halves of the
691 * move are part of the commit. */
692 for (i = 0; i < commit_items->nelts; i++)
694 svn_client_commit_item3_t *item =
695 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
697 svn_pool_clear(iterpool);
699 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
701 /* ### item->moved_from_abspath contains the move origin */
702 const char *moved_from_abspath;
703 const char *delete_op_root_abspath;
705 cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
707 &delete_op_root_abspath,
708 ctx->wc_ctx, item->path,
709 iterpool, iterpool));
713 if (moved_from_abspath && delete_op_root_abspath)
715 svn_client_commit_item3_t *delete_half =
716 svn_hash_gets(committables->by_path, delete_op_root_abspath);
720 cmt_err = svn_error_createf(
721 SVN_ERR_ILLEGAL_TARGET, NULL,
722 _("Cannot commit '%s' because it was moved from "
723 "'%s' which is not part of the commit; both "
724 "sides of the move must be committed together"),
725 svn_dirent_local_style(item->path, iterpool),
726 svn_dirent_local_style(delete_op_root_abspath,
729 if (ctx->notify_func2)
731 svn_wc_notify_t *notify;
732 notify = svn_wc_create_notify(
733 delete_op_root_abspath,
734 svn_wc_notify_failed_requires_target,
736 notify->err = cmt_err;
738 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
743 else if (delete_half->revision == item->copyfrom_rev)
745 /* Ok, now we know that we perform an out-of-date check
746 on the copyfrom location. Remember this for a fixup
747 round right before committing. */
750 move_youngest = apr_hash_make(pool);
752 svn_hash_sets(move_youngest, item->path, item);
757 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
759 const char *moved_to_abspath;
760 const char *copy_op_root_abspath;
762 cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
764 ©_op_root_abspath,
765 ctx->wc_ctx, item->path,
766 iterpool, iterpool));
770 if (moved_to_abspath && copy_op_root_abspath &&
771 strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
772 svn_hash_gets(committables->by_path, copy_op_root_abspath)
775 cmt_err = svn_error_createf(
776 SVN_ERR_ILLEGAL_TARGET, NULL,
777 _("Cannot commit '%s' because it was moved to '%s' "
778 "which is not part of the commit; both sides of "
779 "the move must be committed together"),
780 svn_dirent_local_style(item->path, iterpool),
781 svn_dirent_local_style(copy_op_root_abspath,
784 if (ctx->notify_func2)
786 svn_wc_notify_t *notify;
787 notify = svn_wc_create_notify(
788 copy_op_root_abspath,
789 svn_wc_notify_failed_requires_target,
791 notify->err = cmt_err;
793 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
801 /* Go get a log message. If an error occurs, or no log message is
802 specified, abort the operation. */
803 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
805 const char *tmp_file;
806 cmt_err = svn_error_trace(
807 svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
810 if (cmt_err || (! log_msg))
816 /* Sort and condense our COMMIT_ITEMS. */
817 cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
824 /* Collect our lock tokens with paths relative to base_url. */
825 cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
831 cb.original_callback = commit_callback;
832 cb.original_baton = commit_baton;
833 cb.info = &commit_info;
836 /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
837 * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
838 * parent of nested working copies. We don't support commits to multiple
839 * repositories so using the first WC to get the RA session is safe. */
840 cmt_err = svn_error_trace(
841 svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
842 APR_ARRAY_IDX(lock_targets,
852 if (move_youngest != NULL)
854 apr_hash_index_t *hi;
855 svn_revnum_t youngest;
857 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
859 for (hi = apr_hash_first(iterpool, move_youngest);
861 hi = apr_hash_next(hi))
863 svn_client_commit_item3_t *item = apr_hash_this_val(hi);
865 /* We delete the original side with its original revision and will
866 receive an out-of-date error if that node changed since that
869 The copy is of that same revision and we know that this revision
870 didn't change between this revision and youngest. So we can just
871 as well commit a copy from youngest.
873 Note that it is still possible to see gaps between the delete and
874 copy revisions as the repository might handle multiple commits
875 at the same time (or when an out of date proxy is involved), but
876 in general it should decrease the number of gaps. */
878 if (item->copyfrom_rev < youngest)
879 item->copyfrom_rev = youngest;
883 cmt_err = svn_error_trace(
884 get_ra_editor(&editor, &edit_baton, ra_session, ctx,
885 log_msg, commit_items, revprop_table,
886 lock_tokens, keep_locks, capture_commit_info,
892 /* Make a note that we have a commit-in-progress. */
893 commit_in_progress = TRUE;
895 /* We'll assume that, once we pass this point, we are going to need to
896 * sleep for timestamps. Really, we may not need to do unless and until
897 * we reach the point where we post-commit 'bump' the WC metadata. */
898 timestamp_sleep = TRUE;
900 /* Perform the commit. */
901 cmt_err = svn_error_trace(
902 svn_client__do_commit(base_url, commit_items, editor, edit_baton,
903 notify_prefix, &sha1_checksums, ctx, pool,
906 /* Handle a successful commit. */
908 || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
910 svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
912 /* Make a note that our commit is finished. */
913 commit_in_progress = FALSE;
915 for (i = 0; i < commit_items->nelts; i++)
917 svn_client_commit_item3_t *item
918 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
920 svn_pool_clear(iterpool);
921 bump_err = post_process_commit_item(
922 queue, item, ctx->wc_ctx,
923 keep_changelists, keep_locks, commit_as_operations,
924 svn_hash_gets(sha1_checksums, item->path),
930 SVN_ERR_ASSERT(commit_info);
931 bump_err = svn_wc_process_committed_queue2(
933 commit_info->revision,
936 ctx->cancel_func, ctx->cancel_baton,
944 /* Sleep to ensure timestamp integrity. BASE_ABSPATH may have been
945 removed by the commit or it may the common ancestor of multiple
949 const char *sleep_abspath;
950 svn_error_t *err = svn_wc__get_wcroot(&sleep_abspath, ctx->wc_ctx,
951 base_abspath, pool, pool);
954 svn_error_clear(err);
955 sleep_abspath = base_abspath;
958 svn_io_sleep_for_timestamps(sleep_abspath, pool);
961 /* Abort the commit if it is still in progress. */
962 svn_pool_clear(iterpool); /* Close open handles before aborting */
963 if (commit_in_progress)
964 cmt_err = svn_error_compose_create(cmt_err,
965 editor->abort_edit(edit_baton, pool));
967 /* A bump error is likely to occur while running a working copy log file,
968 explicitly unlocking and removing temporary files would be wrong in
969 that case. A commit error (cmt_err) should only occur before any
970 attempt to modify the working copy, so it doesn't prevent explicit
974 /* Release all locks we obtained */
975 for (i = 0; i < locks_obtained->nelts; i++)
977 const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
980 svn_pool_clear(iterpool);
982 unlock_err = svn_error_compose_create(
983 svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
989 svn_pool_destroy(iterpool);
991 return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,