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