]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/commit.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 #include "private/svn_sorts_private.h"
49
50 #include "svn_private_config.h"
51
52 struct capture_baton_t {
53   svn_commit_callback2_t original_callback;
54   void *original_baton;
55
56   svn_commit_info_t **info;
57   apr_pool_t *pool;
58 };
59
60
61 static svn_error_t *
62 capture_commit_info(const svn_commit_info_t *commit_info,
63                     void *baton,
64                     apr_pool_t *pool)
65 {
66   struct capture_baton_t *cb = baton;
67
68   *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
69
70   if (cb->original_callback)
71     SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
72
73   return SVN_NO_ERROR;
74 }
75
76
77 static svn_error_t *
78 get_ra_editor(const svn_delta_editor_t **editor,
79               void **edit_baton,
80               svn_ra_session_t *ra_session,
81               svn_client_ctx_t *ctx,
82               const char *log_msg,
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,
88               void *commit_baton,
89               apr_pool_t *pool)
90 {
91   apr_hash_t *commit_revprops;
92   apr_hash_t *relpath_map = NULL;
93
94   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
95                                            log_msg, ctx, pool));
96
97 #ifdef ENABLE_EV2_SHIMS
98   if (commit_items)
99     {
100       int i;
101       apr_pool_t *iterpool = svn_pool_create(pool);
102
103       relpath_map = apr_hash_make(pool);
104       for (i = 0; i < commit_items->nelts; i++)
105         {
106           svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
107                                                   svn_client_commit_item3_t *);
108           const char *relpath;
109
110           if (!item->path)
111             continue;
112
113           svn_pool_clear(iterpool);
114           SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
115                                           NULL, NULL,
116                                           ctx->wc_ctx, item->path, FALSE, pool,
117                                           iterpool));
118           if (relpath)
119             svn_hash_sets(relpath_map, relpath, item->path);
120         }
121       svn_pool_destroy(iterpool);
122     }
123 #endif
124
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,
132                                     pool));
133
134   return SVN_NO_ERROR;
135 }
136
137 \f
138 /*** Public Interfaces. ***/
139
140 static svn_error_t *
141 reconcile_errors(svn_error_t *commit_err,
142                  svn_error_t *unlock_err,
143                  svn_error_t *bump_err,
144                  apr_pool_t *pool)
145 {
146   svn_error_t *err;
147
148   /* Early release (for good behavior). */
149   if (! (commit_err || unlock_err || bump_err))
150     return SVN_NO_ERROR;
151
152   /* If there was a commit error, start off our error chain with
153      that. */
154   if (commit_err)
155     {
156       commit_err = svn_error_quick_wrap
157         (commit_err, _("Commit failed (details follow):"));
158       err = commit_err;
159     }
160
161   /* Else, create a new "general" error that will lead off the errors
162      that follow. */
163   else
164     err = svn_error_create(SVN_ERR_BASE, NULL,
165                            _("Commit succeeded, but other errors follow:"));
166
167   /* If there was an unlock error... */
168   if (unlock_err)
169     {
170       /* Wrap the error with some headers. */
171       unlock_err = svn_error_quick_wrap
172         (unlock_err, _("Error unlocking locked dirs (details follow):"));
173
174       /* Append this error to the chain. */
175       svn_error_compose(err, unlock_err);
176     }
177
178   /* If there was a bumping error... */
179   if (bump_err)
180     {
181       /* Wrap the error with some headers. */
182       bump_err = svn_error_quick_wrap
183         (bump_err, _("Error bumping revisions post-commit (details follow):"));
184
185       /* Append this error to the chain. */
186       svn_error_compose(err, bump_err);
187     }
188
189   return err;
190 }
191
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. */
196 static svn_error_t *
197 collect_lock_tokens(apr_hash_t **result,
198                     apr_hash_t *all_tokens,
199                     const char *base_url,
200                     apr_pool_t *pool)
201 {
202   apr_hash_index_t *hi;
203
204   *result = apr_hash_make(pool);
205
206   for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
207     {
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);
211
212       if (relpath)
213         {
214           svn_hash_sets(*result, relpath, token);
215         }
216     }
217
218   return SVN_NO_ERROR;
219 }
220
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. */
223 static svn_error_t *
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)
232 {
233   svn_boolean_t loop_recurse = FALSE;
234   svn_boolean_t remove_lock;
235
236   if (! commit_as_operations
237       && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
238       && (item->kind == svn_node_dir)
239       && (item->copyfrom_url))
240     loop_recurse = TRUE;
241
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)));
246
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)
252     remove_lock = TRUE;
253
254   return svn_error_trace(
255          svn_wc_queue_committed4(queue, wc_ctx, item->path,
256                                  loop_recurse,
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));
265 }
266
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
271
272    Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
273    storage */
274 static svn_error_t *
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)
281 {
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;
285   int i;
286
287   wc_items = apr_hash_make(scratch_pool);
288
289   /* Create an array of targets for each working copy used */
290   for (i = 0; i < target_relpaths->nelts; i++)
291     {
292       const char *target_abspath;
293       const char *wcroot_abspath;
294       apr_array_header_t *wc_targets;
295       svn_error_t *err;
296       const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
297                                                  const char *);
298
299       svn_pool_clear(iterpool);
300       target_abspath = svn_dirent_join(base_abspath, target_relpath,
301                                        scratch_pool);
302
303       err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
304                                iterpool, iterpool);
305
306       if (err)
307         {
308           if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
309             {
310               svn_error_clear(err);
311               continue;
312             }
313           return svn_error_trace(err);
314         }
315
316       wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
317
318       if (! wc_targets)
319         {
320           wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
321           svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
322                         wc_targets);
323         }
324
325       APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
326     }
327
328   *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
329                                  sizeof(const char *));
330
331   /* For each working copy determine where to lock */
332   for (hi = apr_hash_first(scratch_pool, wc_items);
333        hi;
334        hi = apr_hash_next(hi))
335     {
336       const char *common;
337       const char *wcroot_abspath = apr_hash_this_key(hi);
338       apr_array_header_t *wc_targets = apr_hash_this_val(hi);
339
340       svn_pool_clear(iterpool);
341
342       if (wc_targets->nelts == 1)
343         {
344           const char *target_abspath;
345           target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
346
347           if (! strcmp(wcroot_abspath, target_abspath))
348             {
349               APR_ARRAY_PUSH(*lock_targets, const char *)
350                       = apr_pstrdup(result_pool, target_abspath);
351             }
352           else
353             {
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);
357             }
358         }
359       else if (wc_targets->nelts > 1)
360         {
361           SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
362                                               FALSE, iterpool, iterpool));
363
364           svn_sort__array(wc_targets, svn_sort_compare_paths);
365
366           if (wc_targets->nelts == 0
367               || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
368               || !strcmp(common, wcroot_abspath))
369             {
370               APR_ARRAY_PUSH(*lock_targets, const char *)
371                     = apr_pstrdup(result_pool, common);
372             }
373           else
374             {
375               /* Lock the parent to allow deleting the target */
376               APR_ARRAY_PUSH(*lock_targets, const char *)
377                        = svn_dirent_dirname(common, result_pool);
378             }
379         }
380     }
381
382   svn_pool_destroy(iterpool);
383   return SVN_NO_ERROR;
384 }
385
386 /* Baton for check_url_kind */
387 struct check_url_kind_baton
388 {
389   apr_pool_t *pool;
390   svn_ra_session_t *session;
391   const char *repos_root_url;
392   svn_client_ctx_t *ctx;
393 };
394
395 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
396 static svn_error_t *
397 check_url_kind(void *baton,
398                svn_node_kind_t *kind,
399                const char *url,
400                svn_revnum_t revision,
401                apr_pool_t *scratch_pool)
402 {
403   struct check_url_kind_baton *cukb = baton;
404
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))
407     {
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,
411                                      cukb->pool));
412     }
413   else
414     SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
415
416   return svn_error_trace(
417                 svn_ra_check_path(cukb->session, "", revision,
418                                   kind, scratch_pool));
419 }
420
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(). */
425 static svn_error_t*
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,
430                                      svn_depth_t depth,
431                                      svn_client_ctx_t *ctx,
432                                      apr_pool_t *result_pool,
433                                      apr_pool_t *scratch_pool)
434 {
435   int rel_targets_nelts_fixed;
436   int i;
437   apr_pool_t *iterpool;
438
439   if (! (include_file_externals || include_dir_externals))
440     return SVN_NO_ERROR;
441
442   /* Easy part of applying DEPTH to externals. */
443   if (depth == svn_depth_empty)
444     {
445       /* Don't recurse. */
446       return SVN_NO_ERROR;
447     }
448
449   /* Iterate *and* grow REL_TARGETS at the same time. */
450   rel_targets_nelts_fixed = rel_targets->nelts;
451
452   iterpool = svn_pool_create(scratch_pool);
453
454   for (i = 0; i < rel_targets_nelts_fixed; i++)
455     {
456       int j;
457       const char *target;
458       apr_array_header_t *externals = NULL;
459
460       svn_pool_clear(iterpool);
461
462       target = svn_dirent_join(base_abspath,
463                                APR_ARRAY_IDX(rel_targets, i, const char *),
464                                iterpool);
465
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?
468        * ### --> future. */
469
470       SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
471                                                   target, depth,
472                                                   iterpool, iterpool));
473
474       if (externals != NULL)
475         {
476           const char *rel_target;
477
478           for (j = 0; j < externals->nelts; j++)
479             {
480               svn_wc__committable_external_info_t *xinfo =
481                          APR_ARRAY_IDX(externals, j,
482                                        svn_wc__committable_external_info_t *);
483
484               if ((xinfo->kind == svn_node_file && ! include_file_externals)
485                   || (xinfo->kind == svn_node_dir && ! include_dir_externals))
486                 continue;
487
488               rel_target = svn_dirent_skip_ancestor(base_abspath,
489                                                     xinfo->local_abspath);
490
491               SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
492
493               APR_ARRAY_PUSH(rel_targets, const char *) =
494                                          apr_pstrdup(result_pool, rel_target);
495             }
496         }
497     }
498
499   svn_pool_destroy(iterpool);
500   return SVN_NO_ERROR;
501 }
502
503 svn_error_t *
504 svn_client_commit6(const apr_array_header_t *targets,
505                    svn_depth_t depth,
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,
514                    void *commit_baton,
515                    svn_client_ctx_t *ctx,
516                    apr_pool_t *pool)
517 {
518   const svn_delta_editor_t *editor;
519   void *edit_baton;
520   struct capture_baton_t cb;
521   svn_ra_session_t *ra_session;
522   const char *log_msg;
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;
543   int i;
544
545   SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
546
547   /* Committing URLs doesn't make sense, so error if it's tried. */
548   for (i = 0; i < targets->nelts; i++)
549     {
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);
555     }
556
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));
560
561   /* No targets means nothing to commit, so just return. */
562   if (base_abspath == NULL)
563     return SVN_NO_ERROR;
564
565   SVN_ERR_ASSERT(rel_targets != NULL);
566
567   /* If we calculated only a base and no relative targets, this
568      must mean that we are being asked to commit (effectively) a
569      single path. */
570   if (rel_targets->nelts == 0)
571     APR_ARRAY_PUSH(rel_targets, const char *) = "";
572
573   if (include_file_externals || include_dir_externals)
574     {
575       if (depth != svn_depth_unknown && depth != svn_depth_infinity)
576         {
577           /* All targets after this will be handled as depth empty */
578           depth_empty_after = rel_targets->nelts;
579         }
580
581       SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
582                                                    include_file_externals,
583                                                    include_dir_externals,
584                                                    depth, ctx,
585                                                    pool, pool));
586     }
587
588   SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
589                                  rel_targets, pool, iterpool));
590
591   locks_obtained = apr_array_make(pool, lock_targets->nelts,
592                                   sizeof(const char *));
593
594   for (i = 0; i < lock_targets->nelts; i++)
595     {
596       const char *lock_root;
597       const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
598
599       svn_pool_clear(iterpool);
600
601       cmt_err = svn_error_trace(
602                     svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
603                                            FALSE, pool, iterpool));
604
605       if (cmt_err)
606         goto cleanup;
607
608       APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
609     }
610
611   /* Determine prefix to strip from the commit notify messages */
612   SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
613   notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
614                                                   base_abspath,
615                                                   pool);
616
617   /* Crawl the working copy for commit items. */
618   {
619     struct check_url_kind_baton cukb;
620
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;
625     cukb.ctx = ctx;
626
627     cmt_err = svn_error_trace(
628                    svn_client__harvest_committables(&committables,
629                                                     &lock_tokens,
630                                                     base_abspath,
631                                                     rel_targets,
632                                                     depth_empty_after,
633                                                     depth,
634                                                     ! keep_locks,
635                                                     changelists,
636                                                     check_url_kind,
637                                                     &cukb,
638                                                     ctx,
639                                                     pool,
640                                                     iterpool));
641
642     svn_pool_clear(iterpool);
643   }
644
645   if (cmt_err)
646     goto cleanup;
647
648   if (apr_hash_count(committables->by_repository) == 0)
649     {
650       goto cleanup; /* Nothing to do */
651     }
652   else if (apr_hash_count(committables->by_repository) > 1)
653     {
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?"));
658       goto cleanup;
659     }
660
661   {
662     apr_hash_index_t *hi = apr_hash_first(iterpool,
663                                           committables->by_repository);
664
665     commit_items = apr_hash_this_val(hi);
666   }
667
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. */
671   {
672     svn_boolean_t found_changed_path = FALSE;
673
674     for (i = 0; i < commit_items->nelts; ++i)
675       {
676         svn_client_commit_item3_t *item =
677           APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
678
679         if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
680           {
681             found_changed_path = TRUE;
682             break;
683           }
684       }
685
686     if (!found_changed_path)
687       goto cleanup;
688   }
689
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++)
693     {
694       svn_client_commit_item3_t *item =
695         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
696
697       svn_pool_clear(iterpool);
698
699       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
700         {
701           /* ### item->moved_from_abspath contains the move origin */
702           const char *moved_from_abspath;
703           const char *delete_op_root_abspath;
704
705           cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
706                                       &moved_from_abspath,
707                                       &delete_op_root_abspath,
708                                       ctx->wc_ctx, item->path,
709                                       iterpool, iterpool));
710           if (cmt_err)
711             goto cleanup;
712
713           if (moved_from_abspath && delete_op_root_abspath)
714             {
715               svn_client_commit_item3_t *delete_half =
716                 svn_hash_gets(committables->by_path, delete_op_root_abspath);
717
718               if (!delete_half)
719                 {
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,
727                                                      iterpool));
728
729                   if (ctx->notify_func2)
730                     {
731                       svn_wc_notify_t *notify;
732                       notify = svn_wc_create_notify(
733                                     delete_op_root_abspath,
734                                     svn_wc_notify_failed_requires_target,
735                                     iterpool);
736                       notify->err = cmt_err;
737
738                       ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
739                     }
740
741                   goto cleanup;
742                 }
743               else if (delete_half->revision == item->copyfrom_rev)
744                 {
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. */
748
749                   if (!move_youngest)
750                     move_youngest = apr_hash_make(pool);
751
752                   svn_hash_sets(move_youngest, item->path, item);
753                 }
754             }
755         }
756
757       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
758         {
759           const char *moved_to_abspath;
760           const char *copy_op_root_abspath;
761
762           cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
763                                       &moved_to_abspath,
764                                       &copy_op_root_abspath,
765                                       ctx->wc_ctx, item->path,
766                                       iterpool, iterpool));
767           if (cmt_err)
768             goto cleanup;
769
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)
773               == NULL)
774             {
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,
782                                                 iterpool));
783
784               if (ctx->notify_func2)
785                 {
786                     svn_wc_notify_t *notify;
787                     notify = svn_wc_create_notify(
788                                 copy_op_root_abspath,
789                                 svn_wc_notify_failed_requires_target,
790                                 iterpool);
791                     notify->err = cmt_err;
792
793                     ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
794                 }
795
796               goto cleanup;
797             }
798         }
799     }
800
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))
804     {
805       const char *tmp_file;
806       cmt_err = svn_error_trace(
807                      svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
808                                              ctx, pool));
809
810       if (cmt_err || (! log_msg))
811         goto cleanup;
812     }
813   else
814     log_msg = "";
815
816   /* Sort and condense our COMMIT_ITEMS. */
817   cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
818                                                               commit_items,
819                                                               pool));
820
821   if (cmt_err)
822     goto cleanup;
823
824   /* Collect our lock tokens with paths relative to base_url. */
825   cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
826                                                 base_url, pool));
827
828   if (cmt_err)
829     goto cleanup;
830
831   cb.original_callback = commit_callback;
832   cb.original_baton = commit_baton;
833   cb.info = &commit_info;
834   cb.pool = pool;
835
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,
843                                                                  0,
844                                                                  const char *),
845                                                    commit_items,
846                                                    TRUE, TRUE, ctx,
847                                                    pool, pool));
848
849   if (cmt_err)
850     goto cleanup;
851
852   if (move_youngest != NULL)
853     {
854       apr_hash_index_t *hi;
855       svn_revnum_t youngest;
856
857       SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
858
859       for (hi = apr_hash_first(iterpool, move_youngest);
860            hi;
861            hi = apr_hash_next(hi))
862         {
863           svn_client_commit_item3_t *item = apr_hash_this_val(hi);
864
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
867              revision.
868
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.
872
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. */
877
878           if (item->copyfrom_rev < youngest)
879             item->copyfrom_rev = youngest;
880         }
881     }
882
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,
887                             &cb, pool));
888
889   if (cmt_err)
890     goto cleanup;
891
892   /* Make a note that we have a commit-in-progress. */
893   commit_in_progress = TRUE;
894
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;
899
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,
904                                     iterpool));
905
906   /* Handle a successful commit. */
907   if ((! cmt_err)
908       || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
909     {
910       svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
911
912       /* Make a note that our commit is finished. */
913       commit_in_progress = FALSE;
914
915       for (i = 0; i < commit_items->nelts; i++)
916         {
917           svn_client_commit_item3_t *item
918             = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
919
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),
925                        iterpool);
926           if (bump_err)
927             goto cleanup;
928         }
929
930       SVN_ERR_ASSERT(commit_info);
931       bump_err = svn_wc_process_committed_queue2(
932                    queue, ctx->wc_ctx,
933                    commit_info->revision,
934                    commit_info->date,
935                    commit_info->author,
936                    ctx->cancel_func, ctx->cancel_baton,
937                    iterpool);
938
939       if (bump_err)
940         goto cleanup;
941     }
942
943  cleanup:
944   /* Sleep to ensure timestamp integrity.  BASE_ABSPATH may have been
945      removed by the commit or it may the common ancestor of multiple
946      working copies. */
947   if (timestamp_sleep)
948     {
949       const char *sleep_abspath;
950       svn_error_t *err = svn_wc__get_wcroot(&sleep_abspath, ctx->wc_ctx,
951                                             base_abspath, pool, pool);
952       if (err)
953         {
954           svn_error_clear(err);
955           sleep_abspath = base_abspath;
956         }
957
958       svn_io_sleep_for_timestamps(sleep_abspath, pool);
959     }
960
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));
966
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
971      clean-up. */
972   if (! bump_err)
973     {
974       /* Release all locks we obtained */
975       for (i = 0; i < locks_obtained->nelts; i++)
976         {
977           const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
978                                                 const char *);
979
980           svn_pool_clear(iterpool);
981
982           unlock_err = svn_error_compose_create(
983                            svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
984                                                       iterpool),
985                            unlock_err);
986         }
987     }
988
989   svn_pool_destroy(iterpool);
990
991   return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
992                                           pool));
993 }