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"
49 #include "svn_private_config.h"
51 struct capture_baton_t {
52 svn_commit_callback2_t original_callback;
55 svn_commit_info_t **info;
61 capture_commit_info(const svn_commit_info_t *commit_info,
65 struct capture_baton_t *cb = baton;
67 *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
69 if (cb->original_callback)
70 SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
77 get_ra_editor(const svn_delta_editor_t **editor,
79 svn_ra_session_t *ra_session,
80 svn_client_ctx_t *ctx,
82 const apr_array_header_t *commit_items,
83 const apr_hash_t *revprop_table,
84 apr_hash_t *lock_tokens,
85 svn_boolean_t keep_locks,
86 svn_commit_callback2_t commit_callback,
90 apr_hash_t *commit_revprops;
91 apr_hash_t *relpath_map = NULL;
93 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
96 #ifdef ENABLE_EV2_SHIMS
100 apr_pool_t *iterpool = svn_pool_create(pool);
102 relpath_map = apr_hash_make(pool);
103 for (i = 0; i < commit_items->nelts; i++)
105 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
106 svn_client_commit_item3_t *);
112 svn_pool_clear(iterpool);
113 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
114 ctx->wc_ctx, item->path, FALSE, pool,
117 svn_hash_sets(relpath_map, relpath, item->path);
119 svn_pool_destroy(iterpool);
123 /* Fetch RA commit editor. */
124 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
125 svn_client__get_shim_callbacks(ctx->wc_ctx,
126 relpath_map, pool)));
127 SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
128 commit_revprops, commit_callback,
129 commit_baton, lock_tokens, keep_locks,
136 /*** Public Interfaces. ***/
139 reconcile_errors(svn_error_t *commit_err,
140 svn_error_t *unlock_err,
141 svn_error_t *bump_err,
146 /* Early release (for good behavior). */
147 if (! (commit_err || unlock_err || bump_err))
150 /* If there was a commit error, start off our error chain with
154 commit_err = svn_error_quick_wrap
155 (commit_err, _("Commit failed (details follow):"));
159 /* Else, create a new "general" error that will lead off the errors
162 err = svn_error_create(SVN_ERR_BASE, NULL,
163 _("Commit succeeded, but other errors follow:"));
165 /* If there was an unlock error... */
168 /* Wrap the error with some headers. */
169 unlock_err = svn_error_quick_wrap
170 (unlock_err, _("Error unlocking locked dirs (details follow):"));
172 /* Append this error to the chain. */
173 svn_error_compose(err, unlock_err);
176 /* If there was a bumping error... */
179 /* Wrap the error with some headers. */
180 bump_err = svn_error_quick_wrap
181 (bump_err, _("Error bumping revisions post-commit (details follow):"));
183 /* Append this error to the chain. */
184 svn_error_compose(err, bump_err);
190 /* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
191 to a new hashtable allocated in POOL. *RESULT is set to point to this
192 new hash table. *RESULT will be keyed on const char * URI-decoded paths
193 relative to BASE_URL. The lock tokens will not be duplicated. */
195 collect_lock_tokens(apr_hash_t **result,
196 apr_hash_t *all_tokens,
197 const char *base_url,
200 apr_hash_index_t *hi;
202 *result = apr_hash_make(pool);
204 for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
206 const char *url = svn__apr_hash_index_key(hi);
207 const char *token = svn__apr_hash_index_val(hi);
208 const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
212 svn_hash_sets(*result, relpath, token);
219 /* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
220 * If a checksum is provided, it can be the MD5 and/or the SHA1. */
222 post_process_commit_item(svn_wc_committed_queue_t *queue,
223 const svn_client_commit_item3_t *item,
224 svn_wc_context_t *wc_ctx,
225 svn_boolean_t keep_changelists,
226 svn_boolean_t keep_locks,
227 svn_boolean_t commit_as_operations,
228 const svn_checksum_t *sha1_checksum,
229 apr_pool_t *scratch_pool)
231 svn_boolean_t loop_recurse = FALSE;
232 svn_boolean_t remove_lock;
234 if (! commit_as_operations
235 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
236 && (item->kind == svn_node_dir)
237 && (item->copyfrom_url))
240 remove_lock = (! keep_locks && (item->state_flags
241 & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
243 /* When the node was deleted (or replaced), we need to always remove the
244 locks, as they're invalidated on the server. We cannot honor the
245 SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell
246 us whether we have locked children. */
247 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
250 return svn_wc_queue_committed3(queue, wc_ctx, item->path,
251 loop_recurse, item->incoming_prop_changes,
252 remove_lock, !keep_changelists,
253 sha1_checksum, scratch_pool);
258 check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx,
259 const char *target_abspath,
261 apr_pool_t *scratch_pool)
263 svn_node_kind_t kind;
265 SVN_ERR_ASSERT(depth != svn_depth_infinity);
267 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath,
268 TRUE, FALSE, scratch_pool));
271 /* ### TODO(sd): This check is slightly too strict. It should be
274 ### * delete a directory containing only files when
275 ### depth==svn_depth_files;
277 ### * delete a directory containing only files and empty
278 ### subdirs when depth==svn_depth_immediates.
280 ### But for now, we insist on svn_depth_infinity if you're
281 ### going to delete a directory, because we're lazy and
282 ### trying to get depthy commits working in the first place.
284 ### This would be fairly easy to fix, though: just, well,
285 ### check the above conditions!
287 ### GJS: I think there may be some confusion here. there is
288 ### the depth of the commit, and the depth of a checked-out
289 ### directory in the working copy. Delete, by its nature, will
290 ### always delete all of its children, so it seems a bit
291 ### strange to worry about what is in the working copy.
293 if (kind == svn_node_dir)
295 svn_wc_schedule_t schedule;
297 /* ### Looking at schedule is probably enough, no need for
298 pristine compare etc. */
299 SVN_ERR(svn_wc__node_get_schedule(&schedule, NULL,
300 wc_ctx, target_abspath,
303 if (schedule == svn_wc_schedule_delete
304 || schedule == svn_wc_schedule_replace)
306 const apr_array_header_t *children;
308 SVN_ERR(svn_wc__node_get_children(&children, wc_ctx,
309 target_abspath, TRUE,
310 scratch_pool, scratch_pool));
312 if (children->nelts > 0)
313 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
314 _("Cannot delete the directory '%s' "
315 "in a non-recursive commit "
316 "because it has children"),
317 svn_dirent_local_style(target_abspath,
326 /* Given a list of committables described by their common base abspath
327 BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
328 which absolute paths must be locked to commit all these targets and
329 return this as a const char * array in LOCK_TARGETS
331 Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
334 determine_lock_targets(apr_array_header_t **lock_targets,
335 svn_wc_context_t *wc_ctx,
336 const char *base_abspath,
337 const apr_array_header_t *target_relpaths,
338 apr_pool_t *result_pool,
339 apr_pool_t *scratch_pool)
341 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
342 apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
343 apr_hash_index_t *hi;
346 wc_items = apr_hash_make(scratch_pool);
348 /* Create an array of targets for each working copy used */
349 for (i = 0; i < target_relpaths->nelts; i++)
351 const char *target_abspath;
352 const char *wcroot_abspath;
353 apr_array_header_t *wc_targets;
355 const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
358 svn_pool_clear(iterpool);
359 target_abspath = svn_dirent_join(base_abspath, target_relpath,
362 err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
367 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
369 svn_error_clear(err);
372 return svn_error_trace(err);
375 wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
379 wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
380 svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
384 APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
387 *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
388 sizeof(const char *));
390 /* For each working copy determine where to lock */
391 for (hi = apr_hash_first(scratch_pool, wc_items);
393 hi = apr_hash_next(hi))
396 const char *wcroot_abspath = svn__apr_hash_index_key(hi);
397 apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi);
399 svn_pool_clear(iterpool);
401 if (wc_targets->nelts == 1)
403 const char *target_abspath;
404 target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
406 if (! strcmp(wcroot_abspath, target_abspath))
408 APR_ARRAY_PUSH(*lock_targets, const char *)
409 = apr_pstrdup(result_pool, target_abspath);
413 /* Lock the parent to allow deleting the target */
414 APR_ARRAY_PUSH(*lock_targets, const char *)
415 = svn_dirent_dirname(target_abspath, result_pool);
418 else if (wc_targets->nelts > 1)
420 SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
421 FALSE, iterpool, iterpool));
423 qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size,
424 svn_sort_compare_paths);
426 if (wc_targets->nelts == 0
427 || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
428 || !strcmp(common, wcroot_abspath))
430 APR_ARRAY_PUSH(*lock_targets, const char *)
431 = apr_pstrdup(result_pool, common);
435 /* Lock the parent to allow deleting the target */
436 APR_ARRAY_PUSH(*lock_targets, const char *)
437 = svn_dirent_dirname(common, result_pool);
442 svn_pool_destroy(iterpool);
446 /* Baton for check_url_kind */
447 struct check_url_kind_baton
450 svn_ra_session_t *session;
451 const char *repos_root_url;
452 svn_client_ctx_t *ctx;
455 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
457 check_url_kind(void *baton,
458 svn_node_kind_t *kind,
460 svn_revnum_t revision,
461 apr_pool_t *scratch_pool)
463 struct check_url_kind_baton *cukb = baton;
465 /* If we don't have a session or can't use the session, get one */
466 if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
468 SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
469 cukb->pool, scratch_pool));
470 SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
474 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
476 return svn_error_trace(
477 svn_ra_check_path(cukb->session, "", revision,
478 kind, scratch_pool));
481 /* Recurse into every target in REL_TARGETS, finding committable externals
482 * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
483 * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
484 * arguments correspond to those of svn_client_commit6(). */
486 append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
487 const char *base_abspath,
488 svn_boolean_t include_file_externals,
489 svn_boolean_t include_dir_externals,
491 svn_client_ctx_t *ctx,
492 apr_pool_t *result_pool,
493 apr_pool_t *scratch_pool)
495 int rel_targets_nelts_fixed;
497 apr_pool_t *iterpool;
499 if (! (include_file_externals || include_dir_externals))
502 /* Easy part of applying DEPTH to externals. */
503 if (depth == svn_depth_empty)
509 /* Iterate *and* grow REL_TARGETS at the same time. */
510 rel_targets_nelts_fixed = rel_targets->nelts;
512 iterpool = svn_pool_create(scratch_pool);
514 for (i = 0; i < rel_targets_nelts_fixed; i++)
518 apr_array_header_t *externals = NULL;
520 svn_pool_clear(iterpool);
522 target = svn_dirent_join(base_abspath,
523 APR_ARRAY_IDX(rel_targets, i, const char *),
526 /* ### TODO: Possible optimization: No need to do this for file targets.
527 * ### But what's cheaper, stat'ing the file system or querying the db?
530 SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
532 iterpool, iterpool));
534 if (externals != NULL)
536 const char *rel_target;
538 for (j = 0; j < externals->nelts; j++)
540 svn_wc__committable_external_info_t *xinfo =
541 APR_ARRAY_IDX(externals, j,
542 svn_wc__committable_external_info_t *);
544 if ((xinfo->kind == svn_node_file && ! include_file_externals)
545 || (xinfo->kind == svn_node_dir && ! include_dir_externals))
548 rel_target = svn_dirent_skip_ancestor(base_abspath,
549 xinfo->local_abspath);
551 SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
553 APR_ARRAY_PUSH(rel_targets, const char *) =
554 apr_pstrdup(result_pool, rel_target);
559 svn_pool_destroy(iterpool);
564 svn_client_commit6(const apr_array_header_t *targets,
566 svn_boolean_t keep_locks,
567 svn_boolean_t keep_changelists,
568 svn_boolean_t commit_as_operations,
569 svn_boolean_t include_file_externals,
570 svn_boolean_t include_dir_externals,
571 const apr_array_header_t *changelists,
572 const apr_hash_t *revprop_table,
573 svn_commit_callback2_t commit_callback,
575 svn_client_ctx_t *ctx,
578 const svn_delta_editor_t *editor;
580 struct capture_baton_t cb;
581 svn_ra_session_t *ra_session;
583 const char *base_abspath;
584 const char *base_url;
585 apr_array_header_t *rel_targets;
586 apr_array_header_t *lock_targets;
587 apr_array_header_t *locks_obtained;
588 svn_client__committables_t *committables;
589 apr_hash_t *lock_tokens;
590 apr_hash_t *sha1_checksums;
591 apr_array_header_t *commit_items;
592 svn_error_t *cmt_err = SVN_NO_ERROR;
593 svn_error_t *bump_err = SVN_NO_ERROR;
594 svn_error_t *unlock_err = SVN_NO_ERROR;
595 svn_boolean_t commit_in_progress = FALSE;
596 svn_boolean_t timestamp_sleep = FALSE;
597 svn_commit_info_t *commit_info = NULL;
598 apr_pool_t *iterpool = svn_pool_create(pool);
599 const char *current_abspath;
600 const char *notify_prefix;
601 int depth_empty_after = -1;
604 SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
606 /* Committing URLs doesn't make sense, so error if it's tried. */
607 for (i = 0; i < targets->nelts; i++)
609 const char *target = APR_ARRAY_IDX(targets, i, const char *);
610 if (svn_path_is_url(target))
611 return svn_error_createf
612 (SVN_ERR_ILLEGAL_TARGET, NULL,
613 _("'%s' is a URL, but URLs cannot be commit targets"), target);
616 /* Condense the target list. This makes all targets absolute. */
617 SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
618 FALSE, pool, iterpool));
620 /* No targets means nothing to commit, so just return. */
621 if (base_abspath == NULL)
624 SVN_ERR_ASSERT(rel_targets != NULL);
626 /* If we calculated only a base and no relative targets, this
627 must mean that we are being asked to commit (effectively) a
629 if (rel_targets->nelts == 0)
630 APR_ARRAY_PUSH(rel_targets, const char *) = "";
632 if (include_file_externals || include_dir_externals)
634 if (depth != svn_depth_unknown && depth != svn_depth_infinity)
636 /* All targets after this will be handled as depth empty */
637 depth_empty_after = rel_targets->nelts;
640 SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
641 include_file_externals,
642 include_dir_externals,
647 SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
648 rel_targets, pool, iterpool));
650 locks_obtained = apr_array_make(pool, lock_targets->nelts,
651 sizeof(const char *));
653 for (i = 0; i < lock_targets->nelts; i++)
655 const char *lock_root;
656 const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
658 svn_pool_clear(iterpool);
660 cmt_err = svn_error_trace(
661 svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
662 FALSE, pool, iterpool));
667 APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
670 /* Determine prefix to strip from the commit notify messages */
671 SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool));
672 notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
676 /* If a non-recursive commit is desired, do not allow a deleted directory
677 as one of the targets. */
678 if (depth != svn_depth_infinity && ! commit_as_operations)
679 for (i = 0; i < rel_targets->nelts; i++)
681 const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *);
682 const char *target_abspath;
684 svn_pool_clear(iterpool);
686 target_abspath = svn_dirent_join(base_abspath, relpath, iterpool);
688 cmt_err = svn_error_trace(
689 check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath,
696 /* Crawl the working copy for commit items. */
698 struct check_url_kind_baton cukb;
700 /* Prepare for when we have a copy containing not-present nodes. */
701 cukb.pool = iterpool;
702 cukb.session = NULL; /* ### Can we somehow reuse session? */
703 cukb.repos_root_url = NULL;
706 cmt_err = svn_error_trace(
707 svn_client__harvest_committables(&committables,
721 svn_pool_clear(iterpool);
727 if (apr_hash_count(committables->by_repository) == 0)
729 goto cleanup; /* Nothing to do */
731 else if (apr_hash_count(committables->by_repository) > 1)
733 cmt_err = svn_error_create(
734 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
735 _("Commit can only commit to a single repository at a time.\n"
736 "Are all targets part of the same working copy?"));
741 apr_hash_index_t *hi = apr_hash_first(iterpool,
742 committables->by_repository);
744 commit_items = svn__apr_hash_index_val(hi);
747 /* If our array of targets contains only locks (and no actual file
748 or prop modifications), then we return here to avoid committing a
749 revision with no changes. */
751 svn_boolean_t found_changed_path = FALSE;
753 for (i = 0; i < commit_items->nelts; ++i)
755 svn_client_commit_item3_t *item =
756 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
758 if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
760 found_changed_path = TRUE;
765 if (!found_changed_path)
769 /* For every target that was moved verify that both halves of the
770 * move are part of the commit. */
771 for (i = 0; i < commit_items->nelts; i++)
773 svn_client_commit_item3_t *item =
774 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
776 svn_pool_clear(iterpool);
778 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
780 /* ### item->moved_from_abspath contains the move origin */
781 const char *moved_from_abspath;
782 const char *delete_op_root_abspath;
784 cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
786 &delete_op_root_abspath,
787 ctx->wc_ctx, item->path,
788 iterpool, iterpool));
792 if (moved_from_abspath && delete_op_root_abspath &&
793 strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
796 svn_boolean_t found_delete_half =
797 (svn_hash_gets(committables->by_path, delete_op_root_abspath)
800 if (!found_delete_half)
802 const char *delete_half_parent_abspath;
804 /* The delete-half isn't in the commit target list.
805 * However, it might itself be the child of a deleted node,
806 * either because of another move or a deletion.
808 * For example, consider: mv A/B B; mv B/C C; commit;
809 * C's moved-from A/B/C is a child of the deleted A/B.
810 * A/B/C does not appear in the commit target list, but
812 * (Note that moved-from information is always stored
813 * relative to the BASE tree, so we have 'C moved-from
814 * A/B/C', not 'C moved-from B/C'.)
816 * An example involving a move and a delete would be:
817 * mv A/B C; rm A; commit;
818 * Now C is moved-from A/B which does not appear in the
819 * commit target list, but A does appear.
822 /* Scan upwards for a deletion op-root from the
823 * delete-half's parent directory. */
824 delete_half_parent_abspath =
825 svn_dirent_dirname(delete_op_root_abspath, iterpool);
826 if (strcmp(delete_op_root_abspath,
827 delete_half_parent_abspath) != 0)
829 const char *parent_delete_op_root_abspath;
831 cmt_err = svn_error_trace(
832 svn_wc__node_get_deleted_ancestor(
833 &parent_delete_op_root_abspath,
834 ctx->wc_ctx, delete_half_parent_abspath,
835 iterpool, iterpool));
839 if (parent_delete_op_root_abspath)
841 (svn_hash_gets(committables->by_path,
842 parent_delete_op_root_abspath)
847 if (!found_delete_half)
849 cmt_err = svn_error_createf(
850 SVN_ERR_ILLEGAL_TARGET, NULL,
851 _("Cannot commit '%s' because it was moved from "
852 "'%s' which is not part of the commit; both "
853 "sides of the move must be committed together"),
854 svn_dirent_local_style(item->path, iterpool),
855 svn_dirent_local_style(delete_op_root_abspath,
862 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
864 const char *moved_to_abspath;
865 const char *copy_op_root_abspath;
867 cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
869 ©_op_root_abspath,
870 ctx->wc_ctx, item->path,
871 iterpool, iterpool));
875 if (moved_to_abspath && copy_op_root_abspath &&
876 strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
877 svn_hash_gets(committables->by_path, copy_op_root_abspath)
880 cmt_err = svn_error_createf(
881 SVN_ERR_ILLEGAL_TARGET, NULL,
882 _("Cannot commit '%s' because it was moved to '%s' "
883 "which is not part of the commit; both sides of "
884 "the move must be committed together"),
885 svn_dirent_local_style(item->path, iterpool),
886 svn_dirent_local_style(copy_op_root_abspath,
893 /* Go get a log message. If an error occurs, or no log message is
894 specified, abort the operation. */
895 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
897 const char *tmp_file;
898 cmt_err = svn_error_trace(
899 svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
902 if (cmt_err || (! log_msg))
908 /* Sort and condense our COMMIT_ITEMS. */
909 cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
916 /* Collect our lock tokens with paths relative to base_url. */
917 cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
923 cb.original_callback = commit_callback;
924 cb.original_baton = commit_baton;
925 cb.info = &commit_info;
928 /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
929 * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
930 * parent of nested working copies. We don't support commits to multiple
931 * repositories so using the first WC to get the RA session is safe. */
932 cmt_err = svn_error_trace(
933 svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
934 APR_ARRAY_IDX(lock_targets,
944 cmt_err = svn_error_trace(
945 get_ra_editor(&editor, &edit_baton, ra_session, ctx,
946 log_msg, commit_items, revprop_table,
947 lock_tokens, keep_locks, capture_commit_info,
953 /* Make a note that we have a commit-in-progress. */
954 commit_in_progress = TRUE;
956 /* We'll assume that, once we pass this point, we are going to need to
957 * sleep for timestamps. Really, we may not need to do unless and until
958 * we reach the point where we post-commit 'bump' the WC metadata. */
959 timestamp_sleep = TRUE;
961 /* Perform the commit. */
962 cmt_err = svn_error_trace(
963 svn_client__do_commit(base_url, commit_items, editor, edit_baton,
964 notify_prefix, &sha1_checksums, ctx, pool,
967 /* Handle a successful commit. */
969 || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
971 svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
973 /* Make a note that our commit is finished. */
974 commit_in_progress = FALSE;
976 for (i = 0; i < commit_items->nelts; i++)
978 svn_client_commit_item3_t *item
979 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
981 svn_pool_clear(iterpool);
982 bump_err = post_process_commit_item(
983 queue, item, ctx->wc_ctx,
984 keep_changelists, keep_locks, commit_as_operations,
985 svn_hash_gets(sha1_checksums, item->path),
991 SVN_ERR_ASSERT(commit_info);
992 bump_err = svn_wc_process_committed_queue2(
994 commit_info->revision,
997 ctx->cancel_func, ctx->cancel_baton,
1002 /* Sleep to ensure timestamp integrity. */
1003 if (timestamp_sleep)
1004 svn_io_sleep_for_timestamps(base_abspath, pool);
1006 /* Abort the commit if it is still in progress. */
1007 svn_pool_clear(iterpool); /* Close open handles before aborting */
1008 if (commit_in_progress)
1009 cmt_err = svn_error_compose_create(cmt_err,
1010 editor->abort_edit(edit_baton, pool));
1012 /* A bump error is likely to occur while running a working copy log file,
1013 explicitly unlocking and removing temporary files would be wrong in
1014 that case. A commit error (cmt_err) should only occur before any
1015 attempt to modify the working copy, so it doesn't prevent explicit
1019 /* Release all locks we obtained */
1020 for (i = 0; i < locks_obtained->nelts; i++)
1022 const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1025 svn_pool_clear(iterpool);
1027 unlock_err = svn_error_compose_create(
1028 svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1034 svn_pool_destroy(iterpool);
1036 return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,