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