]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/commit.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_client / commit.c
1 /*
2  * commit.c:  wrappers around wc commit functionality.
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26
27 \f
28 /*** Includes. ***/
29
30 #include <string.h>
31 #include <apr_strings.h>
32 #include <apr_hash.h>
33 #include "svn_hash.h"
34 #include "svn_wc.h"
35 #include "svn_ra.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"
42 #include "svn_path.h"
43 #include "svn_sorts.h"
44
45 #include "client.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_ra_private.h"
48
49 #include "svn_private_config.h"
50
51 struct capture_baton_t {
52   svn_commit_callback2_t original_callback;
53   void *original_baton;
54
55   svn_commit_info_t **info;
56   apr_pool_t *pool;
57 };
58
59
60 static svn_error_t *
61 capture_commit_info(const svn_commit_info_t *commit_info,
62                     void *baton,
63                     apr_pool_t *pool)
64 {
65   struct capture_baton_t *cb = baton;
66
67   *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
68
69   if (cb->original_callback)
70     SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
71
72   return SVN_NO_ERROR;
73 }
74
75
76 static svn_error_t *
77 get_ra_editor(const svn_delta_editor_t **editor,
78               void **edit_baton,
79               svn_ra_session_t *ra_session,
80               svn_client_ctx_t *ctx,
81               const char *log_msg,
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,
87               void *commit_baton,
88               apr_pool_t *pool)
89 {
90   apr_hash_t *commit_revprops;
91   apr_hash_t *relpath_map = NULL;
92
93   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
94                                            log_msg, ctx, pool));
95
96 #ifdef ENABLE_EV2_SHIMS
97   if (commit_items)
98     {
99       int i;
100       apr_pool_t *iterpool = svn_pool_create(pool);
101
102       relpath_map = apr_hash_make(pool);
103       for (i = 0; i < commit_items->nelts; i++)
104         {
105           svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
106                                                   svn_client_commit_item3_t *);
107           const char *relpath;
108
109           if (!item->path)
110             continue;
111
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,
115                                           iterpool));
116           if (relpath)
117             svn_hash_sets(relpath_map, relpath, item->path);
118         }
119       svn_pool_destroy(iterpool);
120     }
121 #endif
122
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,
130                                     pool));
131
132   return SVN_NO_ERROR;
133 }
134
135 \f
136 /*** Public Interfaces. ***/
137
138 static svn_error_t *
139 reconcile_errors(svn_error_t *commit_err,
140                  svn_error_t *unlock_err,
141                  svn_error_t *bump_err,
142                  apr_pool_t *pool)
143 {
144   svn_error_t *err;
145
146   /* Early release (for good behavior). */
147   if (! (commit_err || unlock_err || bump_err))
148     return SVN_NO_ERROR;
149
150   /* If there was a commit error, start off our error chain with
151      that. */
152   if (commit_err)
153     {
154       commit_err = svn_error_quick_wrap
155         (commit_err, _("Commit failed (details follow):"));
156       err = commit_err;
157     }
158
159   /* Else, create a new "general" error that will lead off the errors
160      that follow. */
161   else
162     err = svn_error_create(SVN_ERR_BASE, NULL,
163                            _("Commit succeeded, but other errors follow:"));
164
165   /* If there was an unlock error... */
166   if (unlock_err)
167     {
168       /* Wrap the error with some headers. */
169       unlock_err = svn_error_quick_wrap
170         (unlock_err, _("Error unlocking locked dirs (details follow):"));
171
172       /* Append this error to the chain. */
173       svn_error_compose(err, unlock_err);
174     }
175
176   /* If there was a bumping error... */
177   if (bump_err)
178     {
179       /* Wrap the error with some headers. */
180       bump_err = svn_error_quick_wrap
181         (bump_err, _("Error bumping revisions post-commit (details follow):"));
182
183       /* Append this error to the chain. */
184       svn_error_compose(err, bump_err);
185     }
186
187   return err;
188 }
189
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. */
194 static svn_error_t *
195 collect_lock_tokens(apr_hash_t **result,
196                     apr_hash_t *all_tokens,
197                     const char *base_url,
198                     apr_pool_t *pool)
199 {
200   apr_hash_index_t *hi;
201
202   *result = apr_hash_make(pool);
203
204   for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
205     {
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);
209
210       if (relpath)
211         {
212           svn_hash_sets(*result, relpath, token);
213         }
214     }
215
216   return SVN_NO_ERROR;
217 }
218
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. */
221 static svn_error_t *
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)
230 {
231   svn_boolean_t loop_recurse = FALSE;
232   svn_boolean_t remove_lock;
233
234   if (! commit_as_operations
235       && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
236       && (item->kind == svn_node_dir)
237       && (item->copyfrom_url))
238     loop_recurse = TRUE;
239
240   remove_lock = (! keep_locks && (item->state_flags
241                                        & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
242
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)
248     remove_lock = TRUE;
249
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);
254 }
255
256
257 static svn_error_t *
258 check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx,
259                               const char *target_abspath,
260                               svn_depth_t depth,
261                               apr_pool_t *scratch_pool)
262 {
263   svn_node_kind_t kind;
264
265   SVN_ERR_ASSERT(depth != svn_depth_infinity);
266
267   SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath,
268                             TRUE, FALSE, scratch_pool));
269
270
271   /* ### TODO(sd): This check is slightly too strict.  It should be
272      ### possible to:
273      ###
274      ###   * delete a directory containing only files when
275      ###     depth==svn_depth_files;
276      ###
277      ###   * delete a directory containing only files and empty
278      ###     subdirs when depth==svn_depth_immediates.
279      ###
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.
283      ###
284      ### This would be fairly easy to fix, though: just, well,
285      ### check the above conditions!
286      ###
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.
292   */
293   if (kind == svn_node_dir)
294     {
295       svn_wc_schedule_t schedule;
296
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,
301                                         scratch_pool));
302
303       if (schedule == svn_wc_schedule_delete
304           || schedule == svn_wc_schedule_replace)
305         {
306           const apr_array_header_t *children;
307
308           SVN_ERR(svn_wc__node_get_children(&children, wc_ctx,
309                                             target_abspath, TRUE,
310                                             scratch_pool, scratch_pool));
311
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,
318                                                             scratch_pool));
319         }
320     }
321
322   return SVN_NO_ERROR;
323 }
324
325
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
330
331    Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
332    storage */
333 static svn_error_t *
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)
340 {
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;
344   int i;
345
346   wc_items = apr_hash_make(scratch_pool);
347
348   /* Create an array of targets for each working copy used */
349   for (i = 0; i < target_relpaths->nelts; i++)
350     {
351       const char *target_abspath;
352       const char *wcroot_abspath;
353       apr_array_header_t *wc_targets;
354       svn_error_t *err;
355       const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
356                                                  const char *);
357
358       svn_pool_clear(iterpool);
359       target_abspath = svn_dirent_join(base_abspath, target_relpath,
360                                        scratch_pool);
361
362       err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
363                                iterpool, iterpool);
364
365       if (err)
366         {
367           if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
368             {
369               svn_error_clear(err);
370               continue;
371             }
372           return svn_error_trace(err);
373         }
374
375       wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
376
377       if (! wc_targets)
378         {
379           wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
380           svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
381                         wc_targets);
382         }
383
384       APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
385     }
386
387   *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
388                                  sizeof(const char *));
389
390   /* For each working copy determine where to lock */
391   for (hi = apr_hash_first(scratch_pool, wc_items);
392        hi;
393        hi = apr_hash_next(hi))
394     {
395       const char *common;
396       const char *wcroot_abspath = svn__apr_hash_index_key(hi);
397       apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi);
398
399       svn_pool_clear(iterpool);
400
401       if (wc_targets->nelts == 1)
402         {
403           const char *target_abspath;
404           target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
405
406           if (! strcmp(wcroot_abspath, target_abspath))
407             {
408               APR_ARRAY_PUSH(*lock_targets, const char *)
409                       = apr_pstrdup(result_pool, target_abspath);
410             }
411           else
412             {
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);
416             }
417         }
418       else if (wc_targets->nelts > 1)
419         {
420           SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
421                                               FALSE, iterpool, iterpool));
422
423           qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size,
424                 svn_sort_compare_paths);
425
426           if (wc_targets->nelts == 0
427               || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
428               || !strcmp(common, wcroot_abspath))
429             {
430               APR_ARRAY_PUSH(*lock_targets, const char *)
431                     = apr_pstrdup(result_pool, common);
432             }
433           else
434             {
435               /* Lock the parent to allow deleting the target */
436               APR_ARRAY_PUSH(*lock_targets, const char *)
437                        = svn_dirent_dirname(common, result_pool);
438             }
439         }
440     }
441
442   svn_pool_destroy(iterpool);
443   return SVN_NO_ERROR;
444 }
445
446 /* Baton for check_url_kind */
447 struct check_url_kind_baton
448 {
449   apr_pool_t *pool;
450   svn_ra_session_t *session;
451   const char *repos_root_url;
452   svn_client_ctx_t *ctx;
453 };
454
455 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
456 static svn_error_t *
457 check_url_kind(void *baton,
458                svn_node_kind_t *kind,
459                const char *url,
460                svn_revnum_t revision,
461                apr_pool_t *scratch_pool)
462 {
463   struct check_url_kind_baton *cukb = baton;
464
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))
467     {
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,
471                                      cukb->pool));
472     }
473   else
474     SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
475
476   return svn_error_trace(
477                 svn_ra_check_path(cukb->session, "", revision,
478                                   kind, scratch_pool));
479 }
480
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(). */
485 static svn_error_t*
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,
490                                      svn_depth_t depth,
491                                      svn_client_ctx_t *ctx,
492                                      apr_pool_t *result_pool,
493                                      apr_pool_t *scratch_pool)
494 {
495   int rel_targets_nelts_fixed;
496   int i;
497   apr_pool_t *iterpool;
498
499   if (! (include_file_externals || include_dir_externals))
500     return SVN_NO_ERROR;
501
502   /* Easy part of applying DEPTH to externals. */
503   if (depth == svn_depth_empty)
504     {
505       /* Don't recurse. */
506       return SVN_NO_ERROR;
507     }
508
509   /* Iterate *and* grow REL_TARGETS at the same time. */
510   rel_targets_nelts_fixed = rel_targets->nelts;
511
512   iterpool = svn_pool_create(scratch_pool);
513
514   for (i = 0; i < rel_targets_nelts_fixed; i++)
515     {
516       int j;
517       const char *target;
518       apr_array_header_t *externals = NULL;
519
520       svn_pool_clear(iterpool);
521
522       target = svn_dirent_join(base_abspath,
523                                APR_ARRAY_IDX(rel_targets, i, const char *),
524                                iterpool);
525
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?
528        * ### --> future. */
529
530       SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
531                                                   target, depth,
532                                                   iterpool, iterpool));
533
534       if (externals != NULL)
535         {
536           const char *rel_target;
537
538           for (j = 0; j < externals->nelts; j++)
539             {
540               svn_wc__committable_external_info_t *xinfo =
541                          APR_ARRAY_IDX(externals, j,
542                                        svn_wc__committable_external_info_t *);
543
544               if ((xinfo->kind == svn_node_file && ! include_file_externals)
545                   || (xinfo->kind == svn_node_dir && ! include_dir_externals))
546                 continue;
547
548               rel_target = svn_dirent_skip_ancestor(base_abspath,
549                                                     xinfo->local_abspath);
550
551               SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
552
553               APR_ARRAY_PUSH(rel_targets, const char *) =
554                                          apr_pstrdup(result_pool, rel_target);
555             }
556         }
557     }
558
559   svn_pool_destroy(iterpool);
560   return SVN_NO_ERROR;
561 }
562
563 svn_error_t *
564 svn_client_commit6(const apr_array_header_t *targets,
565                    svn_depth_t depth,
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,
574                    void *commit_baton,
575                    svn_client_ctx_t *ctx,
576                    apr_pool_t *pool)
577 {
578   const svn_delta_editor_t *editor;
579   void *edit_baton;
580   struct capture_baton_t cb;
581   svn_ra_session_t *ra_session;
582   const char *log_msg;
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;
602   int i;
603
604   SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
605
606   /* Committing URLs doesn't make sense, so error if it's tried. */
607   for (i = 0; i < targets->nelts; i++)
608     {
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);
614     }
615
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));
619
620   /* No targets means nothing to commit, so just return. */
621   if (base_abspath == NULL)
622     return SVN_NO_ERROR;
623
624   SVN_ERR_ASSERT(rel_targets != NULL);
625
626   /* If we calculated only a base and no relative targets, this
627      must mean that we are being asked to commit (effectively) a
628      single path. */
629   if (rel_targets->nelts == 0)
630     APR_ARRAY_PUSH(rel_targets, const char *) = "";
631
632   if (include_file_externals || include_dir_externals)
633     {
634       if (depth != svn_depth_unknown && depth != svn_depth_infinity)
635         {
636           /* All targets after this will be handled as depth empty */
637           depth_empty_after = rel_targets->nelts;
638         }
639
640       SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
641                                                    include_file_externals,
642                                                    include_dir_externals,
643                                                    depth, ctx,
644                                                    pool, pool));
645     }
646
647   SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
648                                  rel_targets, pool, iterpool));
649
650   locks_obtained = apr_array_make(pool, lock_targets->nelts,
651                                   sizeof(const char *));
652
653   for (i = 0; i < lock_targets->nelts; i++)
654     {
655       const char *lock_root;
656       const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
657
658       svn_pool_clear(iterpool);
659
660       cmt_err = svn_error_trace(
661                     svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
662                                            FALSE, pool, iterpool));
663
664       if (cmt_err)
665         goto cleanup;
666
667       APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
668     }
669
670   /* Determine prefix to strip from the commit notify messages */
671   SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
672   notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
673                                                   base_abspath,
674                                                   pool);
675
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++)
680       {
681         const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *);
682         const char *target_abspath;
683
684         svn_pool_clear(iterpool);
685
686         target_abspath = svn_dirent_join(base_abspath, relpath, iterpool);
687
688         cmt_err = svn_error_trace(
689           check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath,
690                                         depth, iterpool));
691
692         if (cmt_err)
693           goto cleanup;
694       }
695
696   /* Crawl the working copy for commit items. */
697   {
698     struct check_url_kind_baton cukb;
699
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;
704     cukb.ctx = ctx;
705
706     cmt_err = svn_error_trace(
707                    svn_client__harvest_committables(&committables,
708                                                     &lock_tokens,
709                                                     base_abspath,
710                                                     rel_targets,
711                                                     depth_empty_after,
712                                                     depth,
713                                                     ! keep_locks,
714                                                     changelists,
715                                                     check_url_kind,
716                                                     &cukb,
717                                                     ctx,
718                                                     pool,
719                                                     iterpool));
720
721     svn_pool_clear(iterpool);
722   }
723
724   if (cmt_err)
725     goto cleanup;
726
727   if (apr_hash_count(committables->by_repository) == 0)
728     {
729       goto cleanup; /* Nothing to do */
730     }
731   else if (apr_hash_count(committables->by_repository) > 1)
732     {
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?"));
737       goto cleanup;
738     }
739
740   {
741     apr_hash_index_t *hi = apr_hash_first(iterpool,
742                                           committables->by_repository);
743
744     commit_items = svn__apr_hash_index_val(hi);
745   }
746
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. */
750   {
751     svn_boolean_t found_changed_path = FALSE;
752
753     for (i = 0; i < commit_items->nelts; ++i)
754       {
755         svn_client_commit_item3_t *item =
756           APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
757
758         if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
759           {
760             found_changed_path = TRUE;
761             break;
762           }
763       }
764
765     if (!found_changed_path)
766       goto cleanup;
767   }
768
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++)
772     {
773       svn_client_commit_item3_t *item =
774         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
775
776       svn_pool_clear(iterpool);
777
778       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
779         {
780           /* ### item->moved_from_abspath contains the move origin */
781           const char *moved_from_abspath;
782           const char *delete_op_root_abspath;
783
784           cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
785                                       &moved_from_abspath,
786                                       &delete_op_root_abspath,
787                                       ctx->wc_ctx, item->path,
788                                       iterpool, iterpool));
789           if (cmt_err)
790             goto cleanup;
791
792           if (moved_from_abspath && delete_op_root_abspath &&
793               strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
794
795             {
796               svn_boolean_t found_delete_half =
797                 (svn_hash_gets(committables->by_path, delete_op_root_abspath)
798                  != NULL);
799
800               if (!found_delete_half)
801                 {
802                   const char *delete_half_parent_abspath;
803
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.
807                    *
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
811                    * A/B does appear.
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'.)
815                    *
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.
820                    */
821
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)
828                     {
829                       const char *parent_delete_op_root_abspath;
830
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));
836                       if (cmt_err)
837                         goto cleanup;
838
839                       if (parent_delete_op_root_abspath)
840                         found_delete_half =
841                           (svn_hash_gets(committables->by_path,
842                                          parent_delete_op_root_abspath)
843                            != NULL);
844                     }
845                 }
846
847               if (!found_delete_half)
848                 {
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,
856                                                      iterpool));
857                   goto cleanup;
858                 }
859             }
860         }
861
862       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
863         {
864           const char *moved_to_abspath;
865           const char *copy_op_root_abspath;
866
867           cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
868                                       &moved_to_abspath,
869                                       &copy_op_root_abspath,
870                                       ctx->wc_ctx, item->path,
871                                       iterpool, iterpool));
872           if (cmt_err)
873             goto cleanup;
874
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)
878               == NULL)
879             {
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,
887                                                 iterpool));
888               goto cleanup;
889             }
890         }
891     }
892
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))
896     {
897       const char *tmp_file;
898       cmt_err = svn_error_trace(
899                      svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
900                                              ctx, pool));
901
902       if (cmt_err || (! log_msg))
903         goto cleanup;
904     }
905   else
906     log_msg = "";
907
908   /* Sort and condense our COMMIT_ITEMS. */
909   cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
910                                                               commit_items,
911                                                               pool));
912
913   if (cmt_err)
914     goto cleanup;
915
916   /* Collect our lock tokens with paths relative to base_url. */
917   cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
918                                                 base_url, pool));
919
920   if (cmt_err)
921     goto cleanup;
922
923   cb.original_callback = commit_callback;
924   cb.original_baton = commit_baton;
925   cb.info = &commit_info;
926   cb.pool = pool;
927
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,
935                                                                  0,
936                                                                  const char *),
937                                                    commit_items,
938                                                    TRUE, TRUE, ctx,
939                                                    pool, pool));
940
941   if (cmt_err)
942     goto cleanup;
943
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,
948                             &cb, pool));
949
950   if (cmt_err)
951     goto cleanup;
952
953   /* Make a note that we have a commit-in-progress. */
954   commit_in_progress = TRUE;
955
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;
960
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,
965                                     iterpool));
966
967   /* Handle a successful commit. */
968   if ((! cmt_err)
969       || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
970     {
971       svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
972
973       /* Make a note that our commit is finished. */
974       commit_in_progress = FALSE;
975
976       for (i = 0; i < commit_items->nelts; i++)
977         {
978           svn_client_commit_item3_t *item
979             = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
980
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),
986                        iterpool);
987           if (bump_err)
988             goto cleanup;
989         }
990
991       SVN_ERR_ASSERT(commit_info);
992       bump_err = svn_wc_process_committed_queue2(
993                    queue, ctx->wc_ctx,
994                    commit_info->revision,
995                    commit_info->date,
996                    commit_info->author,
997                    ctx->cancel_func, ctx->cancel_baton,
998                    iterpool);
999     }
1000
1001  cleanup:
1002   /* Sleep to ensure timestamp integrity. */
1003   if (timestamp_sleep)
1004     svn_io_sleep_for_timestamps(base_abspath, pool);
1005
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));
1011
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
1016      clean-up. */
1017   if (! bump_err)
1018     {
1019       /* Release all locks we obtained */
1020       for (i = 0; i < locks_obtained->nelts; i++)
1021         {
1022           const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1023                                                 const char *);
1024
1025           svn_pool_clear(iterpool);
1026
1027           unlock_err = svn_error_compose_create(
1028                            svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1029                                                       iterpool),
1030                            unlock_err);
1031         }
1032     }
1033
1034   svn_pool_destroy(iterpool);
1035
1036   return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
1037                                           pool));
1038 }