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
49#include "svn_private_config.h"
50
51struct capture_baton_t {
52  svn_commit_callback2_t original_callback;
53  void *original_baton;
54
55  svn_commit_info_t **info;
56  apr_pool_t *pool;
57};
58
59
60static svn_error_t *
61capture_commit_info(const svn_commit_info_t *commit_info,
62                    void *baton,
63                    apr_pool_t *pool)
64{
65  struct capture_baton_t *cb = baton;
66
67  *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
68
69  if (cb->original_callback)
70    SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
71
72  return SVN_NO_ERROR;
73}
74
75
76static svn_error_t *
77get_ra_editor(const svn_delta_editor_t **editor,
78              void **edit_baton,
79              svn_ra_session_t *ra_session,
80              svn_client_ctx_t *ctx,
81              const char *log_msg,
82              const apr_array_header_t *commit_items,
83              const apr_hash_t *revprop_table,
84              apr_hash_t *lock_tokens,
85              svn_boolean_t keep_locks,
86              svn_commit_callback2_t commit_callback,
87              void *commit_baton,
88              apr_pool_t *pool)
89{
90  apr_hash_t *commit_revprops;
91  apr_hash_t *relpath_map = NULL;
92
93  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
94                                           log_msg, ctx, pool));
95
96#ifdef ENABLE_EV2_SHIMS
97  if (commit_items)
98    {
99      int i;
100      apr_pool_t *iterpool = svn_pool_create(pool);
101
102      relpath_map = apr_hash_make(pool);
103      for (i = 0; i < commit_items->nelts; i++)
104        {
105          svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
106                                                  svn_client_commit_item3_t *);
107          const char *relpath;
108
109          if (!item->path)
110            continue;
111
112          svn_pool_clear(iterpool);
113          SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
114                                          ctx->wc_ctx, item->path, FALSE, pool,
115                                          iterpool));
116          if (relpath)
117            svn_hash_sets(relpath_map, relpath, item->path);
118        }
119      svn_pool_destroy(iterpool);
120    }
121#endif
122
123  /* Fetch RA commit editor. */
124  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
125                        svn_client__get_shim_callbacks(ctx->wc_ctx,
126                                                       relpath_map, pool)));
127  SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
128                                    commit_revprops, commit_callback,
129                                    commit_baton, lock_tokens, keep_locks,
130                                    pool));
131
132  return SVN_NO_ERROR;
133}
134
135
136/*** Public Interfaces. ***/
137
138static svn_error_t *
139reconcile_errors(svn_error_t *commit_err,
140                 svn_error_t *unlock_err,
141                 svn_error_t *bump_err,
142                 apr_pool_t *pool)
143{
144  svn_error_t *err;
145
146  /* Early release (for good behavior). */
147  if (! (commit_err || unlock_err || bump_err))
148    return SVN_NO_ERROR;
149
150  /* If there was a commit error, start off our error chain with
151     that. */
152  if (commit_err)
153    {
154      commit_err = svn_error_quick_wrap
155        (commit_err, _("Commit failed (details follow):"));
156      err = commit_err;
157    }
158
159  /* Else, create a new "general" error that will lead off the errors
160     that follow. */
161  else
162    err = svn_error_create(SVN_ERR_BASE, NULL,
163                           _("Commit succeeded, but other errors follow:"));
164
165  /* If there was an unlock error... */
166  if (unlock_err)
167    {
168      /* Wrap the error with some headers. */
169      unlock_err = svn_error_quick_wrap
170        (unlock_err, _("Error unlocking locked dirs (details follow):"));
171
172      /* Append this error to the chain. */
173      svn_error_compose(err, unlock_err);
174    }
175
176  /* If there was a bumping error... */
177  if (bump_err)
178    {
179      /* Wrap the error with some headers. */
180      bump_err = svn_error_quick_wrap
181        (bump_err, _("Error bumping revisions post-commit (details follow):"));
182
183      /* Append this error to the chain. */
184      svn_error_compose(err, bump_err);
185    }
186
187  return err;
188}
189
190/* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
191   to a new hashtable allocated in POOL.  *RESULT is set to point to this
192   new hash table.  *RESULT will be keyed on const char * URI-decoded paths
193   relative to BASE_URL.  The lock tokens will not be duplicated. */
194static svn_error_t *
195collect_lock_tokens(apr_hash_t **result,
196                    apr_hash_t *all_tokens,
197                    const char *base_url,
198                    apr_pool_t *pool)
199{
200  apr_hash_index_t *hi;
201
202  *result = apr_hash_make(pool);
203
204  for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
205    {
206      const char *url = svn__apr_hash_index_key(hi);
207      const char *token = svn__apr_hash_index_val(hi);
208      const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
209
210      if (relpath)
211        {
212          svn_hash_sets(*result, relpath, token);
213        }
214    }
215
216  return SVN_NO_ERROR;
217}
218
219/* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
220 * If a checksum is provided, it can be the MD5 and/or the SHA1. */
221static svn_error_t *
222post_process_commit_item(svn_wc_committed_queue_t *queue,
223                         const svn_client_commit_item3_t *item,
224                         svn_wc_context_t *wc_ctx,
225                         svn_boolean_t keep_changelists,
226                         svn_boolean_t keep_locks,
227                         svn_boolean_t commit_as_operations,
228                         const svn_checksum_t *sha1_checksum,
229                         apr_pool_t *scratch_pool)
230{
231  svn_boolean_t loop_recurse = FALSE;
232  svn_boolean_t remove_lock;
233
234  if (! commit_as_operations
235      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
236      && (item->kind == svn_node_dir)
237      && (item->copyfrom_url))
238    loop_recurse = TRUE;
239
240  remove_lock = (! keep_locks && (item->state_flags
241                                       & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
242
243  /* When the node was deleted (or replaced), we need to always remove the
244     locks, as they're invalidated on the server. We cannot honor the
245     SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell
246     us whether we have locked children. */
247  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
248    remove_lock = TRUE;
249
250  return svn_wc_queue_committed3(queue, wc_ctx, item->path,
251                                 loop_recurse, item->incoming_prop_changes,
252                                 remove_lock, !keep_changelists,
253                                 sha1_checksum, scratch_pool);
254}
255
256
257static svn_error_t *
258check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx,
259                              const char *target_abspath,
260                              svn_depth_t depth,
261                              apr_pool_t *scratch_pool)
262{
263  svn_node_kind_t kind;
264
265  SVN_ERR_ASSERT(depth != svn_depth_infinity);
266
267  SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath,
268                            TRUE, FALSE, scratch_pool));
269
270
271  /* ### TODO(sd): This check is slightly too strict.  It should be
272     ### possible to:
273     ###
274     ###   * delete a directory containing only files when
275     ###     depth==svn_depth_files;
276     ###
277     ###   * delete a directory containing only files and empty
278     ###     subdirs when depth==svn_depth_immediates.
279     ###
280     ### But for now, we insist on svn_depth_infinity if you're
281     ### going to delete a directory, because we're lazy and
282     ### trying to get depthy commits working in the first place.
283     ###
284     ### This would be fairly easy to fix, though: just, well,
285     ### check the above conditions!
286     ###
287     ### GJS: I think there may be some confusion here. there is
288     ###      the depth of the commit, and the depth of a checked-out
289     ###      directory in the working copy. Delete, by its nature, will
290     ###      always delete all of its children, so it seems a bit
291     ###      strange to worry about what is in the working copy.
292  */
293  if (kind == svn_node_dir)
294    {
295      svn_wc_schedule_t schedule;
296
297      /* ### Looking at schedule is probably enough, no need for
298         pristine compare etc. */
299      SVN_ERR(svn_wc__node_get_schedule(&schedule, NULL,
300                                        wc_ctx, target_abspath,
301                                        scratch_pool));
302
303      if (schedule == svn_wc_schedule_delete
304          || schedule == svn_wc_schedule_replace)
305        {
306          const apr_array_header_t *children;
307
308          SVN_ERR(svn_wc__node_get_children(&children, wc_ctx,
309                                            target_abspath, TRUE,
310                                            scratch_pool, scratch_pool));
311
312          if (children->nelts > 0)
313            return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
314                                     _("Cannot delete the directory '%s' "
315                                       "in a non-recursive commit "
316                                       "because it has children"),
317                                     svn_dirent_local_style(target_abspath,
318                                                            scratch_pool));
319        }
320    }
321
322  return SVN_NO_ERROR;
323}
324
325
326/* Given a list of committables described by their common base abspath
327   BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
328   which absolute paths must be locked to commit all these targets and
329   return this as a const char * array in LOCK_TARGETS
330
331   Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
332   storage */
333static svn_error_t *
334determine_lock_targets(apr_array_header_t **lock_targets,
335                       svn_wc_context_t *wc_ctx,
336                       const char *base_abspath,
337                       const apr_array_header_t *target_relpaths,
338                       apr_pool_t *result_pool,
339                       apr_pool_t *scratch_pool)
340{
341  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
342  apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
343  apr_hash_index_t *hi;
344  int i;
345
346  wc_items = apr_hash_make(scratch_pool);
347
348  /* Create an array of targets for each working copy used */
349  for (i = 0; i < target_relpaths->nelts; i++)
350    {
351      const char *target_abspath;
352      const char *wcroot_abspath;
353      apr_array_header_t *wc_targets;
354      svn_error_t *err;
355      const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
356                                                 const char *);
357
358      svn_pool_clear(iterpool);
359      target_abspath = svn_dirent_join(base_abspath, target_relpath,
360                                       scratch_pool);
361
362      err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
363                               iterpool, iterpool);
364
365      if (err)
366        {
367          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
368            {
369              svn_error_clear(err);
370              continue;
371            }
372          return svn_error_trace(err);
373        }
374
375      wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
376
377      if (! wc_targets)
378        {
379          wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
380          svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
381                        wc_targets);
382        }
383
384      APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
385    }
386
387  *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
388                                 sizeof(const char *));
389
390  /* For each working copy determine where to lock */
391  for (hi = apr_hash_first(scratch_pool, wc_items);
392       hi;
393       hi = apr_hash_next(hi))
394    {
395      const char *common;
396      const char *wcroot_abspath = svn__apr_hash_index_key(hi);
397      apr_array_header_t *wc_targets = svn__apr_hash_index_val(hi);
398
399      svn_pool_clear(iterpool);
400
401      if (wc_targets->nelts == 1)
402        {
403          const char *target_abspath;
404          target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
405
406          if (! strcmp(wcroot_abspath, target_abspath))
407            {
408              APR_ARRAY_PUSH(*lock_targets, const char *)
409                      = apr_pstrdup(result_pool, target_abspath);
410            }
411          else
412            {
413              /* Lock the parent to allow deleting the target */
414              APR_ARRAY_PUSH(*lock_targets, const char *)
415                      = svn_dirent_dirname(target_abspath, result_pool);
416            }
417        }
418      else if (wc_targets->nelts > 1)
419        {
420          SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
421                                              FALSE, iterpool, iterpool));
422
423          qsort(wc_targets->elts, wc_targets->nelts, wc_targets->elt_size,
424                svn_sort_compare_paths);
425
426          if (wc_targets->nelts == 0
427              || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
428              || !strcmp(common, wcroot_abspath))
429            {
430              APR_ARRAY_PUSH(*lock_targets, const char *)
431                    = apr_pstrdup(result_pool, common);
432            }
433          else
434            {
435              /* Lock the parent to allow deleting the target */
436              APR_ARRAY_PUSH(*lock_targets, const char *)
437                       = svn_dirent_dirname(common, result_pool);
438            }
439        }
440    }
441
442  svn_pool_destroy(iterpool);
443  return SVN_NO_ERROR;
444}
445
446/* Baton for check_url_kind */
447struct check_url_kind_baton
448{
449  apr_pool_t *pool;
450  svn_ra_session_t *session;
451  const char *repos_root_url;
452  svn_client_ctx_t *ctx;
453};
454
455/* Implements svn_client__check_url_kind_t for svn_client_commit5 */
456static svn_error_t *
457check_url_kind(void *baton,
458               svn_node_kind_t *kind,
459               const char *url,
460               svn_revnum_t revision,
461               apr_pool_t *scratch_pool)
462{
463  struct check_url_kind_baton *cukb = baton;
464
465  /* If we don't have a session or can't use the session, get one */
466  if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
467    {
468      SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
469                                          cukb->pool, scratch_pool));
470      SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
471                                     cukb->pool));
472    }
473  else
474    SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
475
476  return svn_error_trace(
477                svn_ra_check_path(cukb->session, "", revision,
478                                  kind, scratch_pool));
479}
480
481/* Recurse into every target in REL_TARGETS, finding committable externals
482 * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
483 * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
484 * arguments correspond to those of svn_client_commit6(). */
485static svn_error_t*
486append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
487                                     const char *base_abspath,
488                                     svn_boolean_t include_file_externals,
489                                     svn_boolean_t include_dir_externals,
490                                     svn_depth_t depth,
491                                     svn_client_ctx_t *ctx,
492                                     apr_pool_t *result_pool,
493                                     apr_pool_t *scratch_pool)
494{
495  int rel_targets_nelts_fixed;
496  int i;
497  apr_pool_t *iterpool;
498
499  if (! (include_file_externals || include_dir_externals))
500    return SVN_NO_ERROR;
501
502  /* Easy part of applying DEPTH to externals. */
503  if (depth == svn_depth_empty)
504    {
505      /* Don't recurse. */
506      return SVN_NO_ERROR;
507    }
508
509  /* Iterate *and* grow REL_TARGETS at the same time. */
510  rel_targets_nelts_fixed = rel_targets->nelts;
511
512  iterpool = svn_pool_create(scratch_pool);
513
514  for (i = 0; i < rel_targets_nelts_fixed; i++)
515    {
516      int j;
517      const char *target;
518      apr_array_header_t *externals = NULL;
519
520      svn_pool_clear(iterpool);
521
522      target = svn_dirent_join(base_abspath,
523                               APR_ARRAY_IDX(rel_targets, i, const char *),
524                               iterpool);
525
526      /* ### TODO: Possible optimization: No need to do this for file targets.
527       * ### But what's cheaper, stat'ing the file system or querying the db?
528       * ### --> future. */
529
530      SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
531                                                  target, depth,
532                                                  iterpool, iterpool));
533
534      if (externals != NULL)
535        {
536          const char *rel_target;
537
538          for (j = 0; j < externals->nelts; j++)
539            {
540              svn_wc__committable_external_info_t *xinfo =
541                         APR_ARRAY_IDX(externals, j,
542                                       svn_wc__committable_external_info_t *);
543
544              if ((xinfo->kind == svn_node_file && ! include_file_externals)
545                  || (xinfo->kind == svn_node_dir && ! include_dir_externals))
546                continue;
547
548              rel_target = svn_dirent_skip_ancestor(base_abspath,
549                                                    xinfo->local_abspath);
550
551              SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
552
553              APR_ARRAY_PUSH(rel_targets, const char *) =
554                                         apr_pstrdup(result_pool, rel_target);
555            }
556        }
557    }
558
559  svn_pool_destroy(iterpool);
560  return SVN_NO_ERROR;
561}
562
563svn_error_t *
564svn_client_commit6(const apr_array_header_t *targets,
565                   svn_depth_t depth,
566                   svn_boolean_t keep_locks,
567                   svn_boolean_t keep_changelists,
568                   svn_boolean_t commit_as_operations,
569                   svn_boolean_t include_file_externals,
570                   svn_boolean_t include_dir_externals,
571                   const apr_array_header_t *changelists,
572                   const apr_hash_t *revprop_table,
573                   svn_commit_callback2_t commit_callback,
574                   void *commit_baton,
575                   svn_client_ctx_t *ctx,
576                   apr_pool_t *pool)
577{
578  const svn_delta_editor_t *editor;
579  void *edit_baton;
580  struct capture_baton_t cb;
581  svn_ra_session_t *ra_session;
582  const char *log_msg;
583  const char *base_abspath;
584  const char *base_url;
585  apr_array_header_t *rel_targets;
586  apr_array_header_t *lock_targets;
587  apr_array_header_t *locks_obtained;
588  svn_client__committables_t *committables;
589  apr_hash_t *lock_tokens;
590  apr_hash_t *sha1_checksums;
591  apr_array_header_t *commit_items;
592  svn_error_t *cmt_err = SVN_NO_ERROR;
593  svn_error_t *bump_err = SVN_NO_ERROR;
594  svn_error_t *unlock_err = SVN_NO_ERROR;
595  svn_boolean_t commit_in_progress = FALSE;
596  svn_boolean_t timestamp_sleep = FALSE;
597  svn_commit_info_t *commit_info = NULL;
598  apr_pool_t *iterpool = svn_pool_create(pool);
599  const char *current_abspath;
600  const char *notify_prefix;
601  int depth_empty_after = -1;
602  int i;
603
604  SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
605
606  /* Committing URLs doesn't make sense, so error if it's tried. */
607  for (i = 0; i < targets->nelts; i++)
608    {
609      const char *target = APR_ARRAY_IDX(targets, i, const char *);
610      if (svn_path_is_url(target))
611        return svn_error_createf
612          (SVN_ERR_ILLEGAL_TARGET, NULL,
613           _("'%s' is a URL, but URLs cannot be commit targets"), target);
614    }
615
616  /* Condense the target list. This makes all targets absolute. */
617  SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
618                                      FALSE, pool, iterpool));
619
620  /* No targets means nothing to commit, so just return. */
621  if (base_abspath == NULL)
622    return SVN_NO_ERROR;
623
624  SVN_ERR_ASSERT(rel_targets != NULL);
625
626  /* If we calculated only a base and no relative targets, this
627     must mean that we are being asked to commit (effectively) a
628     single path. */
629  if (rel_targets->nelts == 0)
630    APR_ARRAY_PUSH(rel_targets, const char *) = "";
631
632  if (include_file_externals || include_dir_externals)
633    {
634      if (depth != svn_depth_unknown && depth != svn_depth_infinity)
635        {
636          /* All targets after this will be handled as depth empty */
637          depth_empty_after = rel_targets->nelts;
638        }
639
640      SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
641                                                   include_file_externals,
642                                                   include_dir_externals,
643                                                   depth, ctx,
644                                                   pool, pool));
645    }
646
647  SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
648                                 rel_targets, pool, iterpool));
649
650  locks_obtained = apr_array_make(pool, lock_targets->nelts,
651                                  sizeof(const char *));
652
653  for (i = 0; i < lock_targets->nelts; i++)
654    {
655      const char *lock_root;
656      const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
657
658      svn_pool_clear(iterpool);
659
660      cmt_err = svn_error_trace(
661                    svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
662                                           FALSE, pool, iterpool));
663
664      if (cmt_err)
665        goto cleanup;
666
667      APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
668    }
669
670  /* Determine prefix to strip from the commit notify messages */
671  SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
672  notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
673                                                  base_abspath,
674                                                  pool);
675
676  /* If a non-recursive commit is desired, do not allow a deleted directory
677     as one of the targets. */
678  if (depth != svn_depth_infinity && ! commit_as_operations)
679    for (i = 0; i < rel_targets->nelts; i++)
680      {
681        const char *relpath = APR_ARRAY_IDX(rel_targets, i, const char *);
682        const char *target_abspath;
683
684        svn_pool_clear(iterpool);
685
686        target_abspath = svn_dirent_join(base_abspath, relpath, iterpool);
687
688        cmt_err = svn_error_trace(
689          check_nonrecursive_dir_delete(ctx->wc_ctx, target_abspath,
690                                        depth, iterpool));
691
692        if (cmt_err)
693          goto cleanup;
694      }
695
696  /* Crawl the working copy for commit items. */
697  {
698    struct check_url_kind_baton cukb;
699
700    /* Prepare for when we have a copy containing not-present nodes. */
701    cukb.pool = iterpool;
702    cukb.session = NULL; /* ### Can we somehow reuse session? */
703    cukb.repos_root_url = NULL;
704    cukb.ctx = ctx;
705
706    cmt_err = svn_error_trace(
707                   svn_client__harvest_committables(&committables,
708                                                    &lock_tokens,
709                                                    base_abspath,
710                                                    rel_targets,
711                                                    depth_empty_after,
712                                                    depth,
713                                                    ! keep_locks,
714                                                    changelists,
715                                                    check_url_kind,
716                                                    &cukb,
717                                                    ctx,
718                                                    pool,
719                                                    iterpool));
720
721    svn_pool_clear(iterpool);
722  }
723
724  if (cmt_err)
725    goto cleanup;
726
727  if (apr_hash_count(committables->by_repository) == 0)
728    {
729      goto cleanup; /* Nothing to do */
730    }
731  else if (apr_hash_count(committables->by_repository) > 1)
732    {
733      cmt_err = svn_error_create(
734             SVN_ERR_UNSUPPORTED_FEATURE, NULL,
735             _("Commit can only commit to a single repository at a time.\n"
736               "Are all targets part of the same working copy?"));
737      goto cleanup;
738    }
739
740  {
741    apr_hash_index_t *hi = apr_hash_first(iterpool,
742                                          committables->by_repository);
743
744    commit_items = svn__apr_hash_index_val(hi);
745  }
746
747  /* If our array of targets contains only locks (and no actual file
748     or prop modifications), then we return here to avoid committing a
749     revision with no changes. */
750  {
751    svn_boolean_t found_changed_path = FALSE;
752
753    for (i = 0; i < commit_items->nelts; ++i)
754      {
755        svn_client_commit_item3_t *item =
756          APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
757
758        if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
759          {
760            found_changed_path = TRUE;
761            break;
762          }
763      }
764
765    if (!found_changed_path)
766      goto cleanup;
767  }
768
769  /* For every target that was moved verify that both halves of the
770   * move are part of the commit. */
771  for (i = 0; i < commit_items->nelts; i++)
772    {
773      svn_client_commit_item3_t *item =
774        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
775
776      svn_pool_clear(iterpool);
777
778      if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
779        {
780          /* ### item->moved_from_abspath contains the move origin */
781          const char *moved_from_abspath;
782          const char *delete_op_root_abspath;
783
784          cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
785                                      &moved_from_abspath,
786                                      &delete_op_root_abspath,
787                                      ctx->wc_ctx, item->path,
788                                      iterpool, iterpool));
789          if (cmt_err)
790            goto cleanup;
791
792          if (moved_from_abspath && delete_op_root_abspath &&
793              strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
794
795            {
796              svn_boolean_t found_delete_half =
797                (svn_hash_gets(committables->by_path, delete_op_root_abspath)
798                 != NULL);
799
800              if (!found_delete_half)
801                {
802                  const char *delete_half_parent_abspath;
803
804                  /* The delete-half isn't in the commit target list.
805                   * However, it might itself be the child of a deleted node,
806                   * either because of another move or a deletion.
807                   *
808                   * For example, consider: mv A/B B; mv B/C C; commit;
809                   * C's moved-from A/B/C is a child of the deleted A/B.
810                   * A/B/C does not appear in the commit target list, but
811                   * A/B does appear.
812                   * (Note that moved-from information is always stored
813                   * relative to the BASE tree, so we have 'C moved-from
814                   * A/B/C', not 'C moved-from B/C'.)
815                   *
816                   * An example involving a move and a delete would be:
817                   * mv A/B C; rm A; commit;
818                   * Now C is moved-from A/B which does not appear in the
819                   * commit target list, but A does appear.
820                   */
821
822                  /* Scan upwards for a deletion op-root from the
823                   * delete-half's parent directory. */
824                  delete_half_parent_abspath =
825                    svn_dirent_dirname(delete_op_root_abspath, iterpool);
826                  if (strcmp(delete_op_root_abspath,
827                             delete_half_parent_abspath) != 0)
828                    {
829                      const char *parent_delete_op_root_abspath;
830
831                      cmt_err = svn_error_trace(
832                                  svn_wc__node_get_deleted_ancestor(
833                                    &parent_delete_op_root_abspath,
834                                    ctx->wc_ctx, delete_half_parent_abspath,
835                                    iterpool, iterpool));
836                      if (cmt_err)
837                        goto cleanup;
838
839                      if (parent_delete_op_root_abspath)
840                        found_delete_half =
841                          (svn_hash_gets(committables->by_path,
842                                         parent_delete_op_root_abspath)
843                           != NULL);
844                    }
845                }
846
847              if (!found_delete_half)
848                {
849                  cmt_err = svn_error_createf(
850                              SVN_ERR_ILLEGAL_TARGET, NULL,
851                              _("Cannot commit '%s' because it was moved from "
852                                "'%s' which is not part of the commit; both "
853                                "sides of the move must be committed together"),
854                              svn_dirent_local_style(item->path, iterpool),
855                              svn_dirent_local_style(delete_op_root_abspath,
856                                                     iterpool));
857                  goto cleanup;
858                }
859            }
860        }
861
862      if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
863        {
864          const char *moved_to_abspath;
865          const char *copy_op_root_abspath;
866
867          cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
868                                      &moved_to_abspath,
869                                      &copy_op_root_abspath,
870                                      ctx->wc_ctx, item->path,
871                                      iterpool, iterpool));
872          if (cmt_err)
873            goto cleanup;
874
875          if (moved_to_abspath && copy_op_root_abspath &&
876              strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
877              svn_hash_gets(committables->by_path, copy_op_root_abspath)
878              == NULL)
879            {
880              cmt_err = svn_error_createf(
881                          SVN_ERR_ILLEGAL_TARGET, NULL,
882                         _("Cannot commit '%s' because it was moved to '%s' "
883                           "which is not part of the commit; both sides of "
884                           "the move must be committed together"),
885                         svn_dirent_local_style(item->path, iterpool),
886                         svn_dirent_local_style(copy_op_root_abspath,
887                                                iterpool));
888              goto cleanup;
889            }
890        }
891    }
892
893  /* Go get a log message.  If an error occurs, or no log message is
894     specified, abort the operation. */
895  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
896    {
897      const char *tmp_file;
898      cmt_err = svn_error_trace(
899                     svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
900                                             ctx, pool));
901
902      if (cmt_err || (! log_msg))
903        goto cleanup;
904    }
905  else
906    log_msg = "";
907
908  /* Sort and condense our COMMIT_ITEMS. */
909  cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
910                                                              commit_items,
911                                                              pool));
912
913  if (cmt_err)
914    goto cleanup;
915
916  /* Collect our lock tokens with paths relative to base_url. */
917  cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
918                                                base_url, pool));
919
920  if (cmt_err)
921    goto cleanup;
922
923  cb.original_callback = commit_callback;
924  cb.original_baton = commit_baton;
925  cb.info = &commit_info;
926  cb.pool = pool;
927
928  /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
929   * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
930   * parent of nested working copies. We don't support commits to multiple
931   * repositories so using the first WC to get the RA session is safe. */
932  cmt_err = svn_error_trace(
933              svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
934                                                   APR_ARRAY_IDX(lock_targets,
935                                                                 0,
936                                                                 const char *),
937                                                   commit_items,
938                                                   TRUE, TRUE, ctx,
939                                                   pool, pool));
940
941  if (cmt_err)
942    goto cleanup;
943
944  cmt_err = svn_error_trace(
945              get_ra_editor(&editor, &edit_baton, ra_session, ctx,
946                            log_msg, commit_items, revprop_table,
947                            lock_tokens, keep_locks, capture_commit_info,
948                            &cb, pool));
949
950  if (cmt_err)
951    goto cleanup;
952
953  /* Make a note that we have a commit-in-progress. */
954  commit_in_progress = TRUE;
955
956  /* We'll assume that, once we pass this point, we are going to need to
957   * sleep for timestamps.  Really, we may not need to do unless and until
958   * we reach the point where we post-commit 'bump' the WC metadata. */
959  timestamp_sleep = TRUE;
960
961  /* Perform the commit. */
962  cmt_err = svn_error_trace(
963              svn_client__do_commit(base_url, commit_items, editor, edit_baton,
964                                    notify_prefix, &sha1_checksums, ctx, pool,
965                                    iterpool));
966
967  /* Handle a successful commit. */
968  if ((! cmt_err)
969      || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
970    {
971      svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
972
973      /* Make a note that our commit is finished. */
974      commit_in_progress = FALSE;
975
976      for (i = 0; i < commit_items->nelts; i++)
977        {
978          svn_client_commit_item3_t *item
979            = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
980
981          svn_pool_clear(iterpool);
982          bump_err = post_process_commit_item(
983                       queue, item, ctx->wc_ctx,
984                       keep_changelists, keep_locks, commit_as_operations,
985                       svn_hash_gets(sha1_checksums, item->path),
986                       iterpool);
987          if (bump_err)
988            goto cleanup;
989        }
990
991      SVN_ERR_ASSERT(commit_info);
992      bump_err = svn_wc_process_committed_queue2(
993                   queue, ctx->wc_ctx,
994                   commit_info->revision,
995                   commit_info->date,
996                   commit_info->author,
997                   ctx->cancel_func, ctx->cancel_baton,
998                   iterpool);
999    }
1000
1001 cleanup:
1002  /* Sleep to ensure timestamp integrity. */
1003  if (timestamp_sleep)
1004    svn_io_sleep_for_timestamps(base_abspath, pool);
1005
1006  /* Abort the commit if it is still in progress. */
1007  svn_pool_clear(iterpool); /* Close open handles before aborting */
1008  if (commit_in_progress)
1009    cmt_err = svn_error_compose_create(cmt_err,
1010                                       editor->abort_edit(edit_baton, pool));
1011
1012  /* A bump error is likely to occur while running a working copy log file,
1013     explicitly unlocking and removing temporary files would be wrong in
1014     that case.  A commit error (cmt_err) should only occur before any
1015     attempt to modify the working copy, so it doesn't prevent explicit
1016     clean-up. */
1017  if (! bump_err)
1018    {
1019      /* Release all locks we obtained */
1020      for (i = 0; i < locks_obtained->nelts; i++)
1021        {
1022          const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1023                                                const char *);
1024
1025          svn_pool_clear(iterpool);
1026
1027          unlock_err = svn_error_compose_create(
1028                           svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1029                                                      iterpool),
1030                           unlock_err);
1031        }
1032    }
1033
1034  svn_pool_destroy(iterpool);
1035
1036  return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
1037                                          pool));
1038}
1039