commit.c revision 299742
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
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
52struct 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
61static svn_error_t *
62capture_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
77static svn_error_t *
78get_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
138/*** Public Interfaces. ***/
139
140static svn_error_t *
141reconcile_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. */
196static svn_error_t *
197collect_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. */
223static svn_error_t *
224post_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 */
274static svn_error_t *
275determine_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 */
387struct 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 */
396static svn_error_t *
397check_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(). */
425static svn_error_t*
426append_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
503svn_error_t *
504svn_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}
994