]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/copy.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 /* Quote a string if it would be handled as multiple or different tokens
181    during externals parsing */
182 static const char *
183 maybe_quote(const char *value,
184             apr_pool_t *result_pool)
185 {
186   apr_status_t status;
187   char **argv;
188
189   status = apr_tokenize_to_argv(value, &argv, result_pool);
190
191   if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
192     return apr_pstrdup(result_pool, value);
193
194   {
195     svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
196     const char *c;
197
198     svn_stringbuf_appendbyte(sb, '\"');
199
200     for (c = value; *c; c++)
201       {
202         if (*c == '\\' || *c == '\"' || *c == '\'')
203           svn_stringbuf_appendbyte(sb, '\\');
204
205         svn_stringbuf_appendbyte(sb, *c);
206       }
207
208     svn_stringbuf_appendbyte(sb, '\"');
209
210 #ifdef SVN_DEBUG
211     status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
212
213     SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
214                              && !strcmp(argv[0], value));
215 #endif
216
217     return sb->data;
218   }
219 }
220
221 /* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for
222  * use as a line in an svn:externals property, based on the external item
223  * ITEM and the additional parser information in INFO. Pin the external
224  * to EXTERNAL_PEGREV. Use POOL for all allocations. */
225 static svn_error_t *
226 make_external_description(const char **new_external_description,
227                           const char *local_abspath_or_url,
228                           svn_wc_external_item2_t *item,
229                           svn_wc__externals_parser_info_t *info,
230                           svn_opt_revision_t external_pegrev,
231                           apr_pool_t *pool)
232 {
233   const char *rev_str;
234   const char *peg_rev_str;
235
236   switch (info->format)
237     {
238       case svn_wc__external_description_format_1:
239         if (external_pegrev.kind == svn_opt_revision_unspecified)
240           {
241             /* If info->rev_str is NULL, this yields an empty string. */
242             rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
243           }
244         else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
245           rev_str = apr_psprintf(pool, "%s ", info->rev_str);
246         else
247           {
248             /* ### can't handle svn_opt_revision_date without info->rev_str */
249             SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
250             rev_str = apr_psprintf(pool, "-r%ld ",
251                                    external_pegrev.value.number);
252           }
253
254         *new_external_description =
255           apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
256                                           rev_str,
257                                           maybe_quote(item->url, pool));
258         break;
259
260       case svn_wc__external_description_format_2:
261         if (external_pegrev.kind == svn_opt_revision_unspecified)
262           {
263             /* If info->rev_str is NULL, this yields an empty string. */
264             rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
265           }
266         else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
267           rev_str = apr_psprintf(pool, "%s ", info->rev_str);
268         else
269           rev_str = "";
270
271         if (external_pegrev.kind == svn_opt_revision_unspecified)
272           peg_rev_str = info->peg_rev_str ? info->peg_rev_str : "";
273         else if (info->peg_rev_str &&
274                  item->peg_revision.kind != svn_opt_revision_head)
275           peg_rev_str = info->peg_rev_str;
276         else
277           {
278             /* ### can't handle svn_opt_revision_date without info->rev_str */
279             SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
280             peg_rev_str = apr_psprintf(pool, "@%ld",
281                                        external_pegrev.value.number);
282           }
283
284         *new_external_description =
285           apr_psprintf(pool, "%s%s %s\n", rev_str,
286                        maybe_quote(apr_psprintf(pool, "%s%s", item->url,
287                                                 peg_rev_str),
288                                    pool),
289                        maybe_quote(item->target_dir, pool));
290         break;
291
292       default:
293         return svn_error_createf(
294                  SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
295                  _("%s property defined at '%s' is using an unsupported "
296                    "syntax"), SVN_PROP_EXTERNALS,
297                  svn_dirent_local_style(local_abspath_or_url, pool));
298     }
299
300   return SVN_NO_ERROR;
301 }
302
303 /* Pin all externals listed in EXTERNALS_PROP_VAL to their
304  * last-changed revision. Set *PINNED_EXTERNALS to a new property
305  * value allocated in RESULT_POOL, or to NULL if none of the externals
306  * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the
307  * path or URL defining the svn:externals property. Use SCRATCH_POOL
308  * for temporary allocations.
309  */
310 static svn_error_t *
311 pin_externals_prop(svn_string_t **pinned_externals,
312                    svn_string_t *externals_prop_val,
313                    const apr_hash_t *externals_to_pin,
314                    const char *repos_root_url,
315                    const char *local_abspath_or_url,
316                    svn_client_ctx_t *ctx,
317                    apr_pool_t *result_pool,
318                    apr_pool_t *scratch_pool)
319 {
320   svn_stringbuf_t *buf;
321   apr_array_header_t *external_items;
322   apr_array_header_t *parser_infos;
323   apr_array_header_t *items_to_pin;
324   int pinned_items;
325   int i;
326   apr_pool_t *iterpool;
327
328   SVN_ERR(svn_wc__parse_externals_description(&external_items,
329                                               &parser_infos,
330                                               local_abspath_or_url,
331                                               externals_prop_val->data,
332                                               FALSE /* canonicalize_url */,
333                                               scratch_pool));
334
335   if (externals_to_pin)
336     {
337       items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
338                                    local_abspath_or_url);
339       if (!items_to_pin)
340         {
341           /* No pinning at all for this path. */
342           *pinned_externals = NULL;
343           return SVN_NO_ERROR;
344         }
345     }
346   else
347     items_to_pin = NULL;
348
349   buf = svn_stringbuf_create_empty(scratch_pool);
350   iterpool = svn_pool_create(scratch_pool);
351   pinned_items = 0;
352   for (i = 0; i < external_items->nelts; i++)
353     {
354       svn_wc_external_item2_t *item;
355       svn_wc__externals_parser_info_t *info;
356       svn_opt_revision_t external_pegrev;
357       const char *pinned_desc;
358
359       svn_pool_clear(iterpool);
360
361       item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
362       info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *);
363
364       if (items_to_pin)
365         {
366           int j;
367           svn_wc_external_item2_t *item_to_pin = NULL;
368
369           for (j = 0; j < items_to_pin->nelts; j++)
370             {
371               svn_wc_external_item2_t *const current =
372                 APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
373
374
375               if (current
376                   && 0 == strcmp(item->url, current->url)
377                   && 0 == strcmp(item->target_dir, current->target_dir))
378                 {
379                   item_to_pin = current;
380                   break;
381                 }
382             }
383
384           /* If this item is not in our list of external items to pin then
385            * simply keep the external at its original value. */
386           if (!item_to_pin)
387             {
388               const char *desc;
389
390               external_pegrev.kind = svn_opt_revision_unspecified;
391               SVN_ERR(make_external_description(&desc, local_abspath_or_url,
392                                                 item, info, external_pegrev,
393                                                 iterpool));
394               svn_stringbuf_appendcstr(buf, desc);
395               continue;
396             }
397         }
398
399       if (item->peg_revision.kind == svn_opt_revision_date)
400         {
401           /* Already pinned ... copy the peg date. */
402           external_pegrev.kind = svn_opt_revision_date;
403           external_pegrev.value.date = item->peg_revision.value.date;
404         }
405       else if (item->peg_revision.kind == svn_opt_revision_number)
406         {
407           /* Already pinned ... copy the peg revision number. */
408           external_pegrev.kind = svn_opt_revision_number;
409           external_pegrev.value.number = item->peg_revision.value.number;
410         }
411       else
412         {
413           SVN_ERR_ASSERT(
414             item->peg_revision.kind == svn_opt_revision_head ||
415             item->peg_revision.kind == svn_opt_revision_unspecified);
416
417           /* We're actually going to change the peg revision. */
418           ++pinned_items;
419
420           if (svn_path_is_url(local_abspath_or_url))
421             {
422               const char *resolved_url;
423               svn_ra_session_t *external_ra_session;
424               svn_revnum_t latest_revnum;
425
426               SVN_ERR(svn_wc__resolve_relative_external_url(
427                         &resolved_url, item, repos_root_url,
428                         local_abspath_or_url, iterpool, iterpool));
429               SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
430                                                            NULL, resolved_url,
431                                                            NULL, NULL, FALSE,
432                                                            FALSE, ctx,
433                                                            iterpool,
434                                                            iterpool));
435               SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
436                                                &latest_revnum,
437                                                iterpool));
438
439               external_pegrev.kind = svn_opt_revision_number;
440               external_pegrev.value.number = latest_revnum;
441             }
442           else
443             {
444               const char *external_abspath;
445               svn_node_kind_t external_kind;
446               svn_revnum_t external_checked_out_rev;
447
448               external_abspath = svn_dirent_join(local_abspath_or_url,
449                                                  item->target_dir,
450                                                  iterpool);
451               SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL,
452                                                  NULL, NULL, ctx->wc_ctx,
453                                                  local_abspath_or_url,
454                                                  external_abspath, TRUE,
455                                                  iterpool,
456                                                  iterpool));
457               if (external_kind == svn_node_none)
458                 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
459                                          NULL,
460                                          _("Cannot pin external '%s' defined "
461                                            "in %s at '%s' because it is not "
462                                            "checked out in the working copy "
463                                            "at '%s'"),
464                                            item->url, SVN_PROP_EXTERNALS,
465                                            svn_dirent_local_style(
466                                              local_abspath_or_url, iterpool),
467                                            svn_dirent_local_style(
468                                              external_abspath, iterpool));
469               else if (external_kind == svn_node_dir)
470                 {
471                   svn_boolean_t is_switched;
472                   svn_boolean_t is_modified;
473                   svn_revnum_t min_rev;
474                   svn_revnum_t max_rev;
475
476                   /* Perform some sanity checks on the checked-out external. */
477
478                   SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
479                                                         ctx->wc_ctx,
480                                                         external_abspath, NULL,
481                                                         iterpool));
482                   if (is_switched)
483                     return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
484                                              NULL,
485                                              _("Cannot pin external '%s' defined "
486                                                "in %s at '%s' because '%s' has "
487                                                "switched subtrees (switches "
488                                                "cannot be represented in %s)"),
489                                              item->url, SVN_PROP_EXTERNALS,
490                                              svn_dirent_local_style(
491                                                local_abspath_or_url, iterpool),
492                                              svn_dirent_local_style(
493                                                external_abspath, iterpool),
494                                              SVN_PROP_EXTERNALS);
495
496                   SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
497                                                  external_abspath, TRUE,
498                                                  ctx->cancel_func,
499                                                  ctx->cancel_baton,
500                                                  iterpool));
501                   if (is_modified)
502                     return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
503                                              NULL,
504                                              _("Cannot pin external '%s' defined "
505                                                "in %s at '%s' because '%s' has "
506                                                "local modifications (local "
507                                                "modifications cannot be "
508                                                "represented in %s)"),
509                                              item->url, SVN_PROP_EXTERNALS,
510                                              svn_dirent_local_style(
511                                                local_abspath_or_url, iterpool),
512                                              svn_dirent_local_style(
513                                                external_abspath, iterpool),
514                                              SVN_PROP_EXTERNALS);
515
516                   SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
517                                                     external_abspath, FALSE,
518                                                     iterpool));
519                   if (min_rev != max_rev)
520                     return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
521                                              NULL,
522                                              _("Cannot pin external '%s' defined "
523                                                "in %s at '%s' because '%s' is a "
524                                                "mixed-revision working copy "
525                                                "(mixed-revisions cannot be "
526                                                "represented in %s)"),
527                                              item->url, SVN_PROP_EXTERNALS,
528                                              svn_dirent_local_style(
529                                                local_abspath_or_url, iterpool),
530                                              svn_dirent_local_style(
531                                                external_abspath, iterpool),
532                                              SVN_PROP_EXTERNALS);
533                   external_checked_out_rev = min_rev;
534                 }
535               else
536                 {
537                   SVN_ERR_ASSERT(external_kind == svn_node_file);
538                   SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
539                                                       NULL, NULL, NULL,
540                                                       ctx->wc_ctx, external_abspath,
541                                                       iterpool, iterpool));
542                 }
543
544               external_pegrev.kind = svn_opt_revision_number;
545               external_pegrev.value.number = external_checked_out_rev;
546             }
547         }
548
549       SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
550                      external_pegrev.kind == svn_opt_revision_number);
551
552       SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
553                                         item, info, external_pegrev, iterpool));
554
555       svn_stringbuf_appendcstr(buf, pinned_desc);
556     }
557   svn_pool_destroy(iterpool);
558
559   if (pinned_items > 0)
560     *pinned_externals = svn_string_create_from_buf(buf, result_pool);
561   else
562     *pinned_externals = NULL;
563
564   return SVN_NO_ERROR;
565 }
566
567 /* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths
568  * to svn:externals property values (as const char *), where some or all
569  * external references have been pinned.
570  * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals
571  * mentioned in EXTERNALS_TO_PIN.
572  * The pinning operation takes place as part of the copy operation for
573  * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL
574  * to contact the repository containing the externals definition, if neccesary.
575  * Use CX to fopen additional RA sessions to external repositories, if
576  * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL.
577  * Use SCRATCH_POOL for temporary allocations. */
578 static svn_error_t *
579 resolve_pinned_externals(apr_hash_t **pinned_externals,
580                          const apr_hash_t *externals_to_pin,
581                          svn_client__copy_pair_t *pair,
582                          svn_ra_session_t *ra_session,
583                          const char *repos_root_url,
584                          svn_client_ctx_t *ctx,
585                          apr_pool_t *result_pool,
586                          apr_pool_t *scratch_pool)
587 {
588   const char *old_url = NULL;
589   apr_hash_t *externals_props;
590   apr_hash_index_t *hi;
591   apr_pool_t *iterpool;
592
593   *pinned_externals = apr_hash_make(result_pool);
594
595   if (svn_path_is_url(pair->src_abspath_or_url))
596     {
597       SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
598                                                 pair->src_abspath_or_url,
599                                                 scratch_pool));
600       externals_props = apr_hash_make(scratch_pool);
601       SVN_ERR(svn_client__remote_propget(externals_props, NULL,
602                                          SVN_PROP_EXTERNALS,
603                                          pair->src_abspath_or_url, "",
604                                          svn_node_dir,
605                                          pair->src_revnum,
606                                          ra_session,
607                                          svn_depth_infinity,
608                                          scratch_pool,
609                                          scratch_pool));
610     }
611   else
612     {
613       SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
614                                                    ctx->wc_ctx,
615                                                    pair->src_abspath_or_url,
616                                                    svn_depth_infinity,
617                                                    scratch_pool, scratch_pool));
618
619       /* ### gather_definitions returns propvals as const char * */
620       for (hi = apr_hash_first(scratch_pool, externals_props);
621            hi;
622            hi = apr_hash_next(hi))
623         {
624           const char *local_abspath_or_url = apr_hash_this_key(hi);
625           const char *propval = apr_hash_this_val(hi);
626           svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
627
628           svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
629         }
630     }
631
632   if (apr_hash_count(externals_props) == 0)
633     {
634       if (old_url)
635         SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
636       return SVN_NO_ERROR;
637     }
638
639   iterpool = svn_pool_create(scratch_pool);
640   for (hi = apr_hash_first(scratch_pool, externals_props);
641        hi;
642        hi = apr_hash_next(hi))
643     {
644       const char *local_abspath_or_url = apr_hash_this_key(hi);
645       svn_string_t *externals_propval = apr_hash_this_val(hi);
646       const char *relpath;
647       svn_string_t *new_propval;
648
649       svn_pool_clear(iterpool);
650
651       SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
652                                  externals_to_pin,
653                                  repos_root_url, local_abspath_or_url, ctx,
654                                  result_pool, iterpool));
655       if (new_propval)
656         {
657           if (svn_path_is_url(pair->src_abspath_or_url))
658             relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
659                                             local_abspath_or_url,
660                                             result_pool);
661           else
662             relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
663                                                local_abspath_or_url);
664           SVN_ERR_ASSERT(relpath);
665
666           svn_hash_sets(*pinned_externals, relpath, new_propval);
667         }
668     }
669   svn_pool_destroy(iterpool);
670
671   if (old_url)
672     SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
673
674   return SVN_NO_ERROR;
675 }
676
677
678
679 /* The guts of do_wc_to_wc_copies */
680 static svn_error_t *
681 do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
682                                    const apr_array_header_t *copy_pairs,
683                                    const char *dst_parent,
684                                    svn_boolean_t metadata_only,
685                                    svn_boolean_t pin_externals,
686                                    const apr_hash_t *externals_to_pin,
687                                    svn_client_ctx_t *ctx,
688                                    apr_pool_t *scratch_pool)
689 {
690   int i;
691   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
692   svn_error_t *err = SVN_NO_ERROR;
693
694   for (i = 0; i < copy_pairs->nelts; i++)
695     {
696       const char *dst_abspath;
697       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
698                                                     svn_client__copy_pair_t *);
699       apr_hash_t *pinned_externals = NULL;
700
701       svn_pool_clear(iterpool);
702
703       /* Check for cancellation */
704       if (ctx->cancel_func)
705         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
706
707       if (pin_externals)
708         {
709           const char *repos_root_url;
710
711           SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
712                                           NULL, NULL, NULL, ctx->wc_ctx,
713                                           pair->src_abspath_or_url, FALSE,
714                                           scratch_pool, iterpool));
715           SVN_ERR(resolve_pinned_externals(&pinned_externals,
716                                            externals_to_pin, pair, NULL,
717                                            repos_root_url, ctx,
718                                            iterpool, iterpool));
719         }
720
721       /* Perform the copy */
722       dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
723                                     iterpool);
724       *timestamp_sleep = TRUE;
725       err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
726                          metadata_only,
727                          ctx->cancel_func, ctx->cancel_baton,
728                          ctx->notify_func2, ctx->notify_baton2, iterpool);
729       if (err)
730         break;
731
732       if (pinned_externals)
733         {
734           apr_hash_index_t *hi;
735
736           for (hi = apr_hash_first(iterpool, pinned_externals);
737                hi;
738                hi = apr_hash_next(hi))
739             {
740               const char *dst_relpath = apr_hash_this_key(hi);
741               svn_string_t *externals_propval = apr_hash_this_val(hi);
742               const char *local_abspath;
743
744               local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
745                                               dst_relpath, iterpool);
746               /* ### use a work queue? */
747               SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
748                                        SVN_PROP_EXTERNALS, externals_propval,
749                                        svn_depth_empty, TRUE /* skip_checks */,
750                                        NULL  /* changelist_filter */,
751                                        ctx->cancel_func, ctx->cancel_baton,
752                                        NULL, NULL, /* no extra notification */
753                                        iterpool));
754             }
755         }
756     }
757   svn_pool_destroy(iterpool);
758
759   SVN_ERR(err);
760   return SVN_NO_ERROR;
761 }
762
763 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST.  Use POOL for temporary
764    allocations. */
765 static svn_error_t *
766 do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
767                    const apr_array_header_t *copy_pairs,
768                    svn_boolean_t metadata_only,
769                    svn_boolean_t pin_externals,
770                    const apr_hash_t *externals_to_pin,
771                    svn_client_ctx_t *ctx,
772                    apr_pool_t *pool)
773 {
774   const char *dst_parent, *dst_parent_abspath;
775
776   SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
777   if (copy_pairs->nelts == 1)
778     dst_parent = svn_dirent_dirname(dst_parent, pool);
779
780   SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
781
782   SVN_WC__CALL_WITH_WRITE_LOCK(
783     do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
784                                        metadata_only, pin_externals,
785                                        externals_to_pin, ctx, pool),
786     ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
787
788   return SVN_NO_ERROR;
789 }
790
791 /* The locked bit of do_wc_to_wc_moves. */
792 static svn_error_t *
793 do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
794                               const char *dst_parent_abspath,
795                               svn_boolean_t lock_src,
796                               svn_boolean_t lock_dst,
797                               svn_boolean_t allow_mixed_revisions,
798                               svn_boolean_t metadata_only,
799                               svn_client_ctx_t *ctx,
800                               apr_pool_t *scratch_pool)
801 {
802   const char *dst_abspath;
803
804   dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
805                                 scratch_pool);
806
807   SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
808                         dst_abspath, metadata_only,
809                         allow_mixed_revisions,
810                         ctx->cancel_func, ctx->cancel_baton,
811                         ctx->notify_func2, ctx->notify_baton2,
812                         scratch_pool));
813
814   return SVN_NO_ERROR;
815 }
816
817 /* Wrapper to add an optional second lock */
818 static svn_error_t *
819 do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
820                               const char *dst_parent_abspath,
821                               svn_boolean_t lock_src,
822                               svn_boolean_t lock_dst,
823                               svn_boolean_t allow_mixed_revisions,
824                               svn_boolean_t metadata_only,
825                               svn_client_ctx_t *ctx,
826                               apr_pool_t *scratch_pool)
827 {
828   if (lock_dst)
829     SVN_WC__CALL_WITH_WRITE_LOCK(
830       do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
831                                     lock_dst, allow_mixed_revisions,
832                                     metadata_only,
833                                     ctx, scratch_pool),
834       ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
835   else
836     SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
837                                           lock_dst, allow_mixed_revisions,
838                                           metadata_only,
839                                           ctx, scratch_pool));
840
841   return SVN_NO_ERROR;
842 }
843
844 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
845    afterwards.  Use POOL for temporary allocations. */
846 static svn_error_t *
847 do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
848                   const apr_array_header_t *copy_pairs,
849                   const char *dst_path,
850                   svn_boolean_t allow_mixed_revisions,
851                   svn_boolean_t metadata_only,
852                   svn_client_ctx_t *ctx,
853                   apr_pool_t *pool)
854 {
855   int i;
856   apr_pool_t *iterpool = svn_pool_create(pool);
857   svn_error_t *err = SVN_NO_ERROR;
858
859   for (i = 0; i < copy_pairs->nelts; i++)
860     {
861       const char *src_parent_abspath;
862       svn_boolean_t lock_src, lock_dst;
863       const char *src_wcroot_abspath;
864       const char *dst_wcroot_abspath;
865
866       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
867                                                     svn_client__copy_pair_t *);
868       svn_pool_clear(iterpool);
869
870       /* Check for cancellation */
871       if (ctx->cancel_func)
872         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
873
874       src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
875                                               iterpool);
876
877       SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
878                                  ctx->wc_ctx, src_parent_abspath,
879                                  iterpool, iterpool));
880       SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
881                                  ctx->wc_ctx, pair->dst_parent_abspath,
882                                  iterpool, iterpool));
883
884       /* We now need to lock the right combination of batons.
885          Four cases:
886            1) src_parent == dst_parent
887            2) src_parent is parent of dst_parent
888            3) dst_parent is parent of src_parent
889            4) src_parent and dst_parent are disjoint
890          We can handle 1) as either 2) or 3) */
891       if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
892           || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
893                                   NULL)
894               && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
895                                       NULL)))
896         {
897           lock_src = TRUE;
898           lock_dst = FALSE;
899         }
900       else if (svn_dirent_is_child(pair->dst_parent_abspath,
901                                    src_parent_abspath, NULL)
902                && !svn_dirent_is_child(pair->dst_parent_abspath,
903                                        src_wcroot_abspath, NULL))
904         {
905           lock_src = FALSE;
906           lock_dst = TRUE;
907         }
908       else
909         {
910           lock_src = TRUE;
911           lock_dst = TRUE;
912         }
913
914       *timestamp_sleep = TRUE;
915
916       /* Perform the copy and then the delete. */
917       if (lock_src)
918         SVN_WC__CALL_WITH_WRITE_LOCK(
919           do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
920                                         lock_src, lock_dst,
921                                         allow_mixed_revisions,
922                                         metadata_only,
923                                         ctx, iterpool),
924           ctx->wc_ctx, src_parent_abspath,
925           FALSE, iterpool);
926       else
927         SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
928                                               lock_src, lock_dst,
929                                               allow_mixed_revisions,
930                                               metadata_only,
931                                               ctx, iterpool));
932
933     }
934   svn_pool_destroy(iterpool);
935
936   return svn_error_trace(err);
937 }
938
939 /* Verify that the destinations stored in COPY_PAIRS are valid working copy
940    destinations and set pair->dst_parent_abspath and pair->base_name for each
941    item to the resulting location if they do */
942 static svn_error_t *
943 verify_wc_dsts(const apr_array_header_t *copy_pairs,
944                svn_boolean_t make_parents,
945                svn_boolean_t is_move,
946                svn_boolean_t metadata_only,
947                svn_client_ctx_t *ctx,
948                apr_pool_t *result_pool,
949                apr_pool_t *scratch_pool)
950 {
951   int i;
952   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
953
954   /* Check that DST does not exist, but its parent does */
955   for (i = 0; i < copy_pairs->nelts; i++)
956     {
957       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
958                                                     svn_client__copy_pair_t *);
959       svn_node_kind_t dst_kind, dst_parent_kind;
960
961       svn_pool_clear(iterpool);
962
963       /* If DST_PATH does not exist, then its basename will become a new
964          file or dir added to its parent (possibly an implicit '.').
965          Else, just error out. */
966       SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
967                                 pair->dst_abspath_or_url,
968                                 FALSE /* show_deleted */,
969                                 TRUE /* show_hidden */,
970                                 iterpool));
971       if (dst_kind != svn_node_none)
972         {
973           svn_boolean_t is_excluded;
974           svn_boolean_t is_server_excluded;
975
976           SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
977                                               &is_server_excluded, ctx->wc_ctx,
978                                               pair->dst_abspath_or_url, FALSE,
979                                               iterpool));
980
981           if (is_excluded || is_server_excluded)
982             {
983               return svn_error_createf(
984                   SVN_ERR_WC_OBSTRUCTED_UPDATE,
985                   NULL, _("Path '%s' exists, but is excluded"),
986                   svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
987             }
988           else
989             return svn_error_createf(
990                             SVN_ERR_ENTRY_EXISTS, NULL,
991                             _("Path '%s' already exists"),
992                             svn_dirent_local_style(pair->dst_abspath_or_url,
993                                                    scratch_pool));
994         }
995
996       /* Check that there is no unversioned obstruction */
997       if (metadata_only)
998         dst_kind = svn_node_none;
999       else
1000         SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1001                                   iterpool));
1002
1003       if (dst_kind != svn_node_none)
1004         {
1005           if (is_move
1006               && copy_pairs->nelts == 1
1007               && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
1008                         svn_dirent_dirname(pair->dst_abspath_or_url,
1009                                            iterpool)) == 0)
1010             {
1011               const char *dst;
1012               char *dst_apr;
1013               apr_status_t apr_err;
1014               /* We have a rename inside a directory, which might collide
1015                  just because the case insensivity of the filesystem makes
1016                  the source match the destination. */
1017
1018               SVN_ERR(svn_path_cstring_from_utf8(&dst,
1019                                                  pair->dst_abspath_or_url,
1020                                                  scratch_pool));
1021
1022               apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
1023                                            APR_FILEPATH_TRUENAME, iterpool);
1024
1025               if (!apr_err)
1026                 {
1027                   /* And now bring it back to our canonical format */
1028                   SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
1029                   dst = svn_dirent_canonicalize(dst, iterpool);
1030                 }
1031               /* else: Don't report this error; just report the normal error */
1032
1033               if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
1034                 {
1035                   /* Ok, we have a single case only rename. Get out of here */
1036                   svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1037                                    pair->dst_abspath_or_url, result_pool);
1038
1039                   svn_pool_destroy(iterpool);
1040                   return SVN_NO_ERROR;
1041                 }
1042             }
1043
1044           return svn_error_createf(
1045                             SVN_ERR_ENTRY_EXISTS, NULL,
1046                             _("Path '%s' already exists as unversioned node"),
1047                             svn_dirent_local_style(pair->dst_abspath_or_url,
1048                                                    scratch_pool));
1049         }
1050
1051       svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1052                        pair->dst_abspath_or_url, result_pool);
1053
1054       /* Make sure the destination parent is a directory and produce a clear
1055          error message if it is not. */
1056       SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
1057                                 ctx->wc_ctx, pair->dst_parent_abspath,
1058                                 FALSE, TRUE,
1059                                 iterpool));
1060       if (make_parents && dst_parent_kind == svn_node_none)
1061         {
1062           SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1063                                                  TRUE, ctx, iterpool));
1064         }
1065       else if (dst_parent_kind != svn_node_dir)
1066         {
1067           return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1068                                    _("Path '%s' is not a directory"),
1069                                    svn_dirent_local_style(
1070                                      pair->dst_parent_abspath, scratch_pool));
1071         }
1072
1073       SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1074                                 &dst_parent_kind, scratch_pool));
1075
1076       if (dst_parent_kind != svn_node_dir)
1077         return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1078                                  _("Path '%s' is not a directory"),
1079                                  svn_dirent_local_style(
1080                                      pair->dst_parent_abspath, scratch_pool));
1081     }
1082
1083   svn_pool_destroy(iterpool);
1084
1085   return SVN_NO_ERROR;
1086 }
1087
1088 static svn_error_t *
1089 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1090                         svn_boolean_t make_parents,
1091                         svn_boolean_t is_move,
1092                         svn_boolean_t metadata_only,
1093                         svn_client_ctx_t *ctx,
1094                         apr_pool_t *result_pool,
1095                         apr_pool_t *scratch_pool)
1096 {
1097   int i;
1098   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1099
1100   /* Check that all of our SRCs exist. */
1101   for (i = 0; i < copy_pairs->nelts; i++)
1102     {
1103       svn_boolean_t deleted_ok;
1104       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1105                                                     svn_client__copy_pair_t *);
1106       svn_pool_clear(iterpool);
1107
1108       deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1109                     || pair->src_op_revision.kind == svn_opt_revision_base);
1110
1111       /* Verify that SRC_PATH exists. */
1112       SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1113                                pair->src_abspath_or_url,
1114                                deleted_ok, FALSE, iterpool));
1115       if (pair->src_kind == svn_node_none)
1116         return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1117                                  _("Path '%s' does not exist"),
1118                                  svn_dirent_local_style(
1119                                         pair->src_abspath_or_url,
1120                                         scratch_pool));
1121     }
1122
1123   SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1124                          result_pool, iterpool));
1125
1126   svn_pool_destroy(iterpool);
1127
1128   return SVN_NO_ERROR;
1129 }
1130
1131
1132 /* Path-specific state used as part of path_driver_cb_baton. */
1133 typedef struct path_driver_info_t
1134 {
1135   const char *src_url;
1136   const char *src_path;
1137   const char *dst_path;
1138   svn_node_kind_t src_kind;
1139   svn_revnum_t src_revnum;
1140   svn_boolean_t resurrection;
1141   svn_boolean_t dir_add;
1142   svn_string_t *mergeinfo;  /* the new mergeinfo for the target */
1143   svn_string_t *externals; /* new externals definitions for the target */
1144   svn_boolean_t only_pin_externals;
1145 } path_driver_info_t;
1146
1147
1148 /* The baton used with the path_driver_cb_func() callback for a copy
1149    or move operation. */
1150 struct path_driver_cb_baton
1151 {
1152   /* The editor (and its state) used to perform the operation. */
1153   const svn_delta_editor_t *editor;
1154   void *edit_baton;
1155
1156   /* A hash of path -> path_driver_info_t *'s. */
1157   apr_hash_t *action_hash;
1158
1159   /* Whether the operation is a move or copy. */
1160   svn_boolean_t is_move;
1161 };
1162
1163 static svn_error_t *
1164 path_driver_cb_func(void **dir_baton,
1165                     void *parent_baton,
1166                     void *callback_baton,
1167                     const char *path,
1168                     apr_pool_t *pool)
1169 {
1170   struct path_driver_cb_baton *cb_baton = callback_baton;
1171   svn_boolean_t do_delete = FALSE, do_add = FALSE;
1172   path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1173
1174   /* Initialize return value. */
1175   *dir_baton = NULL;
1176
1177   /* This function should never get an empty PATH.  We can neither
1178      create nor delete the empty PATH, so if someone is calling us
1179      with such, the code is just plain wrong. */
1180   SVN_ERR_ASSERT(! svn_path_is_empty(path));
1181
1182   /* Check to see if we need to add the path as a parent directory. */
1183   if (path_info->dir_add)
1184     {
1185       return cb_baton->editor->add_directory(path, parent_baton, NULL,
1186                                              SVN_INVALID_REVNUM, pool,
1187                                              dir_baton);
1188     }
1189
1190   /* If this is a resurrection, we know the source and dest paths are
1191      the same, and that our driver will only be calling us once.  */
1192   if (path_info->resurrection)
1193     {
1194       /* If this is a move, we do nothing.  Otherwise, we do the copy.  */
1195       if (! cb_baton->is_move)
1196         do_add = TRUE;
1197     }
1198   /* Not a resurrection. */
1199   else
1200     {
1201       /* If this is a move, we check PATH to see if it is the source
1202          or the destination of the move. */
1203       if (cb_baton->is_move)
1204         {
1205           if (strcmp(path_info->src_path, path) == 0)
1206             do_delete = TRUE;
1207           else
1208             do_add = TRUE;
1209         }
1210       /* Not a move?  This must just be the copy addition. */
1211       else
1212         {
1213           do_add = !path_info->only_pin_externals;
1214         }
1215     }
1216
1217   if (do_delete)
1218     {
1219       SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1220                                              parent_baton, pool));
1221     }
1222   if (do_add)
1223     {
1224       SVN_ERR(svn_path_check_valid(path, pool));
1225
1226       if (path_info->src_kind == svn_node_file)
1227         {
1228           void *file_baton;
1229           SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1230                                              path_info->src_url,
1231                                              path_info->src_revnum,
1232                                              pool, &file_baton));
1233           if (path_info->mergeinfo)
1234             SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1235                                                        SVN_PROP_MERGEINFO,
1236                                                        path_info->mergeinfo,
1237                                                        pool));
1238           SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1239         }
1240       else
1241         {
1242           SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1243                                                   path_info->src_url,
1244                                                   path_info->src_revnum,
1245                                                   pool, dir_baton));
1246           if (path_info->mergeinfo)
1247             SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1248                                                       SVN_PROP_MERGEINFO,
1249                                                       path_info->mergeinfo,
1250                                                       pool));
1251         }
1252     }
1253
1254   if (path_info->externals)
1255     {
1256       if (*dir_baton == NULL)
1257         SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1258                                                  SVN_INVALID_REVNUM,
1259                                                  pool, dir_baton));
1260
1261       SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1262                                                 path_info->externals, pool));
1263     }
1264
1265   return SVN_NO_ERROR;
1266 }
1267
1268
1269 /* Starting with the path DIR relative to the RA_SESSION's session
1270    URL, work up through DIR's parents until an existing node is found.
1271    Push each nonexistent path onto the array NEW_DIRS, allocating in
1272    POOL.  Raise an error if the existing node is not a directory.
1273
1274    ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1275    ### implementation susceptible to race conditions.  */
1276 static svn_error_t *
1277 find_absent_parents1(svn_ra_session_t *ra_session,
1278                      const char *dir,
1279                      apr_array_header_t *new_dirs,
1280                      apr_pool_t *pool)
1281 {
1282   svn_node_kind_t kind;
1283   apr_pool_t *iterpool = svn_pool_create(pool);
1284
1285   SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1286                             iterpool));
1287
1288   while (kind == svn_node_none)
1289     {
1290       svn_pool_clear(iterpool);
1291
1292       APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1293       dir = svn_dirent_dirname(dir, pool);
1294
1295       SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1296                                 &kind, iterpool));
1297     }
1298
1299   if (kind != svn_node_dir)
1300     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1301                              _("Path '%s' already exists, but is not a "
1302                                "directory"), dir);
1303
1304   svn_pool_destroy(iterpool);
1305   return SVN_NO_ERROR;
1306 }
1307
1308 /* Starting with the URL *TOP_DST_URL which is also the root of
1309    RA_SESSION, work up through its parents until an existing node is
1310    found. Push each nonexistent URL onto the array NEW_DIRS,
1311    allocating in POOL.  Raise an error if the existing node is not a
1312    directory.
1313
1314    Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1315
1316    ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1317    ### implementation susceptible to race conditions.  */
1318 static svn_error_t *
1319 find_absent_parents2(svn_ra_session_t *ra_session,
1320                      const char **top_dst_url,
1321                      apr_array_header_t *new_dirs,
1322                      apr_pool_t *pool)
1323 {
1324   const char *root_url = *top_dst_url;
1325   svn_node_kind_t kind;
1326
1327   SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1328                             pool));
1329
1330   while (kind == svn_node_none)
1331     {
1332       APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1333       root_url = svn_uri_dirname(root_url, pool);
1334
1335       SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1336       SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1337                                 pool));
1338     }
1339
1340   if (kind != svn_node_dir)
1341     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1342                 _("Path '%s' already exists, but is not a directory"),
1343                 root_url);
1344
1345   *top_dst_url = root_url;
1346   return SVN_NO_ERROR;
1347 }
1348
1349 /* Queue property changes for pinning svn:externals properties set on
1350  * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1351  * is keyed by the relative path of each descendant which should have some
1352  * or all of its externals pinned, with the corresponding pinned svn:externals
1353  * properties as values. Property changes are queued in a new list of path
1354  * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1355  * existing item is found for the descendant. Allocate results in RESULT_POOL.
1356  * Use SCRATCH_POOL for temporary allocations. */
1357 static svn_error_t *
1358 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1359                                   apr_array_header_t *path_infos,
1360                                   apr_hash_t *pinned_externals,
1361                                   path_driver_info_t *parent_info,
1362                                   apr_pool_t *result_pool,
1363                                   apr_pool_t *scratch_pool)
1364 {
1365   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1366   apr_hash_index_t *hi;
1367
1368   for (hi = apr_hash_first(scratch_pool, pinned_externals);
1369        hi;
1370        hi = apr_hash_next(hi))
1371     {
1372       const char *dst_relpath = apr_hash_this_key(hi);
1373       svn_string_t *externals_prop = apr_hash_this_val(hi);
1374       const char *src_url;
1375       path_driver_info_t *info;
1376       int i;
1377
1378       svn_pool_clear(iterpool);
1379
1380       src_url = svn_path_url_add_component2(parent_info->src_url,
1381                                             dst_relpath, iterpool);
1382
1383       /* Try to find a path info the external change can be applied to. */
1384       info = NULL;
1385       for (i = 0; i < path_infos->nelts; i++)
1386         {
1387           path_driver_info_t *existing_info;
1388
1389           existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1390           if (strcmp(src_url, existing_info->src_url) == 0)
1391             {
1392               info = existing_info;
1393               break;
1394             }
1395         }
1396
1397       if (info == NULL)
1398         {
1399           /* A copied-along child needs its externals pinned.
1400              Create a new path info for this property change. */
1401           info = apr_pcalloc(result_pool, sizeof(*info));
1402           info->src_url = svn_path_url_add_component2(
1403                                 parent_info->src_url, dst_relpath,
1404                                 result_pool);
1405           info->src_path = NULL; /* Only needed on copied dirs */
1406           info->dst_path = svn_relpath_join(parent_info->dst_path,
1407                                             dst_relpath,
1408                                             result_pool);
1409           info->src_kind = svn_node_dir;
1410           info->only_pin_externals = TRUE;
1411           APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1412         }
1413
1414       info->externals = externals_prop;
1415     }
1416
1417   svn_pool_destroy(iterpool);
1418
1419   return SVN_NO_ERROR;
1420 }
1421
1422 static svn_error_t *
1423 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1424                     svn_boolean_t make_parents,
1425                     const apr_hash_t *revprop_table,
1426                     svn_commit_callback2_t commit_callback,
1427                     void *commit_baton,
1428                     svn_client_ctx_t *ctx,
1429                     svn_boolean_t is_move,
1430                     svn_boolean_t pin_externals,
1431                     const apr_hash_t *externals_to_pin,
1432                     apr_pool_t *pool)
1433 {
1434   svn_error_t *err;
1435   apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1436                                              sizeof(const char *));
1437   apr_hash_t *action_hash = apr_hash_make(pool);
1438   apr_array_header_t *path_infos;
1439   const char *top_url, *top_url_all, *top_url_dst;
1440   const char *message, *repos_root;
1441   svn_ra_session_t *ra_session = NULL;
1442   const svn_delta_editor_t *editor;
1443   void *edit_baton;
1444   struct path_driver_cb_baton cb_baton;
1445   apr_array_header_t *new_dirs = NULL;
1446   apr_hash_t *commit_revprops;
1447   apr_array_header_t *pin_externals_only_infos = NULL;
1448   int i;
1449   svn_client__copy_pair_t *first_pair =
1450     APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1451
1452   /* Open an RA session to the first copy pair's destination.  We'll
1453      be verifying that every one of our copy source and destination
1454      URLs is or is beneath this sucker's repository root URL as a form
1455      of a cheap(ish) sanity check.  */
1456   SVN_ERR(svn_client_open_ra_session2(&ra_session,
1457                                       first_pair->src_abspath_or_url, NULL,
1458                                       ctx, pool, pool));
1459   SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1460
1461   /* Verify that sources and destinations are all at or under
1462      REPOS_ROOT.  While here, create a path_info struct for each
1463      src/dst pair and initialize portions of it with normalized source
1464      location information.  */
1465   path_infos = apr_array_make(pool, copy_pairs->nelts,
1466                               sizeof(path_driver_info_t *));
1467   for (i = 0; i < copy_pairs->nelts; i++)
1468     {
1469       path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1470       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1471                                                     svn_client__copy_pair_t *);
1472       apr_hash_t *mergeinfo;
1473
1474       /* Are the source and destination URLs at or under REPOS_ROOT? */
1475       if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1476              && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1477         return svn_error_create
1478           (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1479            _("Source and destination URLs appear not to point to the "
1480              "same repository."));
1481
1482       /* Run the history function to get the source's URL and revnum in the
1483          operational revision. */
1484       SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1485       SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1486                                           &pair->src_revnum,
1487                                           NULL, NULL,
1488                                           ra_session,
1489                                           pair->src_abspath_or_url,
1490                                           &pair->src_peg_revision,
1491                                           &pair->src_op_revision, NULL,
1492                                           ctx, pool));
1493
1494       /* Go ahead and grab mergeinfo from the source, too. */
1495       SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1496       SVN_ERR(svn_client__get_repos_mergeinfo(
1497                 &mergeinfo, ra_session,
1498                 pair->src_abspath_or_url, pair->src_revnum,
1499                 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1500       if (mergeinfo)
1501         SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1502
1503       /* Plop an INFO structure onto our array thereof. */
1504       info->src_url = pair->src_abspath_or_url;
1505       info->src_revnum = pair->src_revnum;
1506       info->resurrection = FALSE;
1507       APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1508     }
1509
1510   /* If this is a move, we have to open our session to the longest
1511      path common to all SRC_URLS and DST_URLS in the repository so we
1512      can do existence checks on all paths, and so we can operate on
1513      all paths in the case of a move.  But if this is *not* a move,
1514      then opening our session at the longest path common to sources
1515      *and* destinations might be an optimization when the user is
1516      authorized to access all that stuff, but could cause the
1517      operation to fail altogether otherwise.  See issue #3242.  */
1518   SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1519                                   pool));
1520   top_url = is_move ? top_url_all : top_url_dst;
1521
1522   /* Check each src/dst pair for resurrection, and verify that TOP_URL
1523      is anchored high enough to cover all the editor_t activities
1524      required for this operation.  */
1525   for (i = 0; i < copy_pairs->nelts; i++)
1526     {
1527       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1528                                                     svn_client__copy_pair_t *);
1529       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1530                                                path_driver_info_t *);
1531
1532       /* Source and destination are the same?  It's a resurrection. */
1533       if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1534         info->resurrection = TRUE;
1535
1536       /* We need to add each dst_URL, and (in a move) we'll need to
1537          delete each src_URL.  Our selection of TOP_URL so far ensures
1538          that all our destination URLs (and source URLs, for moves)
1539          are at least as deep as TOP_URL, but we need to make sure
1540          that TOP_URL is an *ancestor* of all our to-be-edited paths.
1541
1542          Issue #683 is demonstrates this scenario.  If you're
1543          resurrecting a deleted item like this: 'svn cp -rN src_URL
1544          dst_URL', then src_URL == dst_URL == top_url.  In this
1545          situation, we want to open an RA session to be at least the
1546          *parent* of all three. */
1547       if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1548           && (strcmp(top_url, repos_root) != 0))
1549         {
1550           top_url = svn_uri_dirname(top_url, pool);
1551         }
1552       if (is_move
1553           && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1554           && (strcmp(top_url, repos_root) != 0))
1555         {
1556           top_url = svn_uri_dirname(top_url, pool);
1557         }
1558     }
1559
1560   /* Point the RA session to our current TOP_URL. */
1561   SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1562
1563   /* If we're allowed to create nonexistent parent directories of our
1564      destinations, then make a list in NEW_DIRS of the parent
1565      directories of the destination that don't yet exist.  */
1566   if (make_parents)
1567     {
1568       new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1569
1570       /* If this is a move, TOP_URL is at least the common ancestor of
1571          all the paths (sources and destinations) involved.  Assuming
1572          the sources exist (which is fair, because if they don't, this
1573          whole operation will fail anyway), TOP_URL must also exist.
1574          So it's the paths between TOP_URL and the destinations which
1575          we have to check for existence.  But here, we take advantage
1576          of the knowledge of our caller.  We know that if there are
1577          multiple copy/move operations being requested, then the
1578          destinations of the copies/moves will all be siblings of one
1579          another.  Therefore, we need only to check for the
1580          nonexistent paths between TOP_URL and *one* of our
1581          destinations to find nonexistent parents of all of them.  */
1582       if (is_move)
1583         {
1584           /* Imagine a situation where the user tries to copy an
1585              existing source directory to nonexistent directory with
1586              --parents options specified:
1587
1588                 svn copy --parents URL/src URL/dst
1589
1590              where src exists and dst does not.  If the dirname of the
1591              destination path is equal to TOP_URL,
1592              do not try to add dst to the NEW_DIRS list since it
1593              will be added to the commit items array later in this
1594              function. */
1595           const char *dir = svn_uri_skip_ancestor(
1596                               top_url,
1597                               svn_uri_dirname(first_pair->dst_abspath_or_url,
1598                                               pool),
1599                               pool);
1600           if (dir && *dir)
1601             SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1602         }
1603       /* If, however, this is *not* a move, TOP_URL only points to the
1604          common ancestor of our destination path(s), or possibly one
1605          level higher.  We'll need to do an existence crawl toward the
1606          root of the repository, starting with one of our destinations
1607          (see "... take advantage of the knowledge of our caller ..."
1608          above), and possibly adjusting TOP_URL as we go. */
1609       else
1610         {
1611           apr_array_header_t *new_urls =
1612             apr_array_make(pool, 0, sizeof(const char *));
1613           SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1614
1615           /* Convert absolute URLs into relpaths relative to TOP_URL. */
1616           for (i = 0; i < new_urls->nelts; i++)
1617             {
1618               const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1619               const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1620
1621               APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1622             }
1623         }
1624     }
1625
1626   /* For each src/dst pair, check to see if that SRC_URL is a child of
1627      the DST_URL (excepting the case where DST_URL is the repo root).
1628      If it is, and the parent of DST_URL is the current TOP_URL, then we
1629      need to reparent the session one directory higher, the parent of
1630      the DST_URL. */
1631   for (i = 0; i < copy_pairs->nelts; i++)
1632     {
1633       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1634                                                     svn_client__copy_pair_t *);
1635       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1636                                                path_driver_info_t *);
1637       const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1638                                                   pair->src_abspath_or_url,
1639                                                   pool);
1640
1641       if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1642           && (relpath != NULL && *relpath != '\0'))
1643         {
1644           info->resurrection = TRUE;
1645           top_url = svn_uri_get_longest_ancestor(
1646                             top_url,
1647                             svn_uri_dirname(pair->dst_abspath_or_url, pool),
1648                             pool);
1649           SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1650         }
1651     }
1652
1653   /* Get the portions of the SRC and DST URLs that are relative to
1654      TOP_URL (URI-decoding them while we're at it), verify that the
1655      source exists and the proposed destination does not, and toss
1656      what we've learned into the INFO array.  (For copies -- that is,
1657      non-moves -- the relative source URL NULL because it isn't a
1658      child of the TOP_URL at all.  That's okay, we'll deal with
1659      it.)  */
1660   for (i = 0; i < copy_pairs->nelts; i++)
1661     {
1662       svn_client__copy_pair_t *pair =
1663         APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1664       path_driver_info_t *info =
1665         APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1666       svn_node_kind_t dst_kind;
1667       const char *src_rel, *dst_rel;
1668
1669       src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1670       if (src_rel)
1671         {
1672           SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1673                                     &info->src_kind, pool));
1674         }
1675       else
1676         {
1677           const char *old_url;
1678
1679           src_rel = NULL;
1680           SVN_ERR_ASSERT(! is_move);
1681
1682           SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1683                                                     pair->src_abspath_or_url,
1684                                                     pool));
1685           SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1686                                     &info->src_kind, pool));
1687           SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1688         }
1689       if (info->src_kind == svn_node_none)
1690         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1691                                  _("Path '%s' does not exist in revision %ld"),
1692                                  pair->src_abspath_or_url, pair->src_revnum);
1693
1694       /* Figure out the basename that will result from this operation,
1695          and ensure that we aren't trying to overwrite existing paths.  */
1696       dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1697       SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1698                                 &dst_kind, pool));
1699       if (dst_kind != svn_node_none)
1700         {
1701           const char *path = svn_uri_skip_ancestor(repos_root,
1702                                                    pair->dst_abspath_or_url,
1703                                                    pool);
1704           return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1705                                    _("Path '/%s' already exists"), path);
1706         }
1707
1708       /* More info for our INFO structure.  */
1709       info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1710       info->dst_path = dst_rel;
1711
1712       svn_hash_sets(action_hash, info->dst_path, info);
1713       if (is_move && (! info->resurrection))
1714         svn_hash_sets(action_hash, info->src_path, info);
1715
1716       if (pin_externals)
1717         {
1718           apr_hash_t *pinned_externals;
1719
1720           SVN_ERR(resolve_pinned_externals(&pinned_externals,
1721                                            externals_to_pin, pair,
1722                                            ra_session, repos_root,
1723                                            ctx, pool, pool));
1724           if (pin_externals_only_infos == NULL)
1725             {
1726               pin_externals_only_infos =
1727                 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1728             }
1729           SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1730                                                     path_infos,
1731                                                     pinned_externals,
1732                                                     info, pool, pool));
1733         }
1734     }
1735
1736   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1737     {
1738       /* Produce a list of new paths to add, and provide it to the
1739          mechanism used to acquire a log message. */
1740       svn_client_commit_item3_t *item;
1741       const char *tmp_file;
1742       apr_array_header_t *commit_items
1743         = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1744
1745       /* Add any intermediate directories to the message */
1746       if (make_parents)
1747         {
1748           for (i = 0; i < new_dirs->nelts; i++)
1749             {
1750               const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1751
1752               item = svn_client_commit_item3_create(pool);
1753               item->url = svn_path_url_add_component2(top_url, relpath, pool);
1754               item->kind = svn_node_dir;
1755               item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1756               APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1757             }
1758         }
1759
1760       for (i = 0; i < path_infos->nelts; i++)
1761         {
1762           path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1763                                                    path_driver_info_t *);
1764
1765           item = svn_client_commit_item3_create(pool);
1766           item->url = svn_path_url_add_component2(top_url, info->dst_path,
1767                                                   pool);
1768           item->kind = info->src_kind;
1769           item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1770                               | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1771           item->copyfrom_url = info->src_url;
1772           item->copyfrom_rev = info->src_revnum;
1773           APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1774
1775           if (is_move && (! info->resurrection))
1776             {
1777               item = svn_client_commit_item3_create(pool);
1778               item->url = svn_path_url_add_component2(top_url, info->src_path,
1779                                                       pool);
1780               item->kind = info->src_kind;
1781               item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1782               APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1783             }
1784         }
1785
1786       SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1787                                       ctx, pool));
1788       if (! message)
1789         return SVN_NO_ERROR;
1790     }
1791   else
1792     message = "";
1793
1794   /* Setup our PATHS for the path-based editor drive. */
1795   /* First any intermediate directories. */
1796   if (make_parents)
1797     {
1798       for (i = 0; i < new_dirs->nelts; i++)
1799         {
1800           const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1801           path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1802
1803           info->dst_path = relpath;
1804           info->dir_add = TRUE;
1805
1806           APR_ARRAY_PUSH(paths, const char *) = relpath;
1807           svn_hash_sets(action_hash, relpath, info);
1808         }
1809     }
1810
1811   /* Then our copy destinations and move sources (if any). */
1812   for (i = 0; i < path_infos->nelts; i++)
1813     {
1814       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1815                                                path_driver_info_t *);
1816
1817       APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1818       if (is_move && (! info->resurrection))
1819         APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1820     }
1821
1822   /* Add any items which only need their externals pinned. */
1823   if (pin_externals_only_infos)
1824     {
1825       for (i = 0; i < pin_externals_only_infos->nelts; i++)
1826         {
1827           path_driver_info_t *info;
1828
1829           info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1830           APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1831           svn_hash_sets(action_hash, info->dst_path, info);
1832         }
1833     }
1834
1835   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1836                                            message, ctx, pool));
1837
1838   /* Fetch RA commit editor. */
1839   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1840                         svn_client__get_shim_callbacks(ctx->wc_ctx,
1841                                                        NULL, pool)));
1842   SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1843                                     commit_revprops,
1844                                     commit_callback,
1845                                     commit_baton,
1846                                     NULL, TRUE, /* No lock tokens */
1847                                     pool));
1848
1849   /* Setup the callback baton. */
1850   cb_baton.editor = editor;
1851   cb_baton.edit_baton = edit_baton;
1852   cb_baton.action_hash = action_hash;
1853   cb_baton.is_move = is_move;
1854
1855   /* Call the path-based editor driver. */
1856   err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1857                                path_driver_cb_func, &cb_baton, pool);
1858   if (err)
1859     {
1860       /* At least try to abort the edit (and fs txn) before throwing err. */
1861       return svn_error_compose_create(
1862                     err,
1863                     editor->abort_edit(edit_baton, pool));
1864     }
1865
1866   if (ctx->notify_func2)
1867     {
1868       svn_wc_notify_t *notify;
1869       notify = svn_wc_create_notify_url(top_url,
1870                                         svn_wc_notify_commit_finalizing,
1871                                         pool);
1872       ctx->notify_func2(ctx->notify_baton2, notify, pool);
1873     }
1874
1875   /* Close the edit. */
1876   return svn_error_trace(editor->close_edit(edit_baton, pool));
1877 }
1878
1879 /* Baton for check_url_kind */
1880 struct check_url_kind_baton
1881 {
1882   svn_ra_session_t *session;
1883   const char *repos_root_url;
1884   svn_boolean_t should_reparent;
1885 };
1886
1887 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1888 static svn_error_t *
1889 check_url_kind(void *baton,
1890                svn_node_kind_t *kind,
1891                const char *url,
1892                svn_revnum_t revision,
1893                apr_pool_t *scratch_pool)
1894 {
1895   struct check_url_kind_baton *cukb = baton;
1896
1897   /* If we don't have a session or can't use the session, get one */
1898   if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1899     *kind = svn_node_none;
1900   else
1901     {
1902       cukb->should_reparent = TRUE;
1903
1904       SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1905
1906       SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1907                                 kind, scratch_pool));
1908     }
1909
1910   return SVN_NO_ERROR;
1911 }
1912
1913 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1914  * in the COMMIT_ITEMS list.
1915  * If the list does not already have a commit item for COMMIT_URL
1916  * add a new commit item for the property change.
1917  * Allocate results in RESULT_POOL.
1918  * Use SCRATCH_POOL for temporary allocations. */
1919 static svn_error_t *
1920 queue_prop_change_commit_items(const char *local_abspath,
1921                                const char *commit_url,
1922                                apr_array_header_t *commit_items,
1923                                const char *propname,
1924                                svn_string_t *propval,
1925                                apr_pool_t *result_pool,
1926                                apr_pool_t *scratch_pool)
1927 {
1928   svn_client_commit_item3_t *item = NULL;
1929   svn_prop_t *prop;
1930   int i;
1931
1932   for (i = 0; i < commit_items->nelts; i++)
1933     {
1934       svn_client_commit_item3_t *existing_item;
1935
1936       existing_item = APR_ARRAY_IDX(commit_items, i,
1937                                     svn_client_commit_item3_t *);
1938       if (strcmp(existing_item->url, commit_url) == 0)
1939         {
1940           item = existing_item;
1941           break;
1942         }
1943     }
1944
1945   if (item == NULL)
1946     {
1947       item = svn_client_commit_item3_create(result_pool);
1948       item->path = local_abspath;
1949       item->url = commit_url;
1950       item->kind = svn_node_dir;
1951       item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1952
1953       item->incoming_prop_changes = apr_array_make(result_pool, 1,
1954                                                    sizeof(svn_prop_t *));
1955       APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1956     }
1957   else
1958     item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1959
1960   if (item->outgoing_prop_changes == NULL)
1961     item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1962                                                  sizeof(svn_prop_t *));
1963
1964   prop = apr_palloc(result_pool, sizeof(*prop));
1965   prop->name = propname;
1966   prop->value = propval;
1967   APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1968
1969   return SVN_NO_ERROR;
1970 }
1971
1972 /* ### Copy ...
1973  * COMMIT_INFO_P is ...
1974  * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1975  * and each 'dst_abspath_or_url' is a URL.
1976  * MAKE_PARENTS is ...
1977  * REVPROP_TABLE is ...
1978  * CTX is ... */
1979 static svn_error_t *
1980 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1981                  svn_boolean_t make_parents,
1982                  const apr_hash_t *revprop_table,
1983                  svn_commit_callback2_t commit_callback,
1984                  void *commit_baton,
1985                  svn_boolean_t pin_externals,
1986                  const apr_hash_t *externals_to_pin,
1987                  svn_client_ctx_t *ctx,
1988                  apr_pool_t *scratch_pool)
1989 {
1990   const char *message;
1991   const char *top_src_path, *top_dst_url;
1992   struct check_url_kind_baton cukb;
1993   const char *top_src_abspath;
1994   svn_ra_session_t *ra_session;
1995   const svn_delta_editor_t *editor;
1996 #ifdef ENABLE_EV2_SHIMS
1997   apr_hash_t *relpath_map = NULL;
1998 #endif
1999   void *edit_baton;
2000   svn_client__committables_t *committables;
2001   apr_array_header_t *commit_items;
2002   apr_pool_t *iterpool;
2003   apr_array_header_t *new_dirs = NULL;
2004   apr_hash_t *commit_revprops;
2005   svn_client__copy_pair_t *first_pair;
2006   apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2007   apr_array_header_t *commit_items_for_dav;
2008   int i;
2009
2010   /* Find the common root of all the source paths */
2011   SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2012                                   scratch_pool));
2013
2014   /* Do we need to lock the working copy?  1.6 didn't take a write
2015      lock, but what happens if the working copy changes during the copy
2016      operation? */
2017
2018   iterpool = svn_pool_create(scratch_pool);
2019
2020   /* Determine the longest common ancestor for the destinations, and open an RA
2021      session to that location. */
2022   /* ### But why start by getting the _parent_ of the first one? */
2023   /* --- That works because multiple destinations always point to the same
2024    *     directory. I'm rather wondering why we need to find a common
2025    *     destination parent here at all, instead of simply getting
2026    *     top_dst_url from get_copy_pair_ancestors() above?
2027    *     It looks like the entire block of code hanging off this comment
2028    *     is redundant. */
2029   first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2030   top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2031   for (i = 1; i < copy_pairs->nelts; i++)
2032     {
2033       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2034                                                     svn_client__copy_pair_t *);
2035       top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2036                                                  pair->dst_abspath_or_url,
2037                                                  scratch_pool);
2038     }
2039
2040   SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2041
2042   commit_items_for_dav = apr_array_make(session_pool, 0,
2043                                         sizeof(svn_client_commit_item3_t*));
2044
2045   /* Open a session to help while determining the exact targets */
2046   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2047                                                top_src_abspath,
2048                                                commit_items_for_dav,
2049                                                FALSE /* write_dav_props */,
2050                                                TRUE /* read_dav_props */,
2051                                                ctx,
2052                                                session_pool, session_pool));
2053
2054   /* If requested, determine the nearest existing parent of the destination,
2055      and reparent the ra session there. */
2056   if (make_parents)
2057     {
2058       new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2059       SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2060                                    scratch_pool));
2061     }
2062
2063   /* Figure out the basename that will result from each copy and check to make
2064      sure it doesn't exist already. */
2065   for (i = 0; i < copy_pairs->nelts; i++)
2066     {
2067       svn_node_kind_t dst_kind;
2068       const char *dst_rel;
2069       svn_client__copy_pair_t *pair =
2070         APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2071
2072       svn_pool_clear(iterpool);
2073       dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2074                                       iterpool);
2075       SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2076                                 &dst_kind, iterpool));
2077       if (dst_kind != svn_node_none)
2078         {
2079           return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2080                                    _("Path '%s' already exists"),
2081                                    pair->dst_abspath_or_url);
2082         }
2083     }
2084
2085   cukb.session = ra_session;
2086   SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2087   cukb.should_reparent = FALSE;
2088
2089   /* Crawl the working copy for commit items. */
2090   /* ### TODO: Pass check_url_func for issue #3314 handling */
2091   SVN_ERR(svn_client__get_copy_committables(&committables,
2092                                             copy_pairs,
2093                                             check_url_kind, &cukb,
2094                                             ctx, scratch_pool, iterpool));
2095
2096   /* The committables are keyed by the repository root */
2097   commit_items = svn_hash_gets(committables->by_repository,
2098                                cukb.repos_root_url);
2099   SVN_ERR_ASSERT(commit_items != NULL);
2100
2101   if (cukb.should_reparent)
2102     SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2103
2104   /* If we are creating intermediate directories, tack them onto the list
2105      of committables. */
2106   if (make_parents)
2107     {
2108       for (i = 0; i < new_dirs->nelts; i++)
2109         {
2110           const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2111           svn_client_commit_item3_t *item;
2112
2113           item = svn_client_commit_item3_create(scratch_pool);
2114           item->url = url;
2115           item->kind = svn_node_dir;
2116           item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2117           item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2118                                                        sizeof(svn_prop_t *));
2119           APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2120         }
2121     }
2122
2123   /* ### TODO: This extra loop would be unnecessary if this code lived
2124      ### in svn_client__get_copy_committables(), which is incidentally
2125      ### only used above (so should really be in this source file). */
2126   for (i = 0; i < copy_pairs->nelts; i++)
2127     {
2128       apr_hash_t *mergeinfo, *wc_mergeinfo;
2129       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2130                                                     svn_client__copy_pair_t *);
2131       svn_client_commit_item3_t *item =
2132         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2133       svn_client__pathrev_t *src_origin;
2134
2135       svn_pool_clear(iterpool);
2136
2137       SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2138                                              pair->src_abspath_or_url,
2139                                              ctx, iterpool, iterpool));
2140
2141       /* Set the mergeinfo for the destination to the combined merge
2142          info known to the WC and the repository. */
2143       /* Repository mergeinfo (or NULL if it's locally added)... */
2144       if (src_origin)
2145         SVN_ERR(svn_client__get_repos_mergeinfo(
2146                   &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2147                   svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2148       else
2149         mergeinfo = NULL;
2150       /* ... and WC mergeinfo. */
2151       SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2152                                           pair->src_abspath_or_url,
2153                                           iterpool, iterpool));
2154       if (wc_mergeinfo && mergeinfo)
2155         SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2156                                      iterpool));
2157       else if (! mergeinfo)
2158         mergeinfo = wc_mergeinfo;
2159
2160       if (mergeinfo)
2161         {
2162           /* Push a mergeinfo prop representing MERGEINFO onto the
2163            * OUTGOING_PROP_CHANGES array. */
2164
2165           svn_prop_t *mergeinfo_prop
2166                             = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2167           svn_string_t *prop_value;
2168
2169           SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2170                                           scratch_pool));
2171
2172           if (!item->outgoing_prop_changes)
2173             {
2174               item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2175                                                            sizeof(svn_prop_t *));
2176             }
2177
2178           mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2179           mergeinfo_prop->value = prop_value;
2180           APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2181             = mergeinfo_prop;
2182         }
2183
2184       if (pin_externals)
2185         {
2186           apr_hash_t *pinned_externals;
2187           apr_hash_index_t *hi;
2188
2189           SVN_ERR(resolve_pinned_externals(&pinned_externals,
2190                                            externals_to_pin, pair,
2191                                            ra_session, cukb.repos_root_url,
2192                                            ctx, scratch_pool, iterpool));
2193           for (hi = apr_hash_first(scratch_pool, pinned_externals);
2194                hi;
2195                hi = apr_hash_next(hi))
2196             {
2197               const char *dst_relpath = apr_hash_this_key(hi);
2198               svn_string_t *externals_propval = apr_hash_this_val(hi);
2199               const char *dst_url;
2200               const char *commit_url;
2201               const char *src_abspath;
2202
2203               if (svn_path_is_url(pair->dst_abspath_or_url))
2204                 dst_url = pair->dst_abspath_or_url;
2205               else
2206                 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2207                                              pair->dst_abspath_or_url,
2208                                              scratch_pool, iterpool));
2209               commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2210                                                        scratch_pool);
2211               src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2212                                             dst_relpath, iterpool);
2213               SVN_ERR(queue_prop_change_commit_items(src_abspath,
2214                                                      commit_url, commit_items,
2215                                                      SVN_PROP_EXTERNALS,
2216                                                      externals_propval,
2217                                                      scratch_pool, iterpool));
2218             }
2219         }
2220     }
2221
2222   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2223     {
2224       const char *tmp_file;
2225
2226       SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2227                                       ctx, scratch_pool));
2228       if (! message)
2229         {
2230           svn_pool_destroy(iterpool);
2231           svn_pool_destroy(session_pool);
2232           return SVN_NO_ERROR;
2233         }
2234     }
2235   else
2236     message = "";
2237
2238   /* Sort and condense our COMMIT_ITEMS. */
2239   SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2240                                             commit_items, scratch_pool));
2241
2242   /* Add the commit items to the DAV commit item list to provide access
2243      to dav properties (for pre http-v2 DAV) */
2244   apr_array_cat(commit_items_for_dav, commit_items);
2245
2246 #ifdef ENABLE_EV2_SHIMS
2247   if (commit_items)
2248     {
2249       relpath_map = apr_hash_make(scratch_pool);
2250       for (i = 0; i < commit_items->nelts; i++)
2251         {
2252           svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2253                                                   svn_client_commit_item3_t *);
2254           const char *relpath;
2255
2256           if (!item->path)
2257             continue;
2258
2259           svn_pool_clear(iterpool);
2260           SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2261                                           NULL, NULL,
2262                                           ctx->wc_ctx, item->path, FALSE,
2263                                           scratch_pool, iterpool));
2264           if (relpath)
2265             svn_hash_sets(relpath_map, relpath, item->path);
2266         }
2267     }
2268 #endif
2269
2270   SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2271
2272   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2273                                            message, ctx, session_pool));
2274
2275   /* Fetch RA commit editor. */
2276 #ifdef ENABLE_EV2_SHIMS
2277   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2278                         svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2279                                                        session_pool)));
2280 #endif
2281   SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2282                                     commit_revprops,
2283                                     commit_callback,
2284                                     commit_baton, NULL,
2285                                     TRUE, /* No lock tokens */
2286                                     session_pool));
2287
2288   /* Perform the commit. */
2289   SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2290                                   editor, edit_baton,
2291                                   NULL /* notify_path_prefix */,
2292                                   NULL, ctx, session_pool, session_pool),
2293             _("Commit failed (details follow):"));
2294
2295   svn_pool_destroy(iterpool);
2296   svn_pool_destroy(session_pool);
2297
2298   return SVN_NO_ERROR;
2299 }
2300
2301 /* A baton for notification_adjust_func(). */
2302 struct notification_adjust_baton
2303 {
2304   svn_wc_notify_func2_t inner_func;
2305   void *inner_baton;
2306   const char *checkout_abspath;
2307   const char *final_abspath;
2308 };
2309
2310 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2311  * baton is BATON->inner_baton) and adjusts the notification paths that
2312  * start with BATON->checkout_abspath to start instead with
2313  * BATON->final_abspath. */
2314 static void
2315 notification_adjust_func(void *baton,
2316                          const svn_wc_notify_t *notify,
2317                          apr_pool_t *pool)
2318 {
2319   struct notification_adjust_baton *nb = baton;
2320   svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2321   const char *relpath;
2322
2323   relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2324   inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2325
2326   if (nb->inner_func)
2327     nb->inner_func(nb->inner_baton, inner_notify, pool);
2328 }
2329
2330 /* Peform each individual copy operation for a repos -> wc copy.  A
2331    helper for repos_to_wc_copy().
2332
2333    Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2334 static svn_error_t *
2335 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2336                         svn_client__copy_pair_t *pair,
2337                         svn_boolean_t same_repositories,
2338                         svn_boolean_t ignore_externals,
2339                         svn_boolean_t pin_externals,
2340                         const apr_hash_t *externals_to_pin,
2341                         svn_ra_session_t *ra_session,
2342                         svn_client_ctx_t *ctx,
2343                         apr_pool_t *pool)
2344 {
2345   apr_hash_t *src_mergeinfo;
2346   const char *dst_abspath = pair->dst_abspath_or_url;
2347
2348   SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2349
2350   if (!same_repositories && ctx->notify_func2)
2351     {
2352       svn_wc_notify_t *notify;
2353       notify = svn_wc_create_notify_url(
2354                             pair->src_abspath_or_url,
2355                             svn_wc_notify_foreign_copy_begin,
2356                             pool);
2357       notify->kind = pair->src_kind;
2358       ctx->notify_func2(ctx->notify_baton2, notify, pool);
2359
2360       /* Allow a theoretical cancel to get through. */
2361       if (ctx->cancel_func)
2362         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2363     }
2364
2365   if (pair->src_kind == svn_node_dir)
2366     {
2367       if (same_repositories)
2368         {
2369           const char *tmpdir_abspath, *tmp_abspath;
2370
2371           /* Find a temporary location in which to check out the copy source. */
2372           SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2373                                      pool, pool));
2374
2375           SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2376                                            svn_io_file_del_on_close, pool, pool));
2377
2378           /* Make a new checkout of the requested source. While doing so,
2379            * resolve pair->src_revnum to an actual revision number in case it
2380            * was until now 'invalid' meaning 'head'.  Ask this function not to
2381            * sleep for timestamps, by passing a sleep_needed output param.
2382            * Send notifications for all nodes except the root node, and adjust
2383            * them to refer to the destination rather than this temporary path. */
2384           {
2385             svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2386             void *old_notify_baton2 = ctx->notify_baton2;
2387             struct notification_adjust_baton nb;
2388             svn_error_t *err;
2389
2390             nb.inner_func = ctx->notify_func2;
2391             nb.inner_baton = ctx->notify_baton2;
2392             nb.checkout_abspath = tmp_abspath;
2393             nb.final_abspath = dst_abspath;
2394             ctx->notify_func2 = notification_adjust_func;
2395             ctx->notify_baton2 = &nb;
2396
2397             /* Avoid a chicken-and-egg problem:
2398              * If pinning externals we'll need to adjust externals
2399              * properties before checking out any externals.
2400              * But copy needs to happen before pinning because else there
2401              * are no svn:externals properties to pin. */
2402             if (pin_externals)
2403               ignore_externals = TRUE;
2404
2405             err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2406                                                 pair->src_original,
2407                                                 tmp_abspath,
2408                                                 &pair->src_peg_revision,
2409                                                 &pair->src_op_revision,
2410                                                 svn_depth_infinity,
2411                                                 ignore_externals, FALSE,
2412                                                 ra_session, ctx, pool);
2413
2414             ctx->notify_func2 = old_notify_func2;
2415             ctx->notify_baton2 = old_notify_baton2;
2416
2417             SVN_ERR(err);
2418           }
2419
2420           *timestamp_sleep = TRUE;
2421
2422       /* Schedule dst_path for addition in parent, with copy history.
2423          Don't send any notification here.
2424          Then remove the temporary checkout's .svn dir in preparation for
2425          moving the rest of it into the final destination. */
2426           SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2427                                TRUE /* metadata_only */,
2428                                ctx->cancel_func, ctx->cancel_baton,
2429                                NULL, NULL, pool));
2430           SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2431                                              FALSE, pool, pool));
2432           SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2433                                                        tmp_abspath,
2434                                                        FALSE, FALSE,
2435                                                        ctx->cancel_func,
2436                                                        ctx->cancel_baton,
2437                                                        pool));
2438
2439           /* Move the temporary disk tree into place. */
2440           SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
2441         }
2442       else
2443         {
2444           *timestamp_sleep = TRUE;
2445
2446           SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2447                                            dst_abspath,
2448                                            &pair->src_peg_revision,
2449                                            &pair->src_op_revision,
2450                                            svn_depth_infinity,
2451                                            FALSE /* make_parents */,
2452                                            TRUE /* already_locked */,
2453                                            ctx, pool));
2454
2455           return SVN_NO_ERROR;
2456         }
2457
2458       if (pin_externals)
2459         {
2460           apr_hash_t *pinned_externals;
2461           apr_hash_index_t *hi;
2462           apr_pool_t *iterpool;
2463           const char *repos_root_url;
2464           apr_hash_t *new_externals;
2465           apr_hash_t *new_depths;
2466
2467           SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2468           SVN_ERR(resolve_pinned_externals(&pinned_externals,
2469                                            externals_to_pin, pair,
2470                                            ra_session, repos_root_url,
2471                                            ctx, pool, pool));
2472
2473           iterpool = svn_pool_create(pool);
2474           for (hi = apr_hash_first(pool, pinned_externals);
2475                hi;
2476                hi = apr_hash_next(hi))
2477             {
2478               const char *dst_relpath = apr_hash_this_key(hi);
2479               svn_string_t *externals_propval = apr_hash_this_val(hi);
2480               const char *local_abspath;
2481
2482               svn_pool_clear(iterpool);
2483
2484               local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2485                                               dst_relpath, iterpool);
2486               /* ### use a work queue? */
2487               SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2488                                        SVN_PROP_EXTERNALS, externals_propval,
2489                                        svn_depth_empty, TRUE /* skip_checks */,
2490                                        NULL  /* changelist_filter */,
2491                                        ctx->cancel_func, ctx->cancel_baton,
2492                                        NULL, NULL, /* no extra notification */
2493                                        iterpool));
2494             }
2495
2496           /* Now update all externals in the newly created copy. */
2497           SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2498                                                        &new_depths,
2499                                                        ctx->wc_ctx,
2500                                                        dst_abspath,
2501                                                        svn_depth_infinity,
2502                                                        iterpool, iterpool));
2503           SVN_ERR(svn_client__handle_externals(new_externals,
2504                                                new_depths,
2505                                                repos_root_url, dst_abspath,
2506                                                svn_depth_infinity,
2507                                                timestamp_sleep,
2508                                                ra_session,
2509                                                ctx, iterpool));
2510           svn_pool_destroy(iterpool);
2511         }
2512     } /* end directory case */
2513
2514   else if (pair->src_kind == svn_node_file)
2515     {
2516       apr_hash_t *new_props;
2517       const char *src_rel;
2518       svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2519
2520       SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2521                                                   pair->src_abspath_or_url,
2522                                                   pool));
2523       /* Fetch the file content. While doing so, resolve pair->src_revnum
2524        * to an actual revision number if it's 'invalid' meaning 'head'. */
2525       SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2526                               new_base_contents,
2527                               &pair->src_revnum, &new_props, pool));
2528
2529       if (new_props && ! same_repositories)
2530         svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2531
2532       *timestamp_sleep = TRUE;
2533
2534       SVN_ERR(svn_wc_add_repos_file4(
2535          ctx->wc_ctx, dst_abspath,
2536          new_base_contents, NULL, new_props, NULL,
2537          same_repositories ? pair->src_abspath_or_url : NULL,
2538          same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2539          ctx->cancel_func, ctx->cancel_baton,
2540          pool));
2541     }
2542
2543   /* Record the implied mergeinfo (before the notification callback
2544      is invoked for the root node). */
2545   SVN_ERR(svn_client__get_repos_mergeinfo(
2546             &src_mergeinfo, ra_session,
2547             pair->src_abspath_or_url, pair->src_revnum,
2548             svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2549   SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2550
2551   /* Do our own notification for the root node, even if we could possibly
2552      have delegated it.  See also issue #1552.
2553
2554      ### Maybe this notification should mention the mergeinfo change. */
2555   if (ctx->notify_func2)
2556     {
2557       svn_wc_notify_t *notify = svn_wc_create_notify(
2558                                   dst_abspath, svn_wc_notify_add, pool);
2559       notify->kind = pair->src_kind;
2560       ctx->notify_func2(ctx->notify_baton2, notify, pool);
2561     }
2562
2563   return SVN_NO_ERROR;
2564 }
2565
2566 static svn_error_t *
2567 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2568                         const apr_array_header_t *copy_pairs,
2569                         const char *top_dst_path,
2570                         svn_boolean_t ignore_externals,
2571                         svn_boolean_t pin_externals,
2572                         const apr_hash_t *externals_to_pin,
2573                         svn_ra_session_t *ra_session,
2574                         svn_client_ctx_t *ctx,
2575                         apr_pool_t *scratch_pool)
2576 {
2577   int i;
2578   svn_boolean_t same_repositories;
2579   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2580
2581   /* We've already checked for physical obstruction by a working file.
2582      But there could also be logical obstruction by an entry whose
2583      working file happens to be missing.*/
2584   SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2585                          ctx, scratch_pool, iterpool));
2586
2587   /* Decide whether the two repositories are the same or not. */
2588   {
2589     svn_error_t *src_err, *dst_err;
2590     const char *parent;
2591     const char *parent_abspath;
2592     const char *src_uuid, *dst_uuid;
2593
2594     /* Get the repository uuid of SRC_URL */
2595     src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
2596     if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2597       return svn_error_trace(src_err);
2598
2599     /* Get repository uuid of dst's parent directory, since dst may
2600        not exist.  ### TODO:  we should probably walk up the wc here,
2601        in case the parent dir has an imaginary URL.  */
2602     if (copy_pairs->nelts == 1)
2603       parent = svn_dirent_dirname(top_dst_path, scratch_pool);
2604     else
2605       parent = top_dst_path;
2606
2607     SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
2608     dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2609                                         parent_abspath, ctx,
2610                                         iterpool, iterpool);
2611     if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2612       return dst_err;
2613
2614     /* If either of the UUIDs are nonexistent, then at least one of
2615        the repositories must be very old.  Rather than punish the
2616        user, just assume the repositories are different, so no
2617        copy-history is attempted. */
2618     if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
2619       same_repositories = FALSE;
2620     else
2621       same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2622   }
2623
2624   /* Perform the move for each of the copy_pairs. */
2625   for (i = 0; i < copy_pairs->nelts; i++)
2626     {
2627       /* Check for cancellation */
2628       if (ctx->cancel_func)
2629         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2630
2631       svn_pool_clear(iterpool);
2632
2633       SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2634                                       APR_ARRAY_IDX(copy_pairs, i,
2635                                                     svn_client__copy_pair_t *),
2636                                       same_repositories,
2637                                       ignore_externals,
2638                                       pin_externals, externals_to_pin,
2639                                       ra_session, ctx, iterpool));
2640     }
2641   svn_pool_destroy(iterpool);
2642
2643   return SVN_NO_ERROR;
2644 }
2645
2646 static svn_error_t *
2647 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2648                  const apr_array_header_t *copy_pairs,
2649                  svn_boolean_t make_parents,
2650                  svn_boolean_t ignore_externals,
2651                  svn_boolean_t pin_externals,
2652                  const apr_hash_t *externals_to_pin,
2653                  svn_client_ctx_t *ctx,
2654                  apr_pool_t *pool)
2655 {
2656   svn_ra_session_t *ra_session;
2657   const char *top_src_url, *top_dst_path;
2658   apr_pool_t *iterpool = svn_pool_create(pool);
2659   const char *lock_abspath;
2660   int i;
2661
2662   /* Get the real path for the source, based upon its peg revision. */
2663   for (i = 0; i < copy_pairs->nelts; i++)
2664     {
2665       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2666                                                     svn_client__copy_pair_t *);
2667       const char *src;
2668
2669       svn_pool_clear(iterpool);
2670
2671       SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2672                                           NULL,
2673                                           pair->src_abspath_or_url,
2674                                           &pair->src_peg_revision,
2675                                           &pair->src_op_revision, NULL,
2676                                           ctx, iterpool));
2677
2678       pair->src_original = pair->src_abspath_or_url;
2679       pair->src_abspath_or_url = apr_pstrdup(pool, src);
2680     }
2681
2682   SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
2683                                   NULL, pool));
2684   lock_abspath = top_dst_path;
2685   if (copy_pairs->nelts == 1)
2686     {
2687       top_src_url = svn_uri_dirname(top_src_url, pool);
2688       lock_abspath = svn_dirent_dirname(top_dst_path, pool);
2689     }
2690
2691   /* Open a repository session to the longest common src ancestor.  We do not
2692      (yet) have a working copy, so we don't have a corresponding path and
2693      tempfiles cannot go into the admin area. */
2694   SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2695                                       ctx, pool, pool));
2696
2697   /* Get the correct src path for the peg revision used, and verify that we
2698      aren't overwriting an existing path. */
2699   for (i = 0; i < copy_pairs->nelts; i++)
2700     {
2701       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2702                                                     svn_client__copy_pair_t *);
2703       svn_node_kind_t dst_parent_kind, dst_kind;
2704       const char *dst_parent;
2705       const char *src_rel;
2706
2707       svn_pool_clear(iterpool);
2708
2709       /* Next, make sure that the path exists in the repository. */
2710       SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2711                                                   pair->src_abspath_or_url,
2712                                                   iterpool));
2713       SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2714                                 &pair->src_kind, pool));
2715       if (pair->src_kind == svn_node_none)
2716         {
2717           if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2718             return svn_error_createf
2719               (SVN_ERR_FS_NOT_FOUND, NULL,
2720                _("Path '%s' not found in revision %ld"),
2721                pair->src_abspath_or_url, pair->src_revnum);
2722           else
2723             return svn_error_createf
2724               (SVN_ERR_FS_NOT_FOUND, NULL,
2725                _("Path '%s' not found in head revision"),
2726                pair->src_abspath_or_url);
2727         }
2728
2729       /* Figure out about dst. */
2730       SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2731                                 iterpool));
2732       if (dst_kind != svn_node_none)
2733         {
2734           return svn_error_createf(
2735             SVN_ERR_ENTRY_EXISTS, NULL,
2736             _("Path '%s' already exists"),
2737             svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2738         }
2739
2740       /* Make sure the destination parent is a directory and produce a clear
2741          error message if it is not. */
2742       dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2743       SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2744       if (make_parents && dst_parent_kind == svn_node_none)
2745         {
2746           SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2747                                                  iterpool));
2748         }
2749       else if (dst_parent_kind != svn_node_dir)
2750         {
2751           return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2752                                    _("Path '%s' is not a directory"),
2753                                    svn_dirent_local_style(dst_parent, pool));
2754         }
2755     }
2756   svn_pool_destroy(iterpool);
2757
2758   SVN_WC__CALL_WITH_WRITE_LOCK(
2759     repos_to_wc_copy_locked(timestamp_sleep,
2760                             copy_pairs, top_dst_path, ignore_externals,
2761                             pin_externals, externals_to_pin,
2762                             ra_session, ctx, pool),
2763     ctx->wc_ctx, lock_abspath, FALSE, pool);
2764   return SVN_NO_ERROR;
2765 }
2766
2767 #define NEED_REPOS_REVNUM(revision) \
2768         ((revision.kind != svn_opt_revision_unspecified) \
2769           && (revision.kind != svn_opt_revision_working))
2770
2771 /* ...
2772  *
2773  * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2774  * change *TIMESTAMP_SLEEP.  This output will be valid even if the
2775  * function returns an error.
2776  *
2777  * Perform all allocations in POOL.
2778  */
2779 static svn_error_t *
2780 try_copy(svn_boolean_t *timestamp_sleep,
2781          const apr_array_header_t *sources,
2782          const char *dst_path_in,
2783          svn_boolean_t is_move,
2784          svn_boolean_t allow_mixed_revisions,
2785          svn_boolean_t metadata_only,
2786          svn_boolean_t make_parents,
2787          svn_boolean_t ignore_externals,
2788          svn_boolean_t pin_externals,
2789          const apr_hash_t *externals_to_pin,
2790          const apr_hash_t *revprop_table,
2791          svn_commit_callback2_t commit_callback,
2792          void *commit_baton,
2793          svn_client_ctx_t *ctx,
2794          apr_pool_t *pool)
2795 {
2796   apr_array_header_t *copy_pairs =
2797                         apr_array_make(pool, sources->nelts,
2798                                        sizeof(svn_client__copy_pair_t *));
2799   svn_boolean_t srcs_are_urls, dst_is_url;
2800   int i;
2801
2802   /* Assert instead of crashing if the sources list is empty. */
2803   SVN_ERR_ASSERT(sources->nelts > 0);
2804
2805   /* Are either of our paths URLs?  Just check the first src_path.  If
2806      there are more than one, we'll check for homogeneity among them
2807      down below. */
2808   srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2809                                   svn_client_copy_source_t *)->path);
2810   dst_is_url = svn_path_is_url(dst_path_in);
2811   if (!dst_is_url)
2812     SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2813
2814   /* If we have multiple source paths, it implies the dst_path is a
2815      directory we are moving or copying into.  Populate the COPY_PAIRS
2816      array to contain a destination path for each of the source paths. */
2817   if (sources->nelts > 1)
2818     {
2819       apr_pool_t *iterpool = svn_pool_create(pool);
2820
2821       for (i = 0; i < sources->nelts; i++)
2822         {
2823           svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2824                                                svn_client_copy_source_t *);
2825           svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2826           const char *src_basename;
2827           svn_boolean_t src_is_url = svn_path_is_url(source->path);
2828
2829           svn_pool_clear(iterpool);
2830
2831           if (src_is_url)
2832             {
2833               pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2834               src_basename = svn_uri_basename(pair->src_abspath_or_url,
2835                                               iterpool);
2836             }
2837           else
2838             {
2839               SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2840                                               source->path, pool));
2841               src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2842                                                  iterpool);
2843             }
2844
2845           pair->src_op_revision = *source->revision;
2846           pair->src_peg_revision = *source->peg_revision;
2847           pair->src_kind = svn_node_unknown;
2848
2849           SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2850                                             &pair->src_op_revision,
2851                                             src_is_url,
2852                                             TRUE,
2853                                             iterpool));
2854
2855           /* Check to see if all the sources are urls or all working copy
2856            * paths. */
2857           if (src_is_url != srcs_are_urls)
2858             return svn_error_create
2859               (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2860                _("Cannot mix repository and working copy sources"));
2861
2862           if (dst_is_url)
2863             pair->dst_abspath_or_url =
2864               svn_path_url_add_component2(dst_path_in, src_basename, pool);
2865           else
2866             pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2867                                                        src_basename, pool);
2868           APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2869         }
2870
2871       svn_pool_destroy(iterpool);
2872     }
2873   else
2874     {
2875       /* Only one source path. */
2876       svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2877       svn_client_copy_source_t *source =
2878         APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2879       svn_boolean_t src_is_url = svn_path_is_url(source->path);
2880
2881       if (src_is_url)
2882         pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2883       else
2884         SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2885                                         source->path, pool));
2886       pair->src_op_revision = *source->revision;
2887       pair->src_peg_revision = *source->peg_revision;
2888       pair->src_kind = svn_node_unknown;
2889
2890       SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2891                                         &pair->src_op_revision,
2892                                         src_is_url, TRUE, pool));
2893
2894       pair->dst_abspath_or_url = dst_path_in;
2895       APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2896     }
2897
2898   if (!srcs_are_urls && !dst_is_url)
2899     {
2900       apr_pool_t *iterpool = svn_pool_create(pool);
2901
2902       for (i = 0; i < copy_pairs->nelts; i++)
2903         {
2904           svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2905                                             svn_client__copy_pair_t *);
2906
2907           svn_pool_clear(iterpool);
2908
2909           if (svn_dirent_is_child(pair->src_abspath_or_url,
2910                                   pair->dst_abspath_or_url, iterpool))
2911             return svn_error_createf
2912               (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2913                _("Cannot copy path '%s' into its own child '%s'"),
2914                svn_dirent_local_style(pair->src_abspath_or_url, pool),
2915                svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2916         }
2917
2918       svn_pool_destroy(iterpool);
2919     }
2920
2921   /* A file external should not be moved since the file external is
2922      implemented as a switched file and it would delete the file the
2923      file external is switched to, which is not the behavior the user
2924      would probably want. */
2925   if (is_move && !srcs_are_urls)
2926     {
2927       apr_pool_t *iterpool = svn_pool_create(pool);
2928
2929       for (i = 0; i < copy_pairs->nelts; i++)
2930         {
2931           svn_client__copy_pair_t *pair =
2932             APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2933           svn_node_kind_t external_kind;
2934           const char *defining_abspath;
2935
2936           svn_pool_clear(iterpool);
2937
2938           SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2939           SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2940                                              NULL, NULL, NULL, ctx->wc_ctx,
2941                                              pair->src_abspath_or_url,
2942                                              pair->src_abspath_or_url, TRUE,
2943                                              iterpool, iterpool));
2944
2945           if (external_kind != svn_node_none)
2946             return svn_error_createf(
2947                      SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2948                      NULL,
2949                      _("Cannot move the external at '%s'; please "
2950                        "edit the svn:externals property on '%s'."),
2951                      svn_dirent_local_style(pair->src_abspath_or_url, pool),
2952                      svn_dirent_local_style(defining_abspath, pool));
2953         }
2954       svn_pool_destroy(iterpool);
2955     }
2956
2957   if (is_move)
2958     {
2959       /* Disallow moves between the working copy and the repository. */
2960       if (srcs_are_urls != dst_is_url)
2961         {
2962           return svn_error_create
2963             (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2964              _("Moves between the working copy and the repository are not "
2965                "supported"));
2966         }
2967
2968       /* Disallow moving any path/URL onto or into itself. */
2969       for (i = 0; i < copy_pairs->nelts; i++)
2970         {
2971           svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2972                                             svn_client__copy_pair_t *);
2973
2974           if (strcmp(pair->src_abspath_or_url,
2975                      pair->dst_abspath_or_url) == 0)
2976             return svn_error_createf(
2977               SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2978               srcs_are_urls ?
2979                 _("Cannot move URL '%s' into itself") :
2980                 _("Cannot move path '%s' into itself"),
2981               srcs_are_urls ?
2982                 pair->src_abspath_or_url :
2983                 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2984         }
2985     }
2986   else
2987     {
2988       if (!srcs_are_urls)
2989         {
2990           /* If we are doing a wc->* copy, but with an operational revision
2991              other than the working copy revision, we are really doing a
2992              repo->* copy, because we're going to need to get the rev from the
2993              repo. */
2994
2995           svn_boolean_t need_repos_op_rev = FALSE;
2996           svn_boolean_t need_repos_peg_rev = FALSE;
2997
2998           /* Check to see if any revision is something other than
2999              svn_opt_revision_unspecified or svn_opt_revision_working. */
3000           for (i = 0; i < copy_pairs->nelts; i++)
3001             {
3002               svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3003                                                 svn_client__copy_pair_t *);
3004
3005               if (NEED_REPOS_REVNUM(pair->src_op_revision))
3006                 need_repos_op_rev = TRUE;
3007
3008               if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3009                 need_repos_peg_rev = TRUE;
3010
3011               if (need_repos_op_rev || need_repos_peg_rev)
3012                 break;
3013             }
3014
3015           if (need_repos_op_rev || need_repos_peg_rev)
3016             {
3017               apr_pool_t *iterpool = svn_pool_create(pool);
3018
3019               for (i = 0; i < copy_pairs->nelts; i++)
3020                 {
3021                   const char *copyfrom_repos_root_url;
3022                   const char *copyfrom_repos_relpath;
3023                   const char *url;
3024                   svn_revnum_t copyfrom_rev;
3025                   svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3026                                                     svn_client__copy_pair_t *);
3027
3028                   svn_pool_clear(iterpool);
3029
3030                   SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3031
3032                   SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
3033                                                   &copyfrom_repos_relpath,
3034                                                   &copyfrom_repos_root_url,
3035                                                   NULL, NULL, NULL,
3036                                                   ctx->wc_ctx,
3037                                                   pair->src_abspath_or_url,
3038                                                   TRUE, iterpool, iterpool));
3039
3040                   if (copyfrom_repos_relpath)
3041                     url = svn_path_url_add_component2(copyfrom_repos_root_url,
3042                                                       copyfrom_repos_relpath,
3043                                                       pool);
3044                   else
3045                     return svn_error_createf
3046                       (SVN_ERR_ENTRY_MISSING_URL, NULL,
3047                        _("'%s' does not have a URL associated with it"),
3048                        svn_dirent_local_style(pair->src_abspath_or_url, pool));
3049
3050                   pair->src_abspath_or_url = url;
3051
3052                   if (!need_repos_peg_rev
3053                       || pair->src_peg_revision.kind == svn_opt_revision_base)
3054                     {
3055                       /* Default the peg revision to that of the WC entry. */
3056                       pair->src_peg_revision.kind = svn_opt_revision_number;
3057                       pair->src_peg_revision.value.number = copyfrom_rev;
3058                     }
3059
3060                   if (pair->src_op_revision.kind == svn_opt_revision_base)
3061                     {
3062                       /* Use the entry's revision as the operational rev. */
3063                       pair->src_op_revision.kind = svn_opt_revision_number;
3064                       pair->src_op_revision.value.number = copyfrom_rev;
3065                     }
3066                 }
3067
3068               svn_pool_destroy(iterpool);
3069               srcs_are_urls = TRUE;
3070             }
3071         }
3072     }
3073
3074   /* Now, call the right handler for the operation. */
3075   if ((! srcs_are_urls) && (! dst_is_url))
3076     {
3077       SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3078                                       metadata_only, ctx, pool, pool));
3079
3080       /* Copy or move all targets. */
3081       if (is_move)
3082         return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3083                                                  copy_pairs, dst_path_in,
3084                                                  allow_mixed_revisions,
3085                                                  metadata_only,
3086                                                  ctx, pool));
3087       else
3088         {
3089           /* We ignore these values, so assert the default value */
3090           SVN_ERR_ASSERT(allow_mixed_revisions);
3091           return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3092                                                     copy_pairs,
3093                                                     metadata_only,
3094                                                     pin_externals,
3095                                                     externals_to_pin,
3096                                                     ctx, pool));
3097         }
3098     }
3099   else if ((! srcs_are_urls) && (dst_is_url))
3100     {
3101       return svn_error_trace(
3102         wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3103                          commit_callback, commit_baton,
3104                          pin_externals, externals_to_pin, ctx, pool));
3105     }
3106   else if ((srcs_are_urls) && (! dst_is_url))
3107     {
3108       return svn_error_trace(
3109         repos_to_wc_copy(timestamp_sleep,
3110                          copy_pairs, make_parents, ignore_externals,
3111                          pin_externals, externals_to_pin, ctx, pool));
3112     }
3113   else
3114     {
3115       return svn_error_trace(
3116         repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3117                             commit_callback, commit_baton, ctx, is_move,
3118                             pin_externals, externals_to_pin, pool));
3119     }
3120 }
3121
3122
3123 \f
3124 /* Public Interfaces */
3125 svn_error_t *
3126 svn_client_copy7(const apr_array_header_t *sources,
3127                  const char *dst_path,
3128                  svn_boolean_t copy_as_child,
3129                  svn_boolean_t make_parents,
3130                  svn_boolean_t ignore_externals,
3131                  svn_boolean_t metadata_only,
3132                  svn_boolean_t pin_externals,
3133                  const apr_hash_t *externals_to_pin,
3134                  const apr_hash_t *revprop_table,
3135                  svn_commit_callback2_t commit_callback,
3136                  void *commit_baton,
3137                  svn_client_ctx_t *ctx,
3138                  apr_pool_t *pool)
3139 {
3140   svn_error_t *err;
3141   svn_boolean_t timestamp_sleep = FALSE;
3142   apr_pool_t *subpool = svn_pool_create(pool);
3143
3144   if (sources->nelts > 1 && !copy_as_child)
3145     return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3146                             NULL, NULL);
3147
3148   err = try_copy(&timestamp_sleep,
3149                  sources, dst_path,
3150                  FALSE /* is_move */,
3151                  TRUE /* allow_mixed_revisions */,
3152                  metadata_only,
3153                  make_parents,
3154                  ignore_externals,
3155                  pin_externals,
3156                  externals_to_pin,
3157                  revprop_table,
3158                  commit_callback, commit_baton,
3159                  ctx,
3160                  subpool);
3161
3162   /* If the destination exists, try to copy the sources as children of the
3163      destination. */
3164   if (copy_as_child && err && (sources->nelts == 1)
3165         && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3166             || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3167     {
3168       const char *src_path = APR_ARRAY_IDX(sources, 0,
3169                                            svn_client_copy_source_t *)->path;
3170       const char *src_basename;
3171       svn_boolean_t src_is_url = svn_path_is_url(src_path);
3172       svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3173
3174       svn_error_clear(err);
3175       svn_pool_clear(subpool);
3176
3177       src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3178                                 : svn_dirent_basename(src_path, subpool);
3179       dst_path
3180         = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3181                                                    subpool)
3182                      : svn_dirent_join(dst_path, src_basename, subpool);
3183
3184       err = try_copy(&timestamp_sleep,
3185                      sources, dst_path,
3186                      FALSE /* is_move */,
3187                      TRUE /* allow_mixed_revisions */,
3188                      metadata_only,
3189                      make_parents,
3190                      ignore_externals,
3191                      pin_externals,
3192                      externals_to_pin,
3193                      revprop_table,
3194                      commit_callback, commit_baton,
3195                      ctx,
3196                      subpool);
3197     }
3198
3199   /* Sleep if required.  DST_PATH is not a URL in these cases. */
3200   if (timestamp_sleep)
3201     svn_io_sleep_for_timestamps(dst_path, subpool);
3202
3203   svn_pool_destroy(subpool);
3204   return svn_error_trace(err);
3205 }
3206
3207
3208 svn_error_t *
3209 svn_client_move7(const apr_array_header_t *src_paths,
3210                  const char *dst_path,
3211                  svn_boolean_t move_as_child,
3212                  svn_boolean_t make_parents,
3213                  svn_boolean_t allow_mixed_revisions,
3214                  svn_boolean_t metadata_only,
3215                  const apr_hash_t *revprop_table,
3216                  svn_commit_callback2_t commit_callback,
3217                  void *commit_baton,
3218                  svn_client_ctx_t *ctx,
3219                  apr_pool_t *pool)
3220 {
3221   const svn_opt_revision_t head_revision
3222     = { svn_opt_revision_head, { 0 } };
3223   svn_error_t *err;
3224   svn_boolean_t timestamp_sleep = FALSE;
3225   int i;
3226   apr_pool_t *subpool = svn_pool_create(pool);
3227   apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3228                                   sizeof(const svn_client_copy_source_t *));
3229
3230   if (src_paths->nelts > 1 && !move_as_child)
3231     return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3232                             NULL, NULL);
3233
3234   for (i = 0; i < src_paths->nelts; i++)
3235     {
3236       const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3237       svn_client_copy_source_t *copy_source = apr_palloc(pool,
3238                                                          sizeof(*copy_source));
3239
3240       copy_source->path = src_path;
3241       copy_source->revision = &head_revision;
3242       copy_source->peg_revision = &head_revision;
3243
3244       APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3245     }
3246
3247   err = try_copy(&timestamp_sleep,
3248                  sources, dst_path,
3249                  TRUE /* is_move */,
3250                  allow_mixed_revisions,
3251                  metadata_only,
3252                  make_parents,
3253                  FALSE /* ignore_externals */,
3254                  FALSE /* pin_externals */,
3255                  NULL /* externals_to_pin */,
3256                  revprop_table,
3257                  commit_callback, commit_baton,
3258                  ctx,
3259                  subpool);
3260
3261   /* If the destination exists, try to move the sources as children of the
3262      destination. */
3263   if (move_as_child && err && (src_paths->nelts == 1)
3264         && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3265             || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3266     {
3267       const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3268       const char *src_basename;
3269       svn_boolean_t src_is_url = svn_path_is_url(src_path);
3270       svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3271
3272       svn_error_clear(err);
3273       svn_pool_clear(subpool);
3274
3275       src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3276                                 : svn_dirent_basename(src_path, pool);
3277       dst_path
3278         = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3279                                                    subpool)
3280                      : svn_dirent_join(dst_path, src_basename, subpool);
3281
3282       err = try_copy(&timestamp_sleep,
3283                      sources, dst_path,
3284                      TRUE /* is_move */,
3285                      allow_mixed_revisions,
3286                      metadata_only,
3287                      make_parents,
3288                      FALSE /* ignore_externals */,
3289                      FALSE /* pin_externals */,
3290                      NULL /* externals_to_pin */,
3291                      revprop_table,
3292                      commit_callback, commit_baton,
3293                      ctx,
3294                      subpool);
3295     }
3296
3297   /* Sleep if required.  DST_PATH is not a URL in these cases. */
3298   if (timestamp_sleep)
3299     svn_io_sleep_for_timestamps(dst_path, subpool);
3300
3301   svn_pool_destroy(subpool);
3302   return svn_error_trace(err);
3303 }