copy.c revision 289166
1/*
2 * copy.c:  copy/move wrappers around wc 'copy' 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 "svn_hash.h"
32#include "svn_client.h"
33#include "svn_error.h"
34#include "svn_error_codes.h"
35#include "svn_dirent_uri.h"
36#include "svn_path.h"
37#include "svn_opt.h"
38#include "svn_time.h"
39#include "svn_props.h"
40#include "svn_mergeinfo.h"
41#include "svn_pools.h"
42
43#include "client.h"
44#include "mergeinfo.h"
45
46#include "svn_private_config.h"
47#include "private/svn_wc_private.h"
48#include "private/svn_ra_private.h"
49#include "private/svn_mergeinfo_private.h"
50#include "private/svn_client_private.h"
51
52
53/*
54 * OUR BASIC APPROACH TO COPIES
55 * ============================
56 *
57 * for each source/destination pair
58 *   if (not exist src_path)
59 *     return ERR_BAD_SRC error
60 *
61 *   if (exist dst_path)
62 *     return ERR_OBSTRUCTION error
63 *   else
64 *     copy src_path into parent_of_dst_path as basename (dst_path)
65 *
66 *   if (this is a move)
67 *     delete src_path
68 */
69
70
71
72/*** Code. ***/
73
74/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
75   MERGEINFO to any mergeinfo pre-existing in the WC. */
76static svn_error_t *
77extend_wc_mergeinfo(const char *target_abspath,
78                    apr_hash_t *mergeinfo,
79                    svn_client_ctx_t *ctx,
80                    apr_pool_t *pool)
81{
82  apr_hash_t *wc_mergeinfo;
83
84  /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
85     updating it. */
86  SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
87                                      target_abspath, pool, pool));
88
89  /* Combine the provided mergeinfo with any mergeinfo from the WC. */
90  if (wc_mergeinfo && mergeinfo)
91    SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
92  else if (! wc_mergeinfo)
93    wc_mergeinfo = mergeinfo;
94
95  return svn_error_trace(
96    svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
97                                    FALSE, ctx, pool));
98}
99
100/* Find the longest common ancestor of paths in COPY_PAIRS.  If
101   SRC_ANCESTOR is NULL, ignore source paths in this calculation.  If
102   DST_ANCESTOR is NULL, ignore destination paths in this calculation.
103   COMMON_ANCESTOR will be the common ancestor of both the
104   SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
105   NULL.
106 */
107static svn_error_t *
108get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
109                        const char **src_ancestor,
110                        const char **dst_ancestor,
111                        const char **common_ancestor,
112                        apr_pool_t *pool)
113{
114  apr_pool_t *subpool = svn_pool_create(pool);
115  svn_client__copy_pair_t *first;
116  const char *first_dst;
117  const char *first_src;
118  const char *top_dst;
119  svn_boolean_t src_is_url;
120  svn_boolean_t dst_is_url;
121  char *top_src;
122  int i;
123
124  first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
125
126  /* Because all the destinations are in the same directory, we can easily
127     determine their common ancestor. */
128  first_dst = first->dst_abspath_or_url;
129  dst_is_url = svn_path_is_url(first_dst);
130
131  if (copy_pairs->nelts == 1)
132    top_dst = apr_pstrdup(subpool, first_dst);
133  else
134    top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
135                         : svn_dirent_dirname(first_dst, subpool);
136
137  /* Sources can came from anywhere, so we have to actually do some
138     work for them.  */
139  first_src = first->src_abspath_or_url;
140  src_is_url = svn_path_is_url(first_src);
141  top_src = apr_pstrdup(subpool, first_src);
142  for (i = 1; i < copy_pairs->nelts; i++)
143    {
144      /* We don't need to clear the subpool here for several reasons:
145         1)  If we do, we can't use it to allocate the initial versions of
146             top_src and top_dst (above).
147         2)  We don't return any errors in the following loop, so we
148             are guanteed to destroy the subpool at the end of this function.
149         3)  The number of iterations is likely to be few, and the loop will
150             be through quickly, so memory leakage will not be significant,
151             in time or space.
152      */
153      const svn_client__copy_pair_t *pair =
154        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
155
156      top_src = src_is_url
157        ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
158                                       subpool)
159        : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
160                                          subpool);
161    }
162
163  if (src_ancestor)
164    *src_ancestor = apr_pstrdup(pool, top_src);
165
166  if (dst_ancestor)
167    *dst_ancestor = apr_pstrdup(pool, top_dst);
168
169  if (common_ancestor)
170    *common_ancestor =
171               src_is_url
172                    ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
173                    : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
174
175  svn_pool_destroy(subpool);
176
177  return SVN_NO_ERROR;
178}
179
180
181/* The guts of do_wc_to_wc_copies */
182static svn_error_t *
183do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
184                                   const apr_array_header_t *copy_pairs,
185                                   const char *dst_parent,
186                                   svn_client_ctx_t *ctx,
187                                   apr_pool_t *scratch_pool)
188{
189  int i;
190  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
191  svn_error_t *err = SVN_NO_ERROR;
192
193  for (i = 0; i < copy_pairs->nelts; i++)
194    {
195      const char *dst_abspath;
196      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
197                                                    svn_client__copy_pair_t *);
198      svn_pool_clear(iterpool);
199
200      /* Check for cancellation */
201      if (ctx->cancel_func)
202        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
203
204      /* Perform the copy */
205      dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
206                                    iterpool);
207      *timestamp_sleep = TRUE;
208      err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
209                         FALSE /* metadata_only */,
210                         ctx->cancel_func, ctx->cancel_baton,
211                         ctx->notify_func2, ctx->notify_baton2, iterpool);
212      if (err)
213        break;
214    }
215  svn_pool_destroy(iterpool);
216
217  SVN_ERR(err);
218  return SVN_NO_ERROR;
219}
220
221/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST.  Use POOL for temporary
222   allocations. */
223static svn_error_t *
224do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
225                   const apr_array_header_t *copy_pairs,
226                   svn_client_ctx_t *ctx,
227                   apr_pool_t *pool)
228{
229  const char *dst_parent, *dst_parent_abspath;
230
231  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
232  if (copy_pairs->nelts == 1)
233    dst_parent = svn_dirent_dirname(dst_parent, pool);
234
235  SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
236
237  SVN_WC__CALL_WITH_WRITE_LOCK(
238    do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
239                                       ctx, pool),
240    ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
241
242  return SVN_NO_ERROR;
243}
244
245/* The locked bit of do_wc_to_wc_moves. */
246static svn_error_t *
247do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
248                              const char *dst_parent_abspath,
249                              svn_boolean_t lock_src,
250                              svn_boolean_t lock_dst,
251                              svn_boolean_t allow_mixed_revisions,
252                              svn_boolean_t metadata_only,
253                              svn_client_ctx_t *ctx,
254                              apr_pool_t *scratch_pool)
255{
256  const char *dst_abspath;
257
258  dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
259                                scratch_pool);
260
261  SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
262                        dst_abspath, metadata_only,
263                        allow_mixed_revisions,
264                        ctx->cancel_func, ctx->cancel_baton,
265                        ctx->notify_func2, ctx->notify_baton2,
266                        scratch_pool));
267
268  return SVN_NO_ERROR;
269}
270
271/* Wrapper to add an optional second lock */
272static svn_error_t *
273do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
274                              const char *dst_parent_abspath,
275                              svn_boolean_t lock_src,
276                              svn_boolean_t lock_dst,
277                              svn_boolean_t allow_mixed_revisions,
278                              svn_boolean_t metadata_only,
279                              svn_client_ctx_t *ctx,
280                              apr_pool_t *scratch_pool)
281{
282  if (lock_dst)
283    SVN_WC__CALL_WITH_WRITE_LOCK(
284      do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
285                                    lock_dst, allow_mixed_revisions,
286                                    metadata_only,
287                                    ctx, scratch_pool),
288      ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
289  else
290    SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
291                                          lock_dst, allow_mixed_revisions,
292                                          metadata_only,
293                                          ctx, scratch_pool));
294
295  return SVN_NO_ERROR;
296}
297
298/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
299   afterwards.  Use POOL for temporary allocations. */
300static svn_error_t *
301do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
302                  const apr_array_header_t *copy_pairs,
303                  const char *dst_path,
304                  svn_boolean_t allow_mixed_revisions,
305                  svn_boolean_t metadata_only,
306                  svn_client_ctx_t *ctx,
307                  apr_pool_t *pool)
308{
309  int i;
310  apr_pool_t *iterpool = svn_pool_create(pool);
311  svn_error_t *err = SVN_NO_ERROR;
312
313  for (i = 0; i < copy_pairs->nelts; i++)
314    {
315      const char *src_parent_abspath;
316      svn_boolean_t lock_src, lock_dst;
317      const char *src_wcroot_abspath;
318      const char *dst_wcroot_abspath;
319
320      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
321                                                    svn_client__copy_pair_t *);
322      svn_pool_clear(iterpool);
323
324      /* Check for cancellation */
325      if (ctx->cancel_func)
326        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
327
328      src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
329                                              iterpool);
330
331      SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
332                                 ctx->wc_ctx, src_parent_abspath,
333                                 iterpool, iterpool));
334      SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
335                                 ctx->wc_ctx, pair->dst_parent_abspath,
336                                 iterpool, iterpool));
337
338      /* We now need to lock the right combination of batons.
339         Four cases:
340           1) src_parent == dst_parent
341           2) src_parent is parent of dst_parent
342           3) dst_parent is parent of src_parent
343           4) src_parent and dst_parent are disjoint
344         We can handle 1) as either 2) or 3) */
345      if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
346          || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
347                                  NULL)
348              && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
349                                      NULL)))
350        {
351          lock_src = TRUE;
352          lock_dst = FALSE;
353        }
354      else if (svn_dirent_is_child(pair->dst_parent_abspath,
355                                   src_parent_abspath, NULL)
356               && !svn_dirent_is_child(pair->dst_parent_abspath,
357                                       src_wcroot_abspath, NULL))
358        {
359          lock_src = FALSE;
360          lock_dst = TRUE;
361        }
362      else
363        {
364          lock_src = TRUE;
365          lock_dst = TRUE;
366        }
367
368      *timestamp_sleep = TRUE;
369
370      /* Perform the copy and then the delete. */
371      if (lock_src)
372        SVN_WC__CALL_WITH_WRITE_LOCK(
373          do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
374                                        lock_src, lock_dst,
375                                        allow_mixed_revisions,
376                                        metadata_only,
377                                        ctx, iterpool),
378          ctx->wc_ctx, src_parent_abspath,
379          FALSE, iterpool);
380      else
381        SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
382                                              lock_src, lock_dst,
383                                              allow_mixed_revisions,
384                                              metadata_only,
385                                              ctx, iterpool));
386
387    }
388  svn_pool_destroy(iterpool);
389
390  return svn_error_trace(err);
391}
392
393/* Verify that the destinations stored in COPY_PAIRS are valid working copy
394   destinations and set pair->dst_parent_abspath and pair->base_name for each
395   item to the resulting location if they do */
396static svn_error_t *
397verify_wc_dsts(const apr_array_header_t *copy_pairs,
398               svn_boolean_t make_parents,
399               svn_boolean_t is_move,
400               svn_boolean_t metadata_only,
401               svn_client_ctx_t *ctx,
402               apr_pool_t *result_pool,
403               apr_pool_t *scratch_pool)
404{
405  int i;
406  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
407
408  /* Check that DST does not exist, but its parent does */
409  for (i = 0; i < copy_pairs->nelts; i++)
410    {
411      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
412                                                    svn_client__copy_pair_t *);
413      svn_node_kind_t dst_kind, dst_parent_kind;
414
415      svn_pool_clear(iterpool);
416
417      /* If DST_PATH does not exist, then its basename will become a new
418         file or dir added to its parent (possibly an implicit '.').
419         Else, just error out. */
420      SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
421                                pair->dst_abspath_or_url,
422                                FALSE /* show_deleted */,
423                                TRUE /* show_hidden */,
424                                iterpool));
425      if (dst_kind != svn_node_none)
426        {
427          svn_boolean_t is_excluded;
428          svn_boolean_t is_server_excluded;
429
430          SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
431                                              &is_server_excluded, ctx->wc_ctx,
432                                              pair->dst_abspath_or_url, FALSE,
433                                              iterpool));
434
435          if (is_excluded || is_server_excluded)
436            {
437              return svn_error_createf(
438                  SVN_ERR_WC_OBSTRUCTED_UPDATE,
439                  NULL, _("Path '%s' exists, but is excluded"),
440                  svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
441            }
442          else
443            return svn_error_createf(
444                            SVN_ERR_ENTRY_EXISTS, NULL,
445                            _("Path '%s' already exists"),
446                            svn_dirent_local_style(pair->dst_abspath_or_url,
447                                                   scratch_pool));
448        }
449
450      /* Check that there is no unversioned obstruction */
451      if (metadata_only)
452        dst_kind = svn_node_none;
453      else
454        SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
455                                  iterpool));
456
457      if (dst_kind != svn_node_none)
458        {
459          if (is_move
460              && copy_pairs->nelts == 1
461              && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
462                        svn_dirent_dirname(pair->dst_abspath_or_url,
463                                           iterpool)) == 0)
464            {
465              const char *dst;
466              char *dst_apr;
467              apr_status_t apr_err;
468              /* We have a rename inside a directory, which might collide
469                 just because the case insensivity of the filesystem makes
470                 the source match the destination. */
471
472              SVN_ERR(svn_path_cstring_from_utf8(&dst,
473                                                 pair->dst_abspath_or_url,
474                                                 scratch_pool));
475
476              apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
477                                           APR_FILEPATH_TRUENAME, iterpool);
478
479              if (!apr_err)
480                {
481                  /* And now bring it back to our canonical format */
482                  SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
483                  dst = svn_dirent_canonicalize(dst, iterpool);
484                }
485              /* else: Don't report this error; just report the normal error */
486
487              if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
488                {
489                  /* Ok, we have a single case only rename. Get out of here */
490                  svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
491                                   pair->dst_abspath_or_url, result_pool);
492
493                  svn_pool_destroy(iterpool);
494                  return SVN_NO_ERROR;
495                }
496            }
497
498          return svn_error_createf(
499                            SVN_ERR_ENTRY_EXISTS, NULL,
500                            _("Path '%s' already exists as unversioned node"),
501                            svn_dirent_local_style(pair->dst_abspath_or_url,
502                                                   scratch_pool));
503        }
504
505      svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
506                       pair->dst_abspath_or_url, result_pool);
507
508      /* Make sure the destination parent is a directory and produce a clear
509         error message if it is not. */
510      SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
511                                ctx->wc_ctx, pair->dst_parent_abspath,
512                                FALSE, TRUE,
513                                iterpool));
514      if (make_parents && dst_parent_kind == svn_node_none)
515        {
516          SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
517                                                 TRUE, ctx, iterpool));
518        }
519      else if (dst_parent_kind != svn_node_dir)
520        {
521          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
522                                   _("Path '%s' is not a directory"),
523                                   svn_dirent_local_style(
524                                     pair->dst_parent_abspath, scratch_pool));
525        }
526
527      SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
528                                &dst_parent_kind, scratch_pool));
529
530      if (dst_parent_kind != svn_node_dir)
531        return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
532                                 _("Path '%s' is not a directory"),
533                                 svn_dirent_local_style(
534                                     pair->dst_parent_abspath, scratch_pool));
535    }
536
537  svn_pool_destroy(iterpool);
538
539  return SVN_NO_ERROR;
540}
541
542static svn_error_t *
543verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
544                        svn_boolean_t make_parents,
545                        svn_boolean_t is_move,
546                        svn_boolean_t metadata_only,
547                        svn_client_ctx_t *ctx,
548                        apr_pool_t *result_pool,
549                        apr_pool_t *scratch_pool)
550{
551  int i;
552  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
553
554  /* Check that all of our SRCs exist. */
555  for (i = 0; i < copy_pairs->nelts; i++)
556    {
557      svn_boolean_t deleted_ok;
558      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
559                                                    svn_client__copy_pair_t *);
560      svn_pool_clear(iterpool);
561
562      deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
563                    || pair->src_op_revision.kind == svn_opt_revision_base);
564
565      /* Verify that SRC_PATH exists. */
566      SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
567                               pair->src_abspath_or_url,
568                               deleted_ok, FALSE, iterpool));
569      if (pair->src_kind == svn_node_none)
570        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
571                                 _("Path '%s' does not exist"),
572                                 svn_dirent_local_style(
573                                        pair->src_abspath_or_url,
574                                        scratch_pool));
575    }
576
577  SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
578                         result_pool, iterpool));
579
580  svn_pool_destroy(iterpool);
581
582  return SVN_NO_ERROR;
583}
584
585
586/* Path-specific state used as part of path_driver_cb_baton. */
587typedef struct path_driver_info_t
588{
589  const char *src_url;
590  const char *src_path;
591  const char *dst_path;
592  svn_node_kind_t src_kind;
593  svn_revnum_t src_revnum;
594  svn_boolean_t resurrection;
595  svn_boolean_t dir_add;
596  svn_string_t *mergeinfo;  /* the new mergeinfo for the target */
597} path_driver_info_t;
598
599
600/* The baton used with the path_driver_cb_func() callback for a copy
601   or move operation. */
602struct path_driver_cb_baton
603{
604  /* The editor (and its state) used to perform the operation. */
605  const svn_delta_editor_t *editor;
606  void *edit_baton;
607
608  /* A hash of path -> path_driver_info_t *'s. */
609  apr_hash_t *action_hash;
610
611  /* Whether the operation is a move or copy. */
612  svn_boolean_t is_move;
613};
614
615static svn_error_t *
616path_driver_cb_func(void **dir_baton,
617                    void *parent_baton,
618                    void *callback_baton,
619                    const char *path,
620                    apr_pool_t *pool)
621{
622  struct path_driver_cb_baton *cb_baton = callback_baton;
623  svn_boolean_t do_delete = FALSE, do_add = FALSE;
624  path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
625
626  /* Initialize return value. */
627  *dir_baton = NULL;
628
629  /* This function should never get an empty PATH.  We can neither
630     create nor delete the empty PATH, so if someone is calling us
631     with such, the code is just plain wrong. */
632  SVN_ERR_ASSERT(! svn_path_is_empty(path));
633
634  /* Check to see if we need to add the path as a directory. */
635  if (path_info->dir_add)
636    {
637      return cb_baton->editor->add_directory(path, parent_baton, NULL,
638                                             SVN_INVALID_REVNUM, pool,
639                                             dir_baton);
640    }
641
642  /* If this is a resurrection, we know the source and dest paths are
643     the same, and that our driver will only be calling us once.  */
644  if (path_info->resurrection)
645    {
646      /* If this is a move, we do nothing.  Otherwise, we do the copy.  */
647      if (! cb_baton->is_move)
648        do_add = TRUE;
649    }
650  /* Not a resurrection. */
651  else
652    {
653      /* If this is a move, we check PATH to see if it is the source
654         or the destination of the move. */
655      if (cb_baton->is_move)
656        {
657          if (strcmp(path_info->src_path, path) == 0)
658            do_delete = TRUE;
659          else
660            do_add = TRUE;
661        }
662      /* Not a move?  This must just be the copy addition. */
663      else
664        {
665          do_add = TRUE;
666        }
667    }
668
669  if (do_delete)
670    {
671      SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
672                                             parent_baton, pool));
673    }
674  if (do_add)
675    {
676      SVN_ERR(svn_path_check_valid(path, pool));
677
678      if (path_info->src_kind == svn_node_file)
679        {
680          void *file_baton;
681          SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
682                                             path_info->src_url,
683                                             path_info->src_revnum,
684                                             pool, &file_baton));
685          if (path_info->mergeinfo)
686            SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
687                                                       SVN_PROP_MERGEINFO,
688                                                       path_info->mergeinfo,
689                                                       pool));
690          SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
691        }
692      else
693        {
694          SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
695                                                  path_info->src_url,
696                                                  path_info->src_revnum,
697                                                  pool, dir_baton));
698          if (path_info->mergeinfo)
699            SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
700                                                      SVN_PROP_MERGEINFO,
701                                                      path_info->mergeinfo,
702                                                      pool));
703        }
704    }
705  return SVN_NO_ERROR;
706}
707
708
709/* Starting with the path DIR relative to the RA_SESSION's session
710   URL, work up through DIR's parents until an existing node is found.
711   Push each nonexistent path onto the array NEW_DIRS, allocating in
712   POOL.  Raise an error if the existing node is not a directory.
713
714   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
715   ### implementation susceptible to race conditions.  */
716static svn_error_t *
717find_absent_parents1(svn_ra_session_t *ra_session,
718                     const char *dir,
719                     apr_array_header_t *new_dirs,
720                     apr_pool_t *pool)
721{
722  svn_node_kind_t kind;
723  apr_pool_t *iterpool = svn_pool_create(pool);
724
725  SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
726                            iterpool));
727
728  while (kind == svn_node_none)
729    {
730      svn_pool_clear(iterpool);
731
732      APR_ARRAY_PUSH(new_dirs, const char *) = dir;
733      dir = svn_dirent_dirname(dir, pool);
734
735      SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
736                                &kind, iterpool));
737    }
738
739  if (kind != svn_node_dir)
740    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
741                             _("Path '%s' already exists, but is not a "
742                               "directory"), dir);
743
744  svn_pool_destroy(iterpool);
745  return SVN_NO_ERROR;
746}
747
748/* Starting with the URL *TOP_DST_URL which is also the root of
749   RA_SESSION, work up through its parents until an existing node is
750   found. Push each nonexistent URL onto the array NEW_DIRS,
751   allocating in POOL.  Raise an error if the existing node is not a
752   directory.
753
754   Set *TOP_DST_URL and the RA session's root to the existing node's URL.
755
756   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
757   ### implementation susceptible to race conditions.  */
758static svn_error_t *
759find_absent_parents2(svn_ra_session_t *ra_session,
760                     const char **top_dst_url,
761                     apr_array_header_t *new_dirs,
762                     apr_pool_t *pool)
763{
764  const char *root_url = *top_dst_url;
765  svn_node_kind_t kind;
766
767  SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
768                            pool));
769
770  while (kind == svn_node_none)
771    {
772      APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
773      root_url = svn_uri_dirname(root_url, pool);
774
775      SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
776      SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
777                                pool));
778    }
779
780  if (kind != svn_node_dir)
781    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
782                _("Path '%s' already exists, but is not a directory"),
783                root_url);
784
785  *top_dst_url = root_url;
786  return SVN_NO_ERROR;
787}
788
789static svn_error_t *
790repos_to_repos_copy(const apr_array_header_t *copy_pairs,
791                    svn_boolean_t make_parents,
792                    const apr_hash_t *revprop_table,
793                    svn_commit_callback2_t commit_callback,
794                    void *commit_baton,
795                    svn_client_ctx_t *ctx,
796                    svn_boolean_t is_move,
797                    apr_pool_t *pool)
798{
799  svn_error_t *err;
800  apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
801                                             sizeof(const char *));
802  apr_hash_t *action_hash = apr_hash_make(pool);
803  apr_array_header_t *path_infos;
804  const char *top_url, *top_url_all, *top_url_dst;
805  const char *message, *repos_root;
806  svn_ra_session_t *ra_session = NULL;
807  const svn_delta_editor_t *editor;
808  void *edit_baton;
809  struct path_driver_cb_baton cb_baton;
810  apr_array_header_t *new_dirs = NULL;
811  apr_hash_t *commit_revprops;
812  int i;
813  svn_client__copy_pair_t *first_pair =
814    APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
815
816  /* Open an RA session to the first copy pair's destination.  We'll
817     be verifying that every one of our copy source and destination
818     URLs is or is beneath this sucker's repository root URL as a form
819     of a cheap(ish) sanity check.  */
820  SVN_ERR(svn_client_open_ra_session2(&ra_session,
821                                      first_pair->src_abspath_or_url, NULL,
822                                      ctx, pool, pool));
823  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
824
825  /* Verify that sources and destinations are all at or under
826     REPOS_ROOT.  While here, create a path_info struct for each
827     src/dst pair and initialize portions of it with normalized source
828     location information.  */
829  path_infos = apr_array_make(pool, copy_pairs->nelts,
830                              sizeof(path_driver_info_t *));
831  for (i = 0; i < copy_pairs->nelts; i++)
832    {
833      path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
834      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
835                                                    svn_client__copy_pair_t *);
836      apr_hash_t *mergeinfo;
837
838      /* Are the source and destination URLs at or under REPOS_ROOT? */
839      if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
840             && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
841        return svn_error_create
842          (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
843           _("Source and destination URLs appear not to point to the "
844             "same repository."));
845
846      /* Run the history function to get the source's URL and revnum in the
847         operational revision. */
848      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
849      SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
850                                          &pair->src_revnum,
851                                          NULL, NULL,
852                                          ra_session,
853                                          pair->src_abspath_or_url,
854                                          &pair->src_peg_revision,
855                                          &pair->src_op_revision, NULL,
856                                          ctx, pool));
857
858      /* Go ahead and grab mergeinfo from the source, too. */
859      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
860      SVN_ERR(svn_client__get_repos_mergeinfo(
861                &mergeinfo, ra_session,
862                pair->src_abspath_or_url, pair->src_revnum,
863                svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
864      if (mergeinfo)
865        SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
866
867      /* Plop an INFO structure onto our array thereof. */
868      info->src_url = pair->src_abspath_or_url;
869      info->src_revnum = pair->src_revnum;
870      info->resurrection = FALSE;
871      APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
872    }
873
874  /* If this is a move, we have to open our session to the longest
875     path common to all SRC_URLS and DST_URLS in the repository so we
876     can do existence checks on all paths, and so we can operate on
877     all paths in the case of a move.  But if this is *not* a move,
878     then opening our session at the longest path common to sources
879     *and* destinations might be an optimization when the user is
880     authorized to access all that stuff, but could cause the
881     operation to fail altogether otherwise.  See issue #3242.  */
882  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
883                                  pool));
884  top_url = is_move ? top_url_all : top_url_dst;
885
886  /* Check each src/dst pair for resurrection, and verify that TOP_URL
887     is anchored high enough to cover all the editor_t activities
888     required for this operation.  */
889  for (i = 0; i < copy_pairs->nelts; i++)
890    {
891      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
892                                                    svn_client__copy_pair_t *);
893      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
894                                               path_driver_info_t *);
895
896      /* Source and destination are the same?  It's a resurrection. */
897      if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
898        info->resurrection = TRUE;
899
900      /* We need to add each dst_URL, and (in a move) we'll need to
901         delete each src_URL.  Our selection of TOP_URL so far ensures
902         that all our destination URLs (and source URLs, for moves)
903         are at least as deep as TOP_URL, but we need to make sure
904         that TOP_URL is an *ancestor* of all our to-be-edited paths.
905
906         Issue #683 is demonstrates this scenario.  If you're
907         resurrecting a deleted item like this: 'svn cp -rN src_URL
908         dst_URL', then src_URL == dst_URL == top_url.  In this
909         situation, we want to open an RA session to be at least the
910         *parent* of all three. */
911      if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
912          && (strcmp(top_url, repos_root) != 0))
913        {
914          top_url = svn_uri_dirname(top_url, pool);
915        }
916      if (is_move
917          && (strcmp(top_url, pair->src_abspath_or_url) == 0)
918          && (strcmp(top_url, repos_root) != 0))
919        {
920          top_url = svn_uri_dirname(top_url, pool);
921        }
922    }
923
924  /* Point the RA session to our current TOP_URL. */
925  SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
926
927  /* If we're allowed to create nonexistent parent directories of our
928     destinations, then make a list in NEW_DIRS of the parent
929     directories of the destination that don't yet exist.  */
930  if (make_parents)
931    {
932      new_dirs = apr_array_make(pool, 0, sizeof(const char *));
933
934      /* If this is a move, TOP_URL is at least the common ancestor of
935         all the paths (sources and destinations) involved.  Assuming
936         the sources exist (which is fair, because if they don't, this
937         whole operation will fail anyway), TOP_URL must also exist.
938         So it's the paths between TOP_URL and the destinations which
939         we have to check for existence.  But here, we take advantage
940         of the knowledge of our caller.  We know that if there are
941         multiple copy/move operations being requested, then the
942         destinations of the copies/moves will all be siblings of one
943         another.  Therefore, we need only to check for the
944         nonexistent paths between TOP_URL and *one* of our
945         destinations to find nonexistent parents of all of them.  */
946      if (is_move)
947        {
948          /* Imagine a situation where the user tries to copy an
949             existing source directory to nonexistent directory with
950             --parents options specified:
951
952                svn copy --parents URL/src URL/dst
953
954             where src exists and dst does not.  If the dirname of the
955             destination path is equal to TOP_URL,
956             do not try to add dst to the NEW_DIRS list since it
957             will be added to the commit items array later in this
958             function. */
959          const char *dir = svn_uri_skip_ancestor(
960                              top_url,
961                              svn_uri_dirname(first_pair->dst_abspath_or_url,
962                                              pool),
963                              pool);
964          if (dir && *dir)
965            SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
966        }
967      /* If, however, this is *not* a move, TOP_URL only points to the
968         common ancestor of our destination path(s), or possibly one
969         level higher.  We'll need to do an existence crawl toward the
970         root of the repository, starting with one of our destinations
971         (see "... take advantage of the knowledge of our caller ..."
972         above), and possibly adjusting TOP_URL as we go. */
973      else
974        {
975          apr_array_header_t *new_urls =
976            apr_array_make(pool, 0, sizeof(const char *));
977          SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
978
979          /* Convert absolute URLs into relpaths relative to TOP_URL. */
980          for (i = 0; i < new_urls->nelts; i++)
981            {
982              const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
983              const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
984
985              APR_ARRAY_PUSH(new_dirs, const char *) = dir;
986            }
987        }
988    }
989
990  /* For each src/dst pair, check to see if that SRC_URL is a child of
991     the DST_URL (excepting the case where DST_URL is the repo root).
992     If it is, and the parent of DST_URL is the current TOP_URL, then we
993     need to reparent the session one directory higher, the parent of
994     the DST_URL. */
995  for (i = 0; i < copy_pairs->nelts; i++)
996    {
997      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
998                                                    svn_client__copy_pair_t *);
999      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1000                                               path_driver_info_t *);
1001      const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1002                                                  pair->src_abspath_or_url,
1003                                                  pool);
1004
1005      if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1006          && (relpath != NULL && *relpath != '\0'))
1007        {
1008          info->resurrection = TRUE;
1009          top_url = svn_uri_get_longest_ancestor(
1010                            top_url,
1011                            svn_uri_dirname(pair->dst_abspath_or_url, pool),
1012                            pool);
1013          SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1014        }
1015    }
1016
1017  /* Get the portions of the SRC and DST URLs that are relative to
1018     TOP_URL (URI-decoding them while we're at it), verify that the
1019     source exists and the proposed destination does not, and toss
1020     what we've learned into the INFO array.  (For copies -- that is,
1021     non-moves -- the relative source URL NULL because it isn't a
1022     child of the TOP_URL at all.  That's okay, we'll deal with
1023     it.)  */
1024  for (i = 0; i < copy_pairs->nelts; i++)
1025    {
1026      svn_client__copy_pair_t *pair =
1027        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1028      path_driver_info_t *info =
1029        APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1030      svn_node_kind_t dst_kind;
1031      const char *src_rel, *dst_rel;
1032
1033      src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1034      if (src_rel)
1035        {
1036          SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1037                                    &info->src_kind, pool));
1038        }
1039      else
1040        {
1041          const char *old_url;
1042
1043          src_rel = NULL;
1044          SVN_ERR_ASSERT(! is_move);
1045
1046          SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1047                                                    pair->src_abspath_or_url,
1048                                                    pool));
1049          SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1050                                    &info->src_kind, pool));
1051          SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1052        }
1053      if (info->src_kind == svn_node_none)
1054        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1055                                 _("Path '%s' does not exist in revision %ld"),
1056                                 pair->src_abspath_or_url, pair->src_revnum);
1057
1058      /* Figure out the basename that will result from this operation,
1059         and ensure that we aren't trying to overwrite existing paths.  */
1060      dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1061      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1062                                &dst_kind, pool));
1063      if (dst_kind != svn_node_none)
1064        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1065                                 _("Path '%s' already exists"), dst_rel);
1066
1067      /* More info for our INFO structure.  */
1068      info->src_path = src_rel;
1069      info->dst_path = dst_rel;
1070
1071      svn_hash_sets(action_hash, info->dst_path, info);
1072      if (is_move && (! info->resurrection))
1073        svn_hash_sets(action_hash, info->src_path, info);
1074    }
1075
1076  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1077    {
1078      /* Produce a list of new paths to add, and provide it to the
1079         mechanism used to acquire a log message. */
1080      svn_client_commit_item3_t *item;
1081      const char *tmp_file;
1082      apr_array_header_t *commit_items
1083        = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1084
1085      /* Add any intermediate directories to the message */
1086      if (make_parents)
1087        {
1088          for (i = 0; i < new_dirs->nelts; i++)
1089            {
1090              const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1091
1092              item = svn_client_commit_item3_create(pool);
1093              item->url = svn_path_url_add_component2(top_url, relpath, pool);
1094              item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1095              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1096            }
1097        }
1098
1099      for (i = 0; i < path_infos->nelts; i++)
1100        {
1101          path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1102                                                   path_driver_info_t *);
1103
1104          item = svn_client_commit_item3_create(pool);
1105          item->url = svn_path_url_add_component2(top_url, info->dst_path,
1106                                                  pool);
1107          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1108          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1109
1110          if (is_move && (! info->resurrection))
1111            {
1112              item = apr_pcalloc(pool, sizeof(*item));
1113              item->url = svn_path_url_add_component2(top_url, info->src_path,
1114                                                      pool);
1115              item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1116              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1117            }
1118        }
1119
1120      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1121                                      ctx, pool));
1122      if (! message)
1123        return SVN_NO_ERROR;
1124    }
1125  else
1126    message = "";
1127
1128  /* Setup our PATHS for the path-based editor drive. */
1129  /* First any intermediate directories. */
1130  if (make_parents)
1131    {
1132      for (i = 0; i < new_dirs->nelts; i++)
1133        {
1134          const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1135          path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1136
1137          info->dst_path = relpath;
1138          info->dir_add = TRUE;
1139
1140          APR_ARRAY_PUSH(paths, const char *) = relpath;
1141          svn_hash_sets(action_hash, relpath, info);
1142        }
1143    }
1144
1145  /* Then our copy destinations and move sources (if any). */
1146  for (i = 0; i < path_infos->nelts; i++)
1147    {
1148      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1149                                               path_driver_info_t *);
1150
1151      APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1152      if (is_move && (! info->resurrection))
1153        APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1154    }
1155
1156  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1157                                           message, ctx, pool));
1158
1159  /* Fetch RA commit editor. */
1160  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1161                        svn_client__get_shim_callbacks(ctx->wc_ctx,
1162                                                       NULL, pool)));
1163  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1164                                    commit_revprops,
1165                                    commit_callback,
1166                                    commit_baton,
1167                                    NULL, TRUE, /* No lock tokens */
1168                                    pool));
1169
1170  /* Setup the callback baton. */
1171  cb_baton.editor = editor;
1172  cb_baton.edit_baton = edit_baton;
1173  cb_baton.action_hash = action_hash;
1174  cb_baton.is_move = is_move;
1175
1176  /* Call the path-based editor driver. */
1177  err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1178                               path_driver_cb_func, &cb_baton, pool);
1179  if (err)
1180    {
1181      /* At least try to abort the edit (and fs txn) before throwing err. */
1182      return svn_error_compose_create(
1183                    err,
1184                    editor->abort_edit(edit_baton, pool));
1185    }
1186
1187  /* Close the edit. */
1188  return svn_error_trace(editor->close_edit(edit_baton, pool));
1189}
1190
1191/* Baton for check_url_kind */
1192struct check_url_kind_baton
1193{
1194  svn_ra_session_t *session;
1195  const char *repos_root_url;
1196  svn_boolean_t should_reparent;
1197};
1198
1199/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1200static svn_error_t *
1201check_url_kind(void *baton,
1202               svn_node_kind_t *kind,
1203               const char *url,
1204               svn_revnum_t revision,
1205               apr_pool_t *scratch_pool)
1206{
1207  struct check_url_kind_baton *cukb = baton;
1208
1209  /* If we don't have a session or can't use the session, get one */
1210  if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1211    *kind = svn_node_none;
1212  else
1213    {
1214      cukb->should_reparent = TRUE;
1215
1216      SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1217
1218      SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1219                                kind, scratch_pool));
1220    }
1221
1222  return SVN_NO_ERROR;
1223}
1224
1225/* ### Copy ...
1226 * COMMIT_INFO_P is ...
1227 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1228 * and each 'dst_abspath_or_url' is a URL.
1229 * MAKE_PARENTS is ...
1230 * REVPROP_TABLE is ...
1231 * CTX is ... */
1232static svn_error_t *
1233wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1234                 svn_boolean_t make_parents,
1235                 const apr_hash_t *revprop_table,
1236                 svn_commit_callback2_t commit_callback,
1237                 void *commit_baton,
1238                 svn_client_ctx_t *ctx,
1239                 apr_pool_t *scratch_pool)
1240{
1241  const char *message;
1242  const char *top_src_path, *top_dst_url;
1243  struct check_url_kind_baton cukb;
1244  const char *top_src_abspath;
1245  svn_ra_session_t *ra_session;
1246  const svn_delta_editor_t *editor;
1247  apr_hash_t *relpath_map = NULL;
1248  void *edit_baton;
1249  svn_client__committables_t *committables;
1250  apr_array_header_t *commit_items;
1251  apr_pool_t *iterpool;
1252  apr_array_header_t *new_dirs = NULL;
1253  apr_hash_t *commit_revprops;
1254  svn_client__copy_pair_t *first_pair;
1255  apr_pool_t *session_pool = svn_pool_create(scratch_pool);
1256  int i;
1257
1258  /* Find the common root of all the source paths */
1259  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
1260                                  scratch_pool));
1261
1262  /* Do we need to lock the working copy?  1.6 didn't take a write
1263     lock, but what happens if the working copy changes during the copy
1264     operation? */
1265
1266  iterpool = svn_pool_create(scratch_pool);
1267
1268  /* Determine the longest common ancestor for the destinations, and open an RA
1269     session to that location. */
1270  /* ### But why start by getting the _parent_ of the first one? */
1271  /* --- That works because multiple destinations always point to the same
1272   *     directory. I'm rather wondering why we need to find a common
1273   *     destination parent here at all, instead of simply getting
1274   *     top_dst_url from get_copy_pair_ancestors() above?
1275   *     It looks like the entire block of code hanging off this comment
1276   *     is redundant. */
1277  first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1278  top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
1279  for (i = 1; i < copy_pairs->nelts; i++)
1280    {
1281      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1282                                                    svn_client__copy_pair_t *);
1283      top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
1284                                                 pair->dst_abspath_or_url,
1285                                                 scratch_pool);
1286    }
1287
1288  SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
1289
1290  /* Open a session to help while determining the exact targets */
1291  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1292                                               top_src_abspath, NULL,
1293                                               FALSE /* write_dav_props */,
1294                                               TRUE /* read_dav_props */,
1295                                               ctx,
1296                                               session_pool, session_pool));
1297
1298  /* If requested, determine the nearest existing parent of the destination,
1299     and reparent the ra session there. */
1300  if (make_parents)
1301    {
1302      new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
1303      SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
1304                                   scratch_pool));
1305    }
1306
1307  /* Figure out the basename that will result from each copy and check to make
1308     sure it doesn't exist already. */
1309  for (i = 0; i < copy_pairs->nelts; i++)
1310    {
1311      svn_node_kind_t dst_kind;
1312      const char *dst_rel;
1313      svn_client__copy_pair_t *pair =
1314        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1315
1316      svn_pool_clear(iterpool);
1317      dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
1318                                      iterpool);
1319      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1320                                &dst_kind, iterpool));
1321      if (dst_kind != svn_node_none)
1322        {
1323          return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1324                                   _("Path '%s' already exists"),
1325                                   pair->dst_abspath_or_url);
1326        }
1327    }
1328
1329  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1330    {
1331      /* Produce a list of new paths to add, and provide it to the
1332         mechanism used to acquire a log message. */
1333      svn_client_commit_item3_t *item;
1334      const char *tmp_file;
1335      commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
1336                                    sizeof(item));
1337
1338      /* Add any intermediate directories to the message */
1339      if (make_parents)
1340        {
1341          for (i = 0; i < new_dirs->nelts; i++)
1342            {
1343              const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1344
1345              item = svn_client_commit_item3_create(scratch_pool);
1346              item->url = url;
1347              item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1348              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1349            }
1350        }
1351
1352      for (i = 0; i < copy_pairs->nelts; i++)
1353        {
1354          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1355                                            svn_client__copy_pair_t *);
1356
1357          item = svn_client_commit_item3_create(scratch_pool);
1358          item->url = pair->dst_abspath_or_url;
1359          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1360          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1361        }
1362
1363      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1364                                      ctx, scratch_pool));
1365      if (! message)
1366        {
1367          svn_pool_destroy(iterpool);
1368          svn_pool_destroy(session_pool);
1369          return SVN_NO_ERROR;
1370        }
1371    }
1372  else
1373    message = "";
1374
1375  cukb.session = ra_session;
1376  SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
1377  cukb.should_reparent = FALSE;
1378
1379  /* Crawl the working copy for commit items. */
1380  /* ### TODO: Pass check_url_func for issue #3314 handling */
1381  SVN_ERR(svn_client__get_copy_committables(&committables,
1382                                            copy_pairs,
1383                                            check_url_kind, &cukb,
1384                                            ctx, scratch_pool, iterpool));
1385
1386  /* The committables are keyed by the repository root */
1387  commit_items = svn_hash_gets(committables->by_repository,
1388                               cukb.repos_root_url);
1389  SVN_ERR_ASSERT(commit_items != NULL);
1390
1391  if (cukb.should_reparent)
1392    SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
1393
1394  /* If we are creating intermediate directories, tack them onto the list
1395     of committables. */
1396  if (make_parents)
1397    {
1398      for (i = 0; i < new_dirs->nelts; i++)
1399        {
1400          const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1401          svn_client_commit_item3_t *item;
1402
1403          item = svn_client_commit_item3_create(scratch_pool);
1404          item->url = url;
1405          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1406          item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
1407                                                       sizeof(svn_prop_t *));
1408          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1409        }
1410    }
1411
1412  /* ### TODO: This extra loop would be unnecessary if this code lived
1413     ### in svn_client__get_copy_committables(), which is incidentally
1414     ### only used above (so should really be in this source file). */
1415  for (i = 0; i < copy_pairs->nelts; i++)
1416    {
1417      apr_hash_t *mergeinfo, *wc_mergeinfo;
1418      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1419                                                    svn_client__copy_pair_t *);
1420      svn_client_commit_item3_t *item =
1421        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1422      svn_client__pathrev_t *src_origin;
1423
1424      svn_pool_clear(iterpool);
1425
1426      SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
1427                                             pair->src_abspath_or_url,
1428                                             ctx, iterpool, iterpool));
1429
1430      /* Set the mergeinfo for the destination to the combined merge
1431         info known to the WC and the repository. */
1432      item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
1433                                                   sizeof(svn_prop_t *));
1434      /* Repository mergeinfo (or NULL if it's locally added)... */
1435      if (src_origin)
1436        SVN_ERR(svn_client__get_repos_mergeinfo(
1437                  &mergeinfo, ra_session, src_origin->url, src_origin->rev,
1438                  svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
1439      else
1440        mergeinfo = NULL;
1441      /* ... and WC mergeinfo. */
1442      SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
1443                                          pair->src_abspath_or_url,
1444                                          iterpool, iterpool));
1445      if (wc_mergeinfo && mergeinfo)
1446        SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
1447                                     iterpool));
1448      else if (! mergeinfo)
1449        mergeinfo = wc_mergeinfo;
1450      if (mergeinfo)
1451        {
1452          /* Push a mergeinfo prop representing MERGEINFO onto the
1453           * OUTGOING_PROP_CHANGES array. */
1454
1455          svn_prop_t *mergeinfo_prop
1456            = apr_palloc(item->outgoing_prop_changes->pool,
1457                         sizeof(svn_prop_t));
1458          svn_string_t *prop_value;
1459
1460          SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
1461                                          item->outgoing_prop_changes->pool));
1462
1463          mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1464          mergeinfo_prop->value = prop_value;
1465          APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
1466            = mergeinfo_prop;
1467        }
1468    }
1469
1470  /* Sort and condense our COMMIT_ITEMS. */
1471  SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1472                                            commit_items, scratch_pool));
1473
1474#ifdef ENABLE_EV2_SHIMS
1475  if (commit_items)
1476    {
1477      relpath_map = apr_hash_make(pool);
1478      for (i = 0; i < commit_items->nelts; i++)
1479        {
1480          svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
1481                                                  svn_client_commit_item3_t *);
1482          const char *relpath;
1483
1484          if (!item->path)
1485            continue;
1486
1487          svn_pool_clear(iterpool);
1488          SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
1489                                          ctx->wc_ctx, item->path, FALSE,
1490                                          scratch_pool, iterpool));
1491          if (relpath)
1492            svn_hash_sets(relpath_map, relpath, item->path);
1493        }
1494    }
1495#endif
1496
1497  /* Close the initial session, to reopen a new session with commit handling */
1498  svn_pool_clear(session_pool);
1499
1500  /* Open a new RA session to DST_URL. */
1501  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1502                                               NULL, commit_items,
1503                                               FALSE, FALSE, ctx,
1504                                               session_pool, session_pool));
1505
1506  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1507                                           message, ctx, session_pool));
1508
1509  /* Fetch RA commit editor. */
1510  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1511                        svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
1512                                                       session_pool)));
1513  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1514                                    commit_revprops,
1515                                    commit_callback,
1516                                    commit_baton, NULL,
1517                                    TRUE, /* No lock tokens */
1518                                    session_pool));
1519
1520  /* Perform the commit. */
1521  SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
1522                                  editor, edit_baton,
1523                                  0, /* ### any notify_path_offset needed? */
1524                                  NULL, ctx, session_pool, session_pool),
1525            _("Commit failed (details follow):"));
1526
1527  svn_pool_destroy(iterpool);
1528  svn_pool_destroy(session_pool);
1529
1530  return SVN_NO_ERROR;
1531}
1532
1533/* A baton for notification_adjust_func(). */
1534struct notification_adjust_baton
1535{
1536  svn_wc_notify_func2_t inner_func;
1537  void *inner_baton;
1538  const char *checkout_abspath;
1539  const char *final_abspath;
1540};
1541
1542/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
1543 * baton is BATON->inner_baton) and adjusts the notification paths that
1544 * start with BATON->checkout_abspath to start instead with
1545 * BATON->final_abspath. */
1546static void
1547notification_adjust_func(void *baton,
1548                         const svn_wc_notify_t *notify,
1549                         apr_pool_t *pool)
1550{
1551  struct notification_adjust_baton *nb = baton;
1552  svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
1553  const char *relpath;
1554
1555  relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
1556  inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
1557
1558  if (nb->inner_func)
1559    nb->inner_func(nb->inner_baton, inner_notify, pool);
1560}
1561
1562/* Peform each individual copy operation for a repos -> wc copy.  A
1563   helper for repos_to_wc_copy().
1564
1565   Resolve PAIR->src_revnum to a real revision number if it isn't already. */
1566static svn_error_t *
1567repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
1568                        svn_client__copy_pair_t *pair,
1569                        svn_boolean_t same_repositories,
1570                        svn_boolean_t ignore_externals,
1571                        svn_ra_session_t *ra_session,
1572                        svn_client_ctx_t *ctx,
1573                        apr_pool_t *pool)
1574{
1575  apr_hash_t *src_mergeinfo;
1576  const char *dst_abspath = pair->dst_abspath_or_url;
1577
1578  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
1579
1580  if (!same_repositories && ctx->notify_func2)
1581    {
1582      svn_wc_notify_t *notify;
1583      notify = svn_wc_create_notify_url(
1584                            pair->src_abspath_or_url,
1585                            svn_wc_notify_foreign_copy_begin,
1586                            pool);
1587      notify->kind = pair->src_kind;
1588      ctx->notify_func2(ctx->notify_baton2, notify, pool);
1589
1590      /* Allow a theoretical cancel to get through. */
1591      if (ctx->cancel_func)
1592        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1593    }
1594
1595  if (pair->src_kind == svn_node_dir)
1596    {
1597      if (same_repositories)
1598        {
1599          svn_boolean_t sleep_needed = FALSE;
1600          const char *tmpdir_abspath, *tmp_abspath;
1601
1602          /* Find a temporary location in which to check out the copy source. */
1603          SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
1604                                     pool, pool));
1605
1606          SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
1607                                           svn_io_file_del_on_close, pool, pool));
1608
1609          /* Make a new checkout of the requested source. While doing so,
1610           * resolve pair->src_revnum to an actual revision number in case it
1611           * was until now 'invalid' meaning 'head'.  Ask this function not to
1612           * sleep for timestamps, by passing a sleep_needed output param.
1613           * Send notifications for all nodes except the root node, and adjust
1614           * them to refer to the destination rather than this temporary path. */
1615          {
1616            svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
1617            void *old_notify_baton2 = ctx->notify_baton2;
1618            struct notification_adjust_baton nb;
1619            svn_error_t *err;
1620
1621            nb.inner_func = ctx->notify_func2;
1622            nb.inner_baton = ctx->notify_baton2;
1623            nb.checkout_abspath = tmp_abspath;
1624            nb.final_abspath = dst_abspath;
1625            ctx->notify_func2 = notification_adjust_func;
1626            ctx->notify_baton2 = &nb;
1627
1628            err = svn_client__checkout_internal(&pair->src_revnum,
1629                                                pair->src_original,
1630                                                tmp_abspath,
1631                                                &pair->src_peg_revision,
1632                                                &pair->src_op_revision,
1633                                                svn_depth_infinity,
1634                                                ignore_externals, FALSE,
1635                                                &sleep_needed, ctx, pool);
1636
1637            ctx->notify_func2 = old_notify_func2;
1638            ctx->notify_baton2 = old_notify_baton2;
1639
1640            SVN_ERR(err);
1641          }
1642
1643          *timestamp_sleep = TRUE;
1644
1645      /* Schedule dst_path for addition in parent, with copy history.
1646         Don't send any notification here.
1647         Then remove the temporary checkout's .svn dir in preparation for
1648         moving the rest of it into the final destination. */
1649          SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
1650                               TRUE /* metadata_only */,
1651                               ctx->cancel_func, ctx->cancel_baton,
1652                               NULL, NULL, pool));
1653          SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
1654                                             FALSE, pool, pool));
1655          SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
1656                                                       tmp_abspath,
1657                                                       FALSE, FALSE,
1658                                                       ctx->cancel_func,
1659                                                       ctx->cancel_baton,
1660                                                       pool));
1661
1662          /* Move the temporary disk tree into place. */
1663          SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
1664        }
1665      else
1666        {
1667          *timestamp_sleep = TRUE;
1668
1669          SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
1670                                           dst_abspath,
1671                                           &pair->src_peg_revision,
1672                                           &pair->src_op_revision,
1673                                           svn_depth_infinity,
1674                                           FALSE /* make_parents */,
1675                                           TRUE /* already_locked */,
1676                                           ctx, pool));
1677
1678          return SVN_NO_ERROR;
1679        }
1680    } /* end directory case */
1681
1682  else if (pair->src_kind == svn_node_file)
1683    {
1684      apr_hash_t *new_props;
1685      const char *src_rel;
1686      svn_stream_t *new_base_contents = svn_stream_buffered(pool);
1687
1688      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1689                                                  pair->src_abspath_or_url,
1690                                                  pool));
1691      /* Fetch the file content. While doing so, resolve pair->src_revnum
1692       * to an actual revision number if it's 'invalid' meaning 'head'. */
1693      SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
1694                              new_base_contents,
1695                              &pair->src_revnum, &new_props, pool));
1696
1697      if (new_props && ! same_repositories)
1698        svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
1699
1700      *timestamp_sleep = TRUE;
1701
1702      SVN_ERR(svn_wc_add_repos_file4(
1703         ctx->wc_ctx, dst_abspath,
1704         new_base_contents, NULL, new_props, NULL,
1705         same_repositories ? pair->src_abspath_or_url : NULL,
1706         same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
1707         ctx->cancel_func, ctx->cancel_baton,
1708         pool));
1709    }
1710
1711  /* Record the implied mergeinfo (before the notification callback
1712     is invoked for the root node). */
1713  SVN_ERR(svn_client__get_repos_mergeinfo(
1714            &src_mergeinfo, ra_session,
1715            pair->src_abspath_or_url, pair->src_revnum,
1716            svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1717  SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
1718
1719  /* Do our own notification for the root node, even if we could possibly
1720     have delegated it.  See also issue #1552.
1721
1722     ### Maybe this notification should mention the mergeinfo change. */
1723  if (ctx->notify_func2)
1724    {
1725      svn_wc_notify_t *notify = svn_wc_create_notify(
1726                                  dst_abspath, svn_wc_notify_add, pool);
1727      notify->kind = pair->src_kind;
1728      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1729    }
1730
1731  return SVN_NO_ERROR;
1732}
1733
1734static svn_error_t *
1735repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
1736                        const apr_array_header_t *copy_pairs,
1737                        const char *top_dst_path,
1738                        svn_boolean_t ignore_externals,
1739                        svn_ra_session_t *ra_session,
1740                        svn_client_ctx_t *ctx,
1741                        apr_pool_t *scratch_pool)
1742{
1743  int i;
1744  svn_boolean_t same_repositories;
1745  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1746
1747  /* We've already checked for physical obstruction by a working file.
1748     But there could also be logical obstruction by an entry whose
1749     working file happens to be missing.*/
1750  SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
1751                         ctx, scratch_pool, iterpool));
1752
1753  /* Decide whether the two repositories are the same or not. */
1754  {
1755    svn_error_t *src_err, *dst_err;
1756    const char *parent;
1757    const char *parent_abspath;
1758    const char *src_uuid, *dst_uuid;
1759
1760    /* Get the repository uuid of SRC_URL */
1761    src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
1762    if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1763      return svn_error_trace(src_err);
1764
1765    /* Get repository uuid of dst's parent directory, since dst may
1766       not exist.  ### TODO:  we should probably walk up the wc here,
1767       in case the parent dir has an imaginary URL.  */
1768    if (copy_pairs->nelts == 1)
1769      parent = svn_dirent_dirname(top_dst_path, scratch_pool);
1770    else
1771      parent = top_dst_path;
1772
1773    SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
1774    dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
1775                                        parent_abspath, ctx,
1776                                        iterpool, iterpool);
1777    if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1778      return dst_err;
1779
1780    /* If either of the UUIDs are nonexistent, then at least one of
1781       the repositories must be very old.  Rather than punish the
1782       user, just assume the repositories are different, so no
1783       copy-history is attempted. */
1784    if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1785      same_repositories = FALSE;
1786    else
1787      same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
1788  }
1789
1790  /* Perform the move for each of the copy_pairs. */
1791  for (i = 0; i < copy_pairs->nelts; i++)
1792    {
1793      /* Check for cancellation */
1794      if (ctx->cancel_func)
1795        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1796
1797      svn_pool_clear(iterpool);
1798
1799      SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
1800                                      APR_ARRAY_IDX(copy_pairs, i,
1801                                                    svn_client__copy_pair_t *),
1802                                      same_repositories,
1803                                      ignore_externals,
1804                                      ra_session, ctx, iterpool));
1805    }
1806  svn_pool_destroy(iterpool);
1807
1808  return SVN_NO_ERROR;
1809}
1810
1811static svn_error_t *
1812repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
1813                 const apr_array_header_t *copy_pairs,
1814                 svn_boolean_t make_parents,
1815                 svn_boolean_t ignore_externals,
1816                 svn_client_ctx_t *ctx,
1817                 apr_pool_t *pool)
1818{
1819  svn_ra_session_t *ra_session;
1820  const char *top_src_url, *top_dst_path;
1821  apr_pool_t *iterpool = svn_pool_create(pool);
1822  const char *lock_abspath;
1823  int i;
1824
1825  /* Get the real path for the source, based upon its peg revision. */
1826  for (i = 0; i < copy_pairs->nelts; i++)
1827    {
1828      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1829                                                    svn_client__copy_pair_t *);
1830      const char *src;
1831
1832      svn_pool_clear(iterpool);
1833
1834      SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
1835                                          NULL,
1836                                          pair->src_abspath_or_url,
1837                                          &pair->src_peg_revision,
1838                                          &pair->src_op_revision, NULL,
1839                                          ctx, iterpool));
1840
1841      pair->src_original = pair->src_abspath_or_url;
1842      pair->src_abspath_or_url = apr_pstrdup(pool, src);
1843    }
1844
1845  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
1846                                  NULL, pool));
1847  lock_abspath = top_dst_path;
1848  if (copy_pairs->nelts == 1)
1849    {
1850      top_src_url = svn_uri_dirname(top_src_url, pool);
1851      lock_abspath = svn_dirent_dirname(top_dst_path, pool);
1852    }
1853
1854  /* Open a repository session to the longest common src ancestor.  We do not
1855     (yet) have a working copy, so we don't have a corresponding path and
1856     tempfiles cannot go into the admin area. */
1857  SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
1858                                      ctx, pool, pool));
1859
1860  /* Get the correct src path for the peg revision used, and verify that we
1861     aren't overwriting an existing path. */
1862  for (i = 0; i < copy_pairs->nelts; i++)
1863    {
1864      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1865                                                    svn_client__copy_pair_t *);
1866      svn_node_kind_t dst_parent_kind, dst_kind;
1867      const char *dst_parent;
1868      const char *src_rel;
1869
1870      svn_pool_clear(iterpool);
1871
1872      /* Next, make sure that the path exists in the repository. */
1873      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1874                                                  pair->src_abspath_or_url,
1875                                                  iterpool));
1876      SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1877                                &pair->src_kind, pool));
1878      if (pair->src_kind == svn_node_none)
1879        {
1880          if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1881            return svn_error_createf
1882              (SVN_ERR_FS_NOT_FOUND, NULL,
1883               _("Path '%s' not found in revision %ld"),
1884               pair->src_abspath_or_url, pair->src_revnum);
1885          else
1886            return svn_error_createf
1887              (SVN_ERR_FS_NOT_FOUND, NULL,
1888               _("Path '%s' not found in head revision"),
1889               pair->src_abspath_or_url);
1890        }
1891
1892      /* Figure out about dst. */
1893      SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1894                                iterpool));
1895      if (dst_kind != svn_node_none)
1896        {
1897          return svn_error_createf(
1898            SVN_ERR_ENTRY_EXISTS, NULL,
1899            _("Path '%s' already exists"),
1900            svn_dirent_local_style(pair->dst_abspath_or_url, pool));
1901        }
1902
1903      /* Make sure the destination parent is a directory and produce a clear
1904         error message if it is not. */
1905      dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
1906      SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1907      if (make_parents && dst_parent_kind == svn_node_none)
1908        {
1909          SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1910                                                 iterpool));
1911        }
1912      else if (dst_parent_kind != svn_node_dir)
1913        {
1914          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1915                                   _("Path '%s' is not a directory"),
1916                                   svn_dirent_local_style(dst_parent, pool));
1917        }
1918    }
1919  svn_pool_destroy(iterpool);
1920
1921  SVN_WC__CALL_WITH_WRITE_LOCK(
1922    repos_to_wc_copy_locked(timestamp_sleep,
1923                            copy_pairs, top_dst_path, ignore_externals,
1924                            ra_session, ctx, pool),
1925    ctx->wc_ctx, lock_abspath, FALSE, pool);
1926  return SVN_NO_ERROR;
1927}
1928
1929#define NEED_REPOS_REVNUM(revision) \
1930        ((revision.kind != svn_opt_revision_unspecified) \
1931          && (revision.kind != svn_opt_revision_working))
1932
1933/* ...
1934 *
1935 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
1936 * change *TIMESTAMP_SLEEP.  This output will be valid even if the
1937 * function returns an error.
1938 *
1939 * Perform all allocations in POOL.
1940 */
1941static svn_error_t *
1942try_copy(svn_boolean_t *timestamp_sleep,
1943         const apr_array_header_t *sources,
1944         const char *dst_path_in,
1945         svn_boolean_t is_move,
1946         svn_boolean_t allow_mixed_revisions,
1947         svn_boolean_t metadata_only,
1948         svn_boolean_t make_parents,
1949         svn_boolean_t ignore_externals,
1950         const apr_hash_t *revprop_table,
1951         svn_commit_callback2_t commit_callback,
1952         void *commit_baton,
1953         svn_client_ctx_t *ctx,
1954         apr_pool_t *pool)
1955{
1956  apr_array_header_t *copy_pairs =
1957                        apr_array_make(pool, sources->nelts,
1958                                       sizeof(svn_client__copy_pair_t *));
1959  svn_boolean_t srcs_are_urls, dst_is_url;
1960  int i;
1961
1962  /* Are either of our paths URLs?  Just check the first src_path.  If
1963     there are more than one, we'll check for homogeneity among them
1964     down below. */
1965  srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1966                                  svn_client_copy_source_t *)->path);
1967  dst_is_url = svn_path_is_url(dst_path_in);
1968  if (!dst_is_url)
1969    SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
1970
1971  /* If we have multiple source paths, it implies the dst_path is a
1972     directory we are moving or copying into.  Populate the COPY_PAIRS
1973     array to contain a destination path for each of the source paths. */
1974  if (sources->nelts > 1)
1975    {
1976      apr_pool_t *iterpool = svn_pool_create(pool);
1977
1978      for (i = 0; i < sources->nelts; i++)
1979        {
1980          svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1981                                               svn_client_copy_source_t *);
1982          svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1983          const char *src_basename;
1984          svn_boolean_t src_is_url = svn_path_is_url(source->path);
1985
1986          svn_pool_clear(iterpool);
1987
1988          if (src_is_url)
1989            {
1990              pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
1991              src_basename = svn_uri_basename(pair->src_abspath_or_url,
1992                                              iterpool);
1993            }
1994          else
1995            {
1996              SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
1997                                              source->path, pool));
1998              src_basename = svn_dirent_basename(pair->src_abspath_or_url,
1999                                                 iterpool);
2000            }
2001
2002          pair->src_op_revision = *source->revision;
2003          pair->src_peg_revision = *source->peg_revision;
2004
2005          SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2006                                            &pair->src_op_revision,
2007                                            src_is_url,
2008                                            TRUE,
2009                                            iterpool));
2010
2011          /* Check to see if all the sources are urls or all working copy
2012           * paths. */
2013          if (src_is_url != srcs_are_urls)
2014            return svn_error_create
2015              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2016               _("Cannot mix repository and working copy sources"));
2017
2018          if (dst_is_url)
2019            pair->dst_abspath_or_url =
2020              svn_path_url_add_component2(dst_path_in, src_basename, pool);
2021          else
2022            pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2023                                                       src_basename, pool);
2024          APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2025        }
2026
2027      svn_pool_destroy(iterpool);
2028    }
2029  else
2030    {
2031      /* Only one source path. */
2032      svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
2033      svn_client_copy_source_t *source =
2034        APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2035      svn_boolean_t src_is_url = svn_path_is_url(source->path);
2036
2037      if (src_is_url)
2038        pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2039      else
2040        SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2041                                        source->path, pool));
2042      pair->src_op_revision = *source->revision;
2043      pair->src_peg_revision = *source->peg_revision;
2044
2045      SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2046                                        &pair->src_op_revision,
2047                                        src_is_url, TRUE, pool));
2048
2049      pair->dst_abspath_or_url = dst_path_in;
2050      APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2051    }
2052
2053  if (!srcs_are_urls && !dst_is_url)
2054    {
2055      apr_pool_t *iterpool = svn_pool_create(pool);
2056
2057      for (i = 0; i < copy_pairs->nelts; i++)
2058        {
2059          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2060                                            svn_client__copy_pair_t *);
2061
2062          svn_pool_clear(iterpool);
2063
2064          if (svn_dirent_is_child(pair->src_abspath_or_url,
2065                                  pair->dst_abspath_or_url, iterpool))
2066            return svn_error_createf
2067              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2068               _("Cannot copy path '%s' into its own child '%s'"),
2069               svn_dirent_local_style(pair->src_abspath_or_url, pool),
2070               svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2071        }
2072
2073      svn_pool_destroy(iterpool);
2074    }
2075
2076  /* A file external should not be moved since the file external is
2077     implemented as a switched file and it would delete the file the
2078     file external is switched to, which is not the behavior the user
2079     would probably want. */
2080  if (is_move && !srcs_are_urls)
2081    {
2082      apr_pool_t *iterpool = svn_pool_create(pool);
2083
2084      for (i = 0; i < copy_pairs->nelts; i++)
2085        {
2086          svn_client__copy_pair_t *pair =
2087            APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2088          svn_node_kind_t external_kind;
2089          const char *defining_abspath;
2090
2091          svn_pool_clear(iterpool);
2092
2093          SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2094          SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2095                                             NULL, NULL, NULL, ctx->wc_ctx,
2096                                             pair->src_abspath_or_url,
2097                                             pair->src_abspath_or_url, TRUE,
2098                                             iterpool, iterpool));
2099
2100          if (external_kind != svn_node_none)
2101            return svn_error_createf(
2102                     SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2103                     NULL,
2104                     _("Cannot move the external at '%s'; please "
2105                       "edit the svn:externals property on '%s'."),
2106                     svn_dirent_local_style(pair->src_abspath_or_url, pool),
2107                     svn_dirent_local_style(defining_abspath, pool));
2108        }
2109      svn_pool_destroy(iterpool);
2110    }
2111
2112  if (is_move)
2113    {
2114      /* Disallow moves between the working copy and the repository. */
2115      if (srcs_are_urls != dst_is_url)
2116        {
2117          return svn_error_create
2118            (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2119             _("Moves between the working copy and the repository are not "
2120               "supported"));
2121        }
2122
2123      /* Disallow moving any path/URL onto or into itself. */
2124      for (i = 0; i < copy_pairs->nelts; i++)
2125        {
2126          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2127                                            svn_client__copy_pair_t *);
2128
2129          if (strcmp(pair->src_abspath_or_url,
2130                     pair->dst_abspath_or_url) == 0)
2131            return svn_error_createf(
2132              SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2133              srcs_are_urls ?
2134                _("Cannot move URL '%s' into itself") :
2135                _("Cannot move path '%s' into itself"),
2136              srcs_are_urls ?
2137                pair->src_abspath_or_url :
2138                svn_dirent_local_style(pair->src_abspath_or_url, pool));
2139        }
2140    }
2141  else
2142    {
2143      if (!srcs_are_urls)
2144        {
2145          /* If we are doing a wc->* copy, but with an operational revision
2146             other than the working copy revision, we are really doing a
2147             repo->* copy, because we're going to need to get the rev from the
2148             repo. */
2149
2150          svn_boolean_t need_repos_op_rev = FALSE;
2151          svn_boolean_t need_repos_peg_rev = FALSE;
2152
2153          /* Check to see if any revision is something other than
2154             svn_opt_revision_unspecified or svn_opt_revision_working. */
2155          for (i = 0; i < copy_pairs->nelts; i++)
2156            {
2157              svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2158                                                svn_client__copy_pair_t *);
2159
2160              if (NEED_REPOS_REVNUM(pair->src_op_revision))
2161                need_repos_op_rev = TRUE;
2162
2163              if (NEED_REPOS_REVNUM(pair->src_peg_revision))
2164                need_repos_peg_rev = TRUE;
2165
2166              if (need_repos_op_rev || need_repos_peg_rev)
2167                break;
2168            }
2169
2170          if (need_repos_op_rev || need_repos_peg_rev)
2171            {
2172              apr_pool_t *iterpool = svn_pool_create(pool);
2173
2174              for (i = 0; i < copy_pairs->nelts; i++)
2175                {
2176                  const char *copyfrom_repos_root_url;
2177                  const char *copyfrom_repos_relpath;
2178                  const char *url;
2179                  svn_revnum_t copyfrom_rev;
2180                  svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2181                                                    svn_client__copy_pair_t *);
2182
2183                  svn_pool_clear(iterpool);
2184
2185                  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2186
2187                  SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
2188                                                  &copyfrom_repos_relpath,
2189                                                  &copyfrom_repos_root_url,
2190                                                  NULL, NULL,
2191                                                  ctx->wc_ctx,
2192                                                  pair->src_abspath_or_url,
2193                                                  TRUE, iterpool, iterpool));
2194
2195                  if (copyfrom_repos_relpath)
2196                    url = svn_path_url_add_component2(copyfrom_repos_root_url,
2197                                                      copyfrom_repos_relpath,
2198                                                      pool);
2199                  else
2200                    return svn_error_createf
2201                      (SVN_ERR_ENTRY_MISSING_URL, NULL,
2202                       _("'%s' does not have a URL associated with it"),
2203                       svn_dirent_local_style(pair->src_abspath_or_url, pool));
2204
2205                  pair->src_abspath_or_url = url;
2206
2207                  if (!need_repos_peg_rev
2208                      || pair->src_peg_revision.kind == svn_opt_revision_base)
2209                    {
2210                      /* Default the peg revision to that of the WC entry. */
2211                      pair->src_peg_revision.kind = svn_opt_revision_number;
2212                      pair->src_peg_revision.value.number = copyfrom_rev;
2213                    }
2214
2215                  if (pair->src_op_revision.kind == svn_opt_revision_base)
2216                    {
2217                      /* Use the entry's revision as the operational rev. */
2218                      pair->src_op_revision.kind = svn_opt_revision_number;
2219                      pair->src_op_revision.value.number = copyfrom_rev;
2220                    }
2221                }
2222
2223              svn_pool_destroy(iterpool);
2224              srcs_are_urls = TRUE;
2225            }
2226        }
2227    }
2228
2229  /* Now, call the right handler for the operation. */
2230  if ((! srcs_are_urls) && (! dst_is_url))
2231    {
2232      SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
2233                                      metadata_only, ctx, pool, pool));
2234
2235      /* Copy or move all targets. */
2236      if (is_move)
2237        return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
2238                                                 copy_pairs, dst_path_in,
2239                                                 allow_mixed_revisions,
2240                                                 metadata_only,
2241                                                 ctx, pool));
2242      else
2243        {
2244          /* We ignore these values, so assert the default value */
2245          SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
2246          return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
2247                                                    copy_pairs, ctx, pool));
2248        }
2249    }
2250  else if ((! srcs_are_urls) && (dst_is_url))
2251    {
2252      return svn_error_trace(
2253        wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
2254                         commit_callback, commit_baton, ctx, pool));
2255    }
2256  else if ((srcs_are_urls) && (! dst_is_url))
2257    {
2258      return svn_error_trace(
2259        repos_to_wc_copy(timestamp_sleep,
2260                         copy_pairs, make_parents, ignore_externals,
2261                         ctx, pool));
2262    }
2263  else
2264    {
2265      return svn_error_trace(
2266        repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
2267                            commit_callback, commit_baton, ctx, is_move,
2268                            pool));
2269    }
2270}
2271
2272
2273
2274/* Public Interfaces */
2275svn_error_t *
2276svn_client_copy6(const apr_array_header_t *sources,
2277                 const char *dst_path,
2278                 svn_boolean_t copy_as_child,
2279                 svn_boolean_t make_parents,
2280                 svn_boolean_t ignore_externals,
2281                 const apr_hash_t *revprop_table,
2282                 svn_commit_callback2_t commit_callback,
2283                 void *commit_baton,
2284                 svn_client_ctx_t *ctx,
2285                 apr_pool_t *pool)
2286{
2287  svn_error_t *err;
2288  svn_boolean_t timestamp_sleep = FALSE;
2289  apr_pool_t *subpool = svn_pool_create(pool);
2290
2291  if (sources->nelts > 1 && !copy_as_child)
2292    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2293                            NULL, NULL);
2294
2295  err = try_copy(&timestamp_sleep,
2296                 sources, dst_path,
2297                 FALSE /* is_move */,
2298                 TRUE /* allow_mixed_revisions */,
2299                 FALSE /* metadata_only */,
2300                 make_parents,
2301                 ignore_externals,
2302                 revprop_table,
2303                 commit_callback, commit_baton,
2304                 ctx,
2305                 subpool);
2306
2307  /* If the destination exists, try to copy the sources as children of the
2308     destination. */
2309  if (copy_as_child && err && (sources->nelts == 1)
2310        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2311            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2312    {
2313      const char *src_path = APR_ARRAY_IDX(sources, 0,
2314                                           svn_client_copy_source_t *)->path;
2315      const char *src_basename;
2316      svn_boolean_t src_is_url = svn_path_is_url(src_path);
2317      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2318
2319      svn_error_clear(err);
2320      svn_pool_clear(subpool);
2321
2322      src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
2323                                : svn_dirent_basename(src_path, subpool);
2324      dst_path
2325        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2326                                                   subpool)
2327                     : svn_dirent_join(dst_path, src_basename, subpool);
2328
2329      err = try_copy(&timestamp_sleep,
2330                     sources, dst_path,
2331                     FALSE /* is_move */,
2332                     TRUE /* allow_mixed_revisions */,
2333                     FALSE /* metadata_only */,
2334                     make_parents,
2335                     ignore_externals,
2336                     revprop_table,
2337                     commit_callback, commit_baton,
2338                     ctx,
2339                     subpool);
2340    }
2341
2342  /* Sleep if required.  DST_PATH is not a URL in these cases. */
2343  if (timestamp_sleep)
2344    svn_io_sleep_for_timestamps(dst_path, subpool);
2345
2346  svn_pool_destroy(subpool);
2347  return svn_error_trace(err);
2348}
2349
2350
2351svn_error_t *
2352svn_client_move7(const apr_array_header_t *src_paths,
2353                 const char *dst_path,
2354                 svn_boolean_t move_as_child,
2355                 svn_boolean_t make_parents,
2356                 svn_boolean_t allow_mixed_revisions,
2357                 svn_boolean_t metadata_only,
2358                 const apr_hash_t *revprop_table,
2359                 svn_commit_callback2_t commit_callback,
2360                 void *commit_baton,
2361                 svn_client_ctx_t *ctx,
2362                 apr_pool_t *pool)
2363{
2364  const svn_opt_revision_t head_revision
2365    = { svn_opt_revision_head, { 0 } };
2366  svn_error_t *err;
2367  svn_boolean_t timestamp_sleep = FALSE;
2368  int i;
2369  apr_pool_t *subpool = svn_pool_create(pool);
2370  apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2371                                  sizeof(const svn_client_copy_source_t *));
2372
2373  if (src_paths->nelts > 1 && !move_as_child)
2374    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2375                            NULL, NULL);
2376
2377  for (i = 0; i < src_paths->nelts; i++)
2378    {
2379      const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2380      svn_client_copy_source_t *copy_source = apr_palloc(pool,
2381                                                         sizeof(*copy_source));
2382
2383      copy_source->path = src_path;
2384      copy_source->revision = &head_revision;
2385      copy_source->peg_revision = &head_revision;
2386
2387      APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2388    }
2389
2390  err = try_copy(&timestamp_sleep,
2391                 sources, dst_path,
2392                 TRUE /* is_move */,
2393                 allow_mixed_revisions,
2394                 metadata_only,
2395                 make_parents,
2396                 FALSE /* ignore_externals */,
2397                 revprop_table,
2398                 commit_callback, commit_baton,
2399                 ctx,
2400                 subpool);
2401
2402  /* If the destination exists, try to move the sources as children of the
2403     destination. */
2404  if (move_as_child && err && (src_paths->nelts == 1)
2405        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2406            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2407    {
2408      const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2409      const char *src_basename;
2410      svn_boolean_t src_is_url = svn_path_is_url(src_path);
2411      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2412
2413      svn_error_clear(err);
2414      svn_pool_clear(subpool);
2415
2416      src_basename = src_is_url ? svn_uri_basename(src_path, pool)
2417                                : svn_dirent_basename(src_path, pool);
2418      dst_path
2419        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2420                                                   subpool)
2421                     : svn_dirent_join(dst_path, src_basename, subpool);
2422
2423      err = try_copy(&timestamp_sleep,
2424                     sources, dst_path,
2425                     TRUE /* is_move */,
2426                     allow_mixed_revisions,
2427                     metadata_only,
2428                     make_parents,
2429                     FALSE /* ignore_externals */,
2430                     revprop_table,
2431                     commit_callback, commit_baton,
2432                     ctx,
2433                     subpool);
2434    }
2435
2436  /* Sleep if required.  DST_PATH is not a URL in these cases. */
2437  if (timestamp_sleep)
2438    svn_io_sleep_for_timestamps(dst_path, subpool);
2439
2440  svn_pool_destroy(subpool);
2441  return svn_error_trace(err);
2442}
2443