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
503/* Crawl the working copy for commit items.
504 */
505static svn_error_t *
506harvest_committables(apr_array_header_t **commit_items_p,
507                     apr_hash_t **committables_by_path_p,
508                     apr_hash_t **lock_tokens,
509                     const char *base_dir_abspath,
510                     const apr_array_header_t *targets,
511                     int depth_empty_start,
512                     svn_depth_t depth,
513                     svn_boolean_t just_locked,
514                     const apr_array_header_t *changelists,
515                     svn_client_ctx_t *ctx,
516                     apr_pool_t *result_pool,
517                     apr_pool_t *scratch_pool)
518{
519  struct check_url_kind_baton cukb;
520  svn_client__committables_t *committables;
521  apr_hash_index_t *hi;
522
523  /* Prepare for when we have a copy containing not-present nodes. */
524  cukb.pool = scratch_pool;
525  cukb.session = NULL; /* ### Can we somehow reuse session? */
526  cukb.repos_root_url = NULL;
527  cukb.ctx = ctx;
528
529  SVN_ERR(svn_client__harvest_committables(&committables, lock_tokens,
530                                           base_dir_abspath, targets,
531                                           depth_empty_start, depth,
532                                           just_locked,
533                                           changelists,
534                                           check_url_kind, &cukb,
535                                           ctx, result_pool, scratch_pool));
536  if (apr_hash_count(committables->by_repository) == 0)
537    {
538      *commit_items_p = NULL;
539      return SVN_NO_ERROR;  /* Nothing to do */
540    }
541  else if (apr_hash_count(committables->by_repository) > 1)
542    {
543      return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
544          _("Commit can only commit to a single repository at a time.\n"
545            "Are all targets part of the same working copy?"));
546    }
547
548  hi = apr_hash_first(scratch_pool, committables->by_repository);
549  *commit_items_p = apr_hash_this_val(hi);
550  if (committables_by_path_p)
551    *committables_by_path_p = committables->by_path;
552  return SVN_NO_ERROR;
553}
554
555svn_error_t *
556svn_client__wc_replay(const char *src_wc_abspath,
557                      const apr_array_header_t *targets,
558                      svn_depth_t depth,
559                      const apr_array_header_t *changelists,
560                      const svn_delta_editor_t *editor,
561                      void *edit_baton,
562                      svn_wc_notify_func2_t notify_func,
563                      void *notify_baton,
564                      svn_client_ctx_t *ctx,
565                      apr_pool_t *pool)
566{
567  const char *base_abspath;
568  apr_array_header_t *rel_targets;
569  apr_hash_t *lock_tokens;
570  apr_array_header_t *commit_items;
571  svn_client__pathrev_t *base;
572  const char *base_url;
573  svn_wc_notify_func2_t saved_notify_func;
574  void *saved_notify_baton;
575
576  /* Condense the target list. This makes all targets absolute. */
577  SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
578                                      FALSE, pool, pool));
579
580  /* No targets means nothing to commit, so just return. */
581  if (base_abspath == NULL)
582    return SVN_NO_ERROR;
583
584  SVN_ERR_ASSERT(rel_targets != NULL);
585
586  /* If we calculated only a base and no relative targets, this
587     must mean that we are being asked to commit (effectively) a
588     single path. */
589  if (rel_targets->nelts == 0)
590    APR_ARRAY_PUSH(rel_targets, const char *) = "";
591
592  /* Crawl the working copy for commit items. */
593  SVN_ERR(harvest_committables(&commit_items, NULL /*committables_by_path_p*/,
594                               &lock_tokens,
595                               base_abspath, rel_targets,
596                               -1 /*depth_empty_start*/,
597                               depth,
598                               FALSE /*just_locked*/,
599                               changelists,
600                               ctx, pool, pool));
601  if (!commit_items)
602    {
603      return SVN_NO_ERROR;
604    }
605
606  SVN_ERR(svn_client__wc_node_get_base(&base,
607                                       src_wc_abspath, ctx->wc_ctx, pool, pool));
608  base_url = base->url;
609  /* Sort our COMMIT_ITEMS by URL and find their relative URL-paths. */
610  SVN_ERR(svn_client__condense_commit_items2(base_url, commit_items, pool));
611
612  saved_notify_func = ctx->notify_func2;
613  saved_notify_baton = ctx->notify_baton2;
614  ctx->notify_func2 = notify_func;
615  ctx->notify_baton2 = notify_baton;
616  /* BASE_URL is only used here in notifications & errors */
617  SVN_ERR(svn_client__do_commit(base_url, commit_items,
618                                editor, edit_baton,
619                                NULL /*notify_prefix*/, NULL /*sha1_checksums*/,
620                                ctx, pool, pool));
621  ctx->notify_func2 = saved_notify_func;
622  ctx->notify_baton2 = saved_notify_baton;
623  return SVN_NO_ERROR;
624}
625
626svn_error_t *
627svn_client_commit6(const apr_array_header_t *targets,
628                   svn_depth_t depth,
629                   svn_boolean_t keep_locks,
630                   svn_boolean_t keep_changelists,
631                   svn_boolean_t commit_as_operations,
632                   svn_boolean_t include_file_externals,
633                   svn_boolean_t include_dir_externals,
634                   const apr_array_header_t *changelists,
635                   const apr_hash_t *revprop_table,
636                   svn_commit_callback2_t commit_callback,
637                   void *commit_baton,
638                   svn_client_ctx_t *ctx,
639                   apr_pool_t *pool)
640{
641  const svn_delta_editor_t *editor;
642  void *edit_baton;
643  struct capture_baton_t cb;
644  svn_ra_session_t *ra_session;
645  const char *log_msg;
646  const char *base_abspath;
647  const char *base_url;
648  apr_array_header_t *rel_targets;
649  apr_array_header_t *lock_targets;
650  apr_array_header_t *locks_obtained;
651  apr_hash_t *committables_by_path;
652  apr_hash_t *lock_tokens;
653  apr_hash_t *sha1_checksums;
654  apr_array_header_t *commit_items;
655  svn_error_t *cmt_err = SVN_NO_ERROR;
656  svn_error_t *bump_err = SVN_NO_ERROR;
657  svn_error_t *unlock_err = SVN_NO_ERROR;
658  svn_boolean_t commit_in_progress = FALSE;
659  svn_boolean_t timestamp_sleep = FALSE;
660  svn_commit_info_t *commit_info = NULL;
661  apr_pool_t *iterpool = svn_pool_create(pool);
662  const char *current_abspath;
663  const char *notify_prefix;
664  int depth_empty_after = -1;
665  apr_hash_t *move_youngest = NULL;
666  int i;
667
668  SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
669
670  /* Committing URLs doesn't make sense, so error if it's tried. */
671  for (i = 0; i < targets->nelts; i++)
672    {
673      const char *target = APR_ARRAY_IDX(targets, i, const char *);
674      if (svn_path_is_url(target))
675        return svn_error_createf
676          (SVN_ERR_ILLEGAL_TARGET, NULL,
677           _("'%s' is a URL, but URLs cannot be commit targets"), target);
678    }
679
680  /* Condense the target list. This makes all targets absolute. */
681  SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
682                                      FALSE, pool, iterpool));
683
684  /* No targets means nothing to commit, so just return. */
685  if (base_abspath == NULL)
686    return SVN_NO_ERROR;
687
688  SVN_ERR_ASSERT(rel_targets != NULL);
689
690  /* If we calculated only a base and no relative targets, this
691     must mean that we are being asked to commit (effectively) a
692     single path. */
693  if (rel_targets->nelts == 0)
694    APR_ARRAY_PUSH(rel_targets, const char *) = "";
695
696  if (include_file_externals || include_dir_externals)
697    {
698      if (depth != svn_depth_unknown && depth != svn_depth_infinity)
699        {
700          /* All targets after this will be handled as depth empty */
701          depth_empty_after = rel_targets->nelts;
702        }
703
704      SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
705                                                   include_file_externals,
706                                                   include_dir_externals,
707                                                   depth, ctx,
708                                                   pool, pool));
709    }
710
711  SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
712                                 rel_targets, pool, iterpool));
713
714  locks_obtained = apr_array_make(pool, lock_targets->nelts,
715                                  sizeof(const char *));
716
717  for (i = 0; i < lock_targets->nelts; i++)
718    {
719      const char *lock_root;
720      const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
721
722      svn_pool_clear(iterpool);
723
724      cmt_err = svn_error_trace(
725                    svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
726                                           FALSE, pool, iterpool));
727
728      if (cmt_err)
729        goto cleanup;
730
731      APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
732    }
733
734  /* Determine prefix to strip from the commit notify messages */
735  SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
736  notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
737                                                  base_abspath,
738                                                  pool);
739
740  /* Crawl the working copy for commit items. */
741  cmt_err = svn_error_trace(
742              harvest_committables(&commit_items, &committables_by_path,
743                                   &lock_tokens,
744                                   base_abspath,
745                                   rel_targets,
746                                   depth_empty_after,
747                                   depth,
748                                   ! keep_locks,
749                                   changelists,
750                                   ctx,
751                                   pool,
752                                   iterpool));
753  svn_pool_clear(iterpool);
754
755  if (cmt_err)
756    goto cleanup;
757
758  if (!commit_items)
759    {
760      goto cleanup; /* Nothing to do */
761    }
762
763  /* If our array of targets contains only locks (and no actual file
764     or prop modifications), then we return here to avoid committing a
765     revision with no changes. */
766  {
767    svn_boolean_t found_changed_path = FALSE;
768
769    for (i = 0; i < commit_items->nelts; ++i)
770      {
771        svn_client_commit_item3_t *item =
772          APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
773
774        if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
775          {
776            found_changed_path = TRUE;
777            break;
778          }
779      }
780
781    if (!found_changed_path)
782      goto cleanup;
783  }
784
785  /* For every target that was moved verify that both halves of the
786   * move are part of the commit. */
787  for (i = 0; i < commit_items->nelts; i++)
788    {
789      svn_client_commit_item3_t *item =
790        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
791
792      svn_pool_clear(iterpool);
793
794      if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
795        {
796          /* ### item->moved_from_abspath contains the move origin */
797          const char *moved_from_abspath;
798          const char *delete_op_root_abspath;
799
800          cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
801                                      &moved_from_abspath,
802                                      &delete_op_root_abspath,
803                                      ctx->wc_ctx, item->path,
804                                      iterpool, iterpool));
805          if (cmt_err)
806            goto cleanup;
807
808          if (moved_from_abspath && delete_op_root_abspath)
809            {
810              svn_client_commit_item3_t *delete_half =
811                svn_hash_gets(committables_by_path, delete_op_root_abspath);
812
813              if (!delete_half)
814                {
815                  cmt_err = svn_error_createf(
816                              SVN_ERR_ILLEGAL_TARGET, NULL,
817                              _("Cannot commit '%s' because it was moved from "
818                                "'%s' which is not part of the commit; both "
819                                "sides of the move must be committed together"),
820                              svn_dirent_local_style(item->path, iterpool),
821                              svn_dirent_local_style(delete_op_root_abspath,
822                                                     iterpool));
823
824                  if (ctx->notify_func2)
825                    {
826                      svn_wc_notify_t *notify;
827                      notify = svn_wc_create_notify(
828                                    delete_op_root_abspath,
829                                    svn_wc_notify_failed_requires_target,
830                                    iterpool);
831                      notify->err = cmt_err;
832
833                      ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
834                    }
835
836                  goto cleanup;
837                }
838              else if (delete_half->revision == item->copyfrom_rev)
839                {
840                  /* Ok, now we know that we perform an out-of-date check
841                     on the copyfrom location. Remember this for a fixup
842                     round right before committing. */
843
844                  if (!move_youngest)
845                    move_youngest = apr_hash_make(pool);
846
847                  svn_hash_sets(move_youngest, item->path, item);
848                }
849            }
850        }
851
852      if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
853        {
854          const char *moved_to_abspath;
855          const char *copy_op_root_abspath;
856
857          cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
858                                      &moved_to_abspath,
859                                      &copy_op_root_abspath,
860                                      ctx->wc_ctx, item->path,
861                                      iterpool, iterpool));
862          if (cmt_err)
863            goto cleanup;
864
865          if (moved_to_abspath && copy_op_root_abspath &&
866              strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
867              svn_hash_gets(committables_by_path, copy_op_root_abspath)
868              == NULL)
869            {
870              cmt_err = svn_error_createf(
871                          SVN_ERR_ILLEGAL_TARGET, NULL,
872                         _("Cannot commit '%s' because it was moved to '%s' "
873                           "which is not part of the commit; both sides of "
874                           "the move must be committed together"),
875                         svn_dirent_local_style(item->path, iterpool),
876                         svn_dirent_local_style(copy_op_root_abspath,
877                                                iterpool));
878
879              if (ctx->notify_func2)
880                {
881                    svn_wc_notify_t *notify;
882                    notify = svn_wc_create_notify(
883                                copy_op_root_abspath,
884                                svn_wc_notify_failed_requires_target,
885                                iterpool);
886                    notify->err = cmt_err;
887
888                    ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
889                }
890
891              goto cleanup;
892            }
893        }
894    }
895
896  /* Go get a log message.  If an error occurs, or no log message is
897     specified, abort the operation. */
898  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
899    {
900      const char *tmp_file;
901      cmt_err = svn_error_trace(
902                     svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
903                                             ctx, pool));
904
905      if (cmt_err || (! log_msg))
906        goto cleanup;
907    }
908  else
909    log_msg = "";
910
911  /* Sort and condense our COMMIT_ITEMS. */
912  cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
913                                                              commit_items,
914                                                              pool));
915
916  if (cmt_err)
917    goto cleanup;
918
919  /* Collect our lock tokens with paths relative to base_url. */
920  cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
921                                                base_url, pool));
922
923  if (cmt_err)
924    goto cleanup;
925
926  cb.original_callback = commit_callback;
927  cb.original_baton = commit_baton;
928  cb.info = &commit_info;
929  cb.pool = pool;
930
931  /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
932   * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
933   * parent of nested working copies. We don't support commits to multiple
934   * repositories so using the first WC to get the RA session is safe. */
935  cmt_err = svn_error_trace(
936              svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
937                                                   APR_ARRAY_IDX(lock_targets,
938                                                                 0,
939                                                                 const char *),
940                                                   commit_items,
941                                                   TRUE, TRUE, ctx,
942                                                   pool, pool));
943
944  if (cmt_err)
945    goto cleanup;
946
947  if (move_youngest != NULL)
948    {
949      apr_hash_index_t *hi;
950      svn_revnum_t youngest;
951
952      SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
953
954      for (hi = apr_hash_first(iterpool, move_youngest);
955           hi;
956           hi = apr_hash_next(hi))
957        {
958          svn_client_commit_item3_t *item = apr_hash_this_val(hi);
959
960          /* We delete the original side with its original revision and will
961             receive an out-of-date error if that node changed since that
962             revision.
963
964             The copy is of that same revision and we know that this revision
965             didn't change between this revision and youngest. So we can just
966             as well commit a copy from youngest.
967
968            Note that it is still possible to see gaps between the delete and
969            copy revisions as the repository might handle multiple commits
970            at the same time (or when an out of date proxy is involved), but
971            in general it should decrease the number of gaps. */
972
973          if (item->copyfrom_rev < youngest)
974            item->copyfrom_rev = youngest;
975        }
976    }
977
978  cmt_err = svn_error_trace(
979              get_ra_editor(&editor, &edit_baton, ra_session, ctx,
980                            log_msg, commit_items, revprop_table,
981                            lock_tokens, keep_locks, capture_commit_info,
982                            &cb, pool));
983
984  if (cmt_err)
985    goto cleanup;
986
987  /* Make a note that we have a commit-in-progress. */
988  commit_in_progress = TRUE;
989
990  /* We'll assume that, once we pass this point, we are going to need to
991   * sleep for timestamps.  Really, we may not need to do unless and until
992   * we reach the point where we post-commit 'bump' the WC metadata. */
993  timestamp_sleep = TRUE;
994
995  /* Perform the commit. */
996  cmt_err = svn_error_trace(
997              svn_client__do_commit(base_url, commit_items, editor, edit_baton,
998                                    notify_prefix, &sha1_checksums, ctx, pool,
999                                    iterpool));
1000
1001  /* Handle a successful commit. */
1002  if ((! cmt_err)
1003      || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1004    {
1005      svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
1006
1007      /* Make a note that our commit is finished. */
1008      commit_in_progress = FALSE;
1009
1010      for (i = 0; i < commit_items->nelts; i++)
1011        {
1012          svn_client_commit_item3_t *item
1013            = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1014
1015          svn_pool_clear(iterpool);
1016          bump_err = post_process_commit_item(
1017                       queue, item, ctx->wc_ctx,
1018                       keep_changelists, keep_locks, commit_as_operations,
1019                       svn_hash_gets(sha1_checksums, item->path),
1020                       iterpool);
1021          if (bump_err)
1022            goto cleanup;
1023        }
1024
1025      SVN_ERR_ASSERT(commit_info);
1026      bump_err = svn_wc_process_committed_queue2(
1027                   queue, ctx->wc_ctx,
1028                   commit_info->revision,
1029                   commit_info->date,
1030                   commit_info->author,
1031                   ctx->cancel_func, ctx->cancel_baton,
1032                   iterpool);
1033
1034      if (bump_err)
1035        goto cleanup;
1036    }
1037
1038 cleanup:
1039  /* Sleep to ensure timestamp integrity.  BASE_ABSPATH may have been
1040     removed by the commit or it may the common ancestor of multiple
1041     working copies. */
1042  if (timestamp_sleep)
1043    {
1044      const char *sleep_abspath;
1045      svn_error_t *err = svn_wc__get_wcroot(&sleep_abspath, ctx->wc_ctx,
1046                                            base_abspath, pool, pool);
1047      if (err)
1048        {
1049          svn_error_clear(err);
1050          sleep_abspath = base_abspath;
1051        }
1052
1053      svn_io_sleep_for_timestamps(sleep_abspath, pool);
1054    }
1055
1056  /* Abort the commit if it is still in progress. */
1057  svn_pool_clear(iterpool); /* Close open handles before aborting */
1058  if (commit_in_progress)
1059    cmt_err = svn_error_compose_create(cmt_err,
1060                                       editor->abort_edit(edit_baton, pool));
1061
1062  /* A bump error is likely to occur while running a working copy log file,
1063     explicitly unlocking and removing temporary files would be wrong in
1064     that case.  A commit error (cmt_err) should only occur before any
1065     attempt to modify the working copy, so it doesn't prevent explicit
1066     clean-up. */
1067  if (! bump_err)
1068    {
1069      /* Release all locks we obtained */
1070      for (i = 0; i < locks_obtained->nelts; i++)
1071        {
1072          const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1073                                                const char *);
1074
1075          svn_pool_clear(iterpool);
1076
1077          unlock_err = svn_error_compose_create(
1078                           svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1079                                                      iterpool),
1080                           unlock_err);
1081        }
1082    }
1083
1084  svn_pool_destroy(iterpool);
1085
1086  return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
1087                                          pool));
1088}
1089