]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_client/copy.c
MFV r337818:
[FreeBSD/FreeBSD.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 (dst_parent_kind == svn_node_none)
1061         {
1062           if (make_parents)
1063             SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1064                                                    TRUE, ctx, iterpool));
1065           else
1066             {
1067               SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1068                                         &dst_parent_kind, scratch_pool));
1069               return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1070                                        (dst_parent_kind == svn_node_dir)
1071                                          ? _("Directory '%s' is not under "
1072                                              "version control")
1073                                          : _("Path '%s' is not a directory"),
1074                                        svn_dirent_local_style(
1075                                          pair->dst_parent_abspath,
1076                                          scratch_pool));
1077             }
1078         }
1079       else if (dst_parent_kind != svn_node_dir)
1080         {
1081           return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1082                                    _("Path '%s' is not a directory"),
1083                                    svn_dirent_local_style(
1084                                      pair->dst_parent_abspath, scratch_pool));
1085         }
1086
1087       SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1088                                 &dst_parent_kind, scratch_pool));
1089
1090       if (dst_parent_kind != svn_node_dir)
1091         return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1092                                  _("Path '%s' is not a directory"),
1093                                  svn_dirent_local_style(
1094                                      pair->dst_parent_abspath, scratch_pool));
1095     }
1096
1097   svn_pool_destroy(iterpool);
1098
1099   return SVN_NO_ERROR;
1100 }
1101
1102 static svn_error_t *
1103 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1104                         svn_boolean_t make_parents,
1105                         svn_boolean_t is_move,
1106                         svn_boolean_t metadata_only,
1107                         svn_client_ctx_t *ctx,
1108                         apr_pool_t *result_pool,
1109                         apr_pool_t *scratch_pool)
1110 {
1111   int i;
1112   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1113
1114   /* Check that all of our SRCs exist. */
1115   for (i = 0; i < copy_pairs->nelts; i++)
1116     {
1117       svn_boolean_t deleted_ok;
1118       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1119                                                     svn_client__copy_pair_t *);
1120       svn_pool_clear(iterpool);
1121
1122       deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1123                     || pair->src_op_revision.kind == svn_opt_revision_base);
1124
1125       /* Verify that SRC_PATH exists. */
1126       SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1127                                pair->src_abspath_or_url,
1128                                deleted_ok, FALSE, iterpool));
1129       if (pair->src_kind == svn_node_none)
1130         return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1131                                  _("Path '%s' does not exist"),
1132                                  svn_dirent_local_style(
1133                                         pair->src_abspath_or_url,
1134                                         scratch_pool));
1135     }
1136
1137   SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1138                          result_pool, iterpool));
1139
1140   svn_pool_destroy(iterpool);
1141
1142   return SVN_NO_ERROR;
1143 }
1144
1145
1146 /* Path-specific state used as part of path_driver_cb_baton. */
1147 typedef struct path_driver_info_t
1148 {
1149   const char *src_url;
1150   const char *src_path;
1151   const char *dst_path;
1152   svn_node_kind_t src_kind;
1153   svn_revnum_t src_revnum;
1154   svn_boolean_t resurrection;
1155   svn_boolean_t dir_add;
1156   svn_string_t *mergeinfo;  /* the new mergeinfo for the target */
1157   svn_string_t *externals; /* new externals definitions for the target */
1158   svn_boolean_t only_pin_externals;
1159 } path_driver_info_t;
1160
1161
1162 /* The baton used with the path_driver_cb_func() callback for a copy
1163    or move operation. */
1164 struct path_driver_cb_baton
1165 {
1166   /* The editor (and its state) used to perform the operation. */
1167   const svn_delta_editor_t *editor;
1168   void *edit_baton;
1169
1170   /* A hash of path -> path_driver_info_t *'s. */
1171   apr_hash_t *action_hash;
1172
1173   /* Whether the operation is a move or copy. */
1174   svn_boolean_t is_move;
1175 };
1176
1177 static svn_error_t *
1178 path_driver_cb_func(void **dir_baton,
1179                     void *parent_baton,
1180                     void *callback_baton,
1181                     const char *path,
1182                     apr_pool_t *pool)
1183 {
1184   struct path_driver_cb_baton *cb_baton = callback_baton;
1185   svn_boolean_t do_delete = FALSE, do_add = FALSE;
1186   path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1187
1188   /* Initialize return value. */
1189   *dir_baton = NULL;
1190
1191   /* This function should never get an empty PATH.  We can neither
1192      create nor delete the empty PATH, so if someone is calling us
1193      with such, the code is just plain wrong. */
1194   SVN_ERR_ASSERT(! svn_path_is_empty(path));
1195
1196   /* Check to see if we need to add the path as a parent directory. */
1197   if (path_info->dir_add)
1198     {
1199       return cb_baton->editor->add_directory(path, parent_baton, NULL,
1200                                              SVN_INVALID_REVNUM, pool,
1201                                              dir_baton);
1202     }
1203
1204   /* If this is a resurrection, we know the source and dest paths are
1205      the same, and that our driver will only be calling us once.  */
1206   if (path_info->resurrection)
1207     {
1208       /* If this is a move, we do nothing.  Otherwise, we do the copy.  */
1209       if (! cb_baton->is_move)
1210         do_add = TRUE;
1211     }
1212   /* Not a resurrection. */
1213   else
1214     {
1215       /* If this is a move, we check PATH to see if it is the source
1216          or the destination of the move. */
1217       if (cb_baton->is_move)
1218         {
1219           if (strcmp(path_info->src_path, path) == 0)
1220             do_delete = TRUE;
1221           else
1222             do_add = TRUE;
1223         }
1224       /* Not a move?  This must just be the copy addition. */
1225       else
1226         {
1227           do_add = !path_info->only_pin_externals;
1228         }
1229     }
1230
1231   if (do_delete)
1232     {
1233       SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1234                                              parent_baton, pool));
1235     }
1236   if (do_add)
1237     {
1238       SVN_ERR(svn_path_check_valid(path, pool));
1239
1240       if (path_info->src_kind == svn_node_file)
1241         {
1242           void *file_baton;
1243           SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1244                                              path_info->src_url,
1245                                              path_info->src_revnum,
1246                                              pool, &file_baton));
1247           if (path_info->mergeinfo)
1248             SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1249                                                        SVN_PROP_MERGEINFO,
1250                                                        path_info->mergeinfo,
1251                                                        pool));
1252           SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1253         }
1254       else
1255         {
1256           SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1257                                                   path_info->src_url,
1258                                                   path_info->src_revnum,
1259                                                   pool, dir_baton));
1260           if (path_info->mergeinfo)
1261             SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1262                                                       SVN_PROP_MERGEINFO,
1263                                                       path_info->mergeinfo,
1264                                                       pool));
1265         }
1266     }
1267
1268   if (path_info->externals)
1269     {
1270       if (*dir_baton == NULL)
1271         SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1272                                                  SVN_INVALID_REVNUM,
1273                                                  pool, dir_baton));
1274
1275       SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1276                                                 path_info->externals, pool));
1277     }
1278
1279   return SVN_NO_ERROR;
1280 }
1281
1282
1283 /* Starting with the path DIR relative to the RA_SESSION's session
1284    URL, work up through DIR's parents until an existing node is found.
1285    Push each nonexistent path onto the array NEW_DIRS, allocating in
1286    POOL.  Raise an error if the existing node is not a directory.
1287
1288    ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1289    ### implementation susceptible to race conditions.  */
1290 static svn_error_t *
1291 find_absent_parents1(svn_ra_session_t *ra_session,
1292                      const char *dir,
1293                      apr_array_header_t *new_dirs,
1294                      apr_pool_t *pool)
1295 {
1296   svn_node_kind_t kind;
1297   apr_pool_t *iterpool = svn_pool_create(pool);
1298
1299   SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1300                             iterpool));
1301
1302   while (kind == svn_node_none)
1303     {
1304       svn_pool_clear(iterpool);
1305
1306       APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1307       dir = svn_dirent_dirname(dir, pool);
1308
1309       SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1310                                 &kind, iterpool));
1311     }
1312
1313   if (kind != svn_node_dir)
1314     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1315                              _("Path '%s' already exists, but is not a "
1316                                "directory"), dir);
1317
1318   svn_pool_destroy(iterpool);
1319   return SVN_NO_ERROR;
1320 }
1321
1322 /* Starting with the URL *TOP_DST_URL which is also the root of
1323    RA_SESSION, work up through its parents until an existing node is
1324    found. Push each nonexistent URL onto the array NEW_DIRS,
1325    allocating in POOL.  Raise an error if the existing node is not a
1326    directory.
1327
1328    Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1329
1330    ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1331    ### implementation susceptible to race conditions.  */
1332 static svn_error_t *
1333 find_absent_parents2(svn_ra_session_t *ra_session,
1334                      const char **top_dst_url,
1335                      apr_array_header_t *new_dirs,
1336                      apr_pool_t *pool)
1337 {
1338   const char *root_url = *top_dst_url;
1339   svn_node_kind_t kind;
1340
1341   SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1342                             pool));
1343
1344   while (kind == svn_node_none)
1345     {
1346       APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1347       root_url = svn_uri_dirname(root_url, pool);
1348
1349       SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1350       SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1351                                 pool));
1352     }
1353
1354   if (kind != svn_node_dir)
1355     return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1356                 _("Path '%s' already exists, but is not a directory"),
1357                 root_url);
1358
1359   *top_dst_url = root_url;
1360   return SVN_NO_ERROR;
1361 }
1362
1363 /* Queue property changes for pinning svn:externals properties set on
1364  * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1365  * is keyed by the relative path of each descendant which should have some
1366  * or all of its externals pinned, with the corresponding pinned svn:externals
1367  * properties as values. Property changes are queued in a new list of path
1368  * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1369  * existing item is found for the descendant. Allocate results in RESULT_POOL.
1370  * Use SCRATCH_POOL for temporary allocations. */
1371 static svn_error_t *
1372 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1373                                   apr_array_header_t *path_infos,
1374                                   apr_hash_t *pinned_externals,
1375                                   path_driver_info_t *parent_info,
1376                                   apr_pool_t *result_pool,
1377                                   apr_pool_t *scratch_pool)
1378 {
1379   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1380   apr_hash_index_t *hi;
1381
1382   for (hi = apr_hash_first(scratch_pool, pinned_externals);
1383        hi;
1384        hi = apr_hash_next(hi))
1385     {
1386       const char *dst_relpath = apr_hash_this_key(hi);
1387       svn_string_t *externals_prop = apr_hash_this_val(hi);
1388       const char *src_url;
1389       path_driver_info_t *info;
1390       int i;
1391
1392       svn_pool_clear(iterpool);
1393
1394       src_url = svn_path_url_add_component2(parent_info->src_url,
1395                                             dst_relpath, iterpool);
1396
1397       /* Try to find a path info the external change can be applied to. */
1398       info = NULL;
1399       for (i = 0; i < path_infos->nelts; i++)
1400         {
1401           path_driver_info_t *existing_info;
1402
1403           existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1404           if (strcmp(src_url, existing_info->src_url) == 0)
1405             {
1406               info = existing_info;
1407               break;
1408             }
1409         }
1410
1411       if (info == NULL)
1412         {
1413           /* A copied-along child needs its externals pinned.
1414              Create a new path info for this property change. */
1415           info = apr_pcalloc(result_pool, sizeof(*info));
1416           info->src_url = svn_path_url_add_component2(
1417                                 parent_info->src_url, dst_relpath,
1418                                 result_pool);
1419           info->src_path = NULL; /* Only needed on copied dirs */
1420           info->dst_path = svn_relpath_join(parent_info->dst_path,
1421                                             dst_relpath,
1422                                             result_pool);
1423           info->src_kind = svn_node_dir;
1424           info->only_pin_externals = TRUE;
1425           APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1426         }
1427
1428       info->externals = externals_prop;
1429     }
1430
1431   svn_pool_destroy(iterpool);
1432
1433   return SVN_NO_ERROR;
1434 }
1435
1436 static svn_error_t *
1437 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1438                     svn_boolean_t make_parents,
1439                     const apr_hash_t *revprop_table,
1440                     svn_commit_callback2_t commit_callback,
1441                     void *commit_baton,
1442                     svn_client_ctx_t *ctx,
1443                     svn_boolean_t is_move,
1444                     svn_boolean_t pin_externals,
1445                     const apr_hash_t *externals_to_pin,
1446                     apr_pool_t *pool)
1447 {
1448   svn_error_t *err;
1449   apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1450                                              sizeof(const char *));
1451   apr_hash_t *action_hash = apr_hash_make(pool);
1452   apr_array_header_t *path_infos;
1453   const char *top_url, *top_url_all, *top_url_dst;
1454   const char *message, *repos_root;
1455   svn_ra_session_t *ra_session = NULL;
1456   const svn_delta_editor_t *editor;
1457   void *edit_baton;
1458   struct path_driver_cb_baton cb_baton;
1459   apr_array_header_t *new_dirs = NULL;
1460   apr_hash_t *commit_revprops;
1461   apr_array_header_t *pin_externals_only_infos = NULL;
1462   int i;
1463   svn_client__copy_pair_t *first_pair =
1464     APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1465
1466   /* Open an RA session to the first copy pair's destination.  We'll
1467      be verifying that every one of our copy source and destination
1468      URLs is or is beneath this sucker's repository root URL as a form
1469      of a cheap(ish) sanity check.  */
1470   SVN_ERR(svn_client_open_ra_session2(&ra_session,
1471                                       first_pair->src_abspath_or_url, NULL,
1472                                       ctx, pool, pool));
1473   SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1474
1475   /* Verify that sources and destinations are all at or under
1476      REPOS_ROOT.  While here, create a path_info struct for each
1477      src/dst pair and initialize portions of it with normalized source
1478      location information.  */
1479   path_infos = apr_array_make(pool, copy_pairs->nelts,
1480                               sizeof(path_driver_info_t *));
1481   for (i = 0; i < copy_pairs->nelts; i++)
1482     {
1483       path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1484       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1485                                                     svn_client__copy_pair_t *);
1486       apr_hash_t *mergeinfo;
1487
1488       /* Are the source and destination URLs at or under REPOS_ROOT? */
1489       if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1490              && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1491         return svn_error_create
1492           (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1493            _("Source and destination URLs appear not to point to the "
1494              "same repository."));
1495
1496       /* Run the history function to get the source's URL and revnum in the
1497          operational revision. */
1498       SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1499       SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1500                                           &pair->src_revnum,
1501                                           NULL, NULL,
1502                                           ra_session,
1503                                           pair->src_abspath_or_url,
1504                                           &pair->src_peg_revision,
1505                                           &pair->src_op_revision, NULL,
1506                                           ctx, pool));
1507
1508       /* Go ahead and grab mergeinfo from the source, too. */
1509       SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1510       SVN_ERR(svn_client__get_repos_mergeinfo(
1511                 &mergeinfo, ra_session,
1512                 pair->src_abspath_or_url, pair->src_revnum,
1513                 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1514       if (mergeinfo)
1515         SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1516
1517       /* Plop an INFO structure onto our array thereof. */
1518       info->src_url = pair->src_abspath_or_url;
1519       info->src_revnum = pair->src_revnum;
1520       info->resurrection = FALSE;
1521       APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1522     }
1523
1524   /* If this is a move, we have to open our session to the longest
1525      path common to all SRC_URLS and DST_URLS in the repository so we
1526      can do existence checks on all paths, and so we can operate on
1527      all paths in the case of a move.  But if this is *not* a move,
1528      then opening our session at the longest path common to sources
1529      *and* destinations might be an optimization when the user is
1530      authorized to access all that stuff, but could cause the
1531      operation to fail altogether otherwise.  See issue #3242.  */
1532   SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1533                                   pool));
1534   top_url = is_move ? top_url_all : top_url_dst;
1535
1536   /* Check each src/dst pair for resurrection, and verify that TOP_URL
1537      is anchored high enough to cover all the editor_t activities
1538      required for this operation.  */
1539   for (i = 0; i < copy_pairs->nelts; i++)
1540     {
1541       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1542                                                     svn_client__copy_pair_t *);
1543       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1544                                                path_driver_info_t *);
1545
1546       /* Source and destination are the same?  It's a resurrection. */
1547       if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1548         info->resurrection = TRUE;
1549
1550       /* We need to add each dst_URL, and (in a move) we'll need to
1551          delete each src_URL.  Our selection of TOP_URL so far ensures
1552          that all our destination URLs (and source URLs, for moves)
1553          are at least as deep as TOP_URL, but we need to make sure
1554          that TOP_URL is an *ancestor* of all our to-be-edited paths.
1555
1556          Issue #683 is demonstrates this scenario.  If you're
1557          resurrecting a deleted item like this: 'svn cp -rN src_URL
1558          dst_URL', then src_URL == dst_URL == top_url.  In this
1559          situation, we want to open an RA session to be at least the
1560          *parent* of all three. */
1561       if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1562           && (strcmp(top_url, repos_root) != 0))
1563         {
1564           top_url = svn_uri_dirname(top_url, pool);
1565         }
1566       if (is_move
1567           && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1568           && (strcmp(top_url, repos_root) != 0))
1569         {
1570           top_url = svn_uri_dirname(top_url, pool);
1571         }
1572     }
1573
1574   /* Point the RA session to our current TOP_URL. */
1575   SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1576
1577   /* If we're allowed to create nonexistent parent directories of our
1578      destinations, then make a list in NEW_DIRS of the parent
1579      directories of the destination that don't yet exist.  */
1580   if (make_parents)
1581     {
1582       new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1583
1584       /* If this is a move, TOP_URL is at least the common ancestor of
1585          all the paths (sources and destinations) involved.  Assuming
1586          the sources exist (which is fair, because if they don't, this
1587          whole operation will fail anyway), TOP_URL must also exist.
1588          So it's the paths between TOP_URL and the destinations which
1589          we have to check for existence.  But here, we take advantage
1590          of the knowledge of our caller.  We know that if there are
1591          multiple copy/move operations being requested, then the
1592          destinations of the copies/moves will all be siblings of one
1593          another.  Therefore, we need only to check for the
1594          nonexistent paths between TOP_URL and *one* of our
1595          destinations to find nonexistent parents of all of them.  */
1596       if (is_move)
1597         {
1598           /* Imagine a situation where the user tries to copy an
1599              existing source directory to nonexistent directory with
1600              --parents options specified:
1601
1602                 svn copy --parents URL/src URL/dst
1603
1604              where src exists and dst does not.  If the dirname of the
1605              destination path is equal to TOP_URL,
1606              do not try to add dst to the NEW_DIRS list since it
1607              will be added to the commit items array later in this
1608              function. */
1609           const char *dir = svn_uri_skip_ancestor(
1610                               top_url,
1611                               svn_uri_dirname(first_pair->dst_abspath_or_url,
1612                                               pool),
1613                               pool);
1614           if (dir && *dir)
1615             SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1616         }
1617       /* If, however, this is *not* a move, TOP_URL only points to the
1618          common ancestor of our destination path(s), or possibly one
1619          level higher.  We'll need to do an existence crawl toward the
1620          root of the repository, starting with one of our destinations
1621          (see "... take advantage of the knowledge of our caller ..."
1622          above), and possibly adjusting TOP_URL as we go. */
1623       else
1624         {
1625           apr_array_header_t *new_urls =
1626             apr_array_make(pool, 0, sizeof(const char *));
1627           SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1628
1629           /* Convert absolute URLs into relpaths relative to TOP_URL. */
1630           for (i = 0; i < new_urls->nelts; i++)
1631             {
1632               const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1633               const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1634
1635               APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1636             }
1637         }
1638     }
1639
1640   /* For each src/dst pair, check to see if that SRC_URL is a child of
1641      the DST_URL (excepting the case where DST_URL is the repo root).
1642      If it is, and the parent of DST_URL is the current TOP_URL, then we
1643      need to reparent the session one directory higher, the parent of
1644      the DST_URL. */
1645   for (i = 0; i < copy_pairs->nelts; i++)
1646     {
1647       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1648                                                     svn_client__copy_pair_t *);
1649       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1650                                                path_driver_info_t *);
1651       const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1652                                                   pair->src_abspath_or_url,
1653                                                   pool);
1654
1655       if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1656           && (relpath != NULL && *relpath != '\0'))
1657         {
1658           info->resurrection = TRUE;
1659           top_url = svn_uri_get_longest_ancestor(
1660                             top_url,
1661                             svn_uri_dirname(pair->dst_abspath_or_url, pool),
1662                             pool);
1663           SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1664         }
1665     }
1666
1667   /* Get the portions of the SRC and DST URLs that are relative to
1668      TOP_URL (URI-decoding them while we're at it), verify that the
1669      source exists and the proposed destination does not, and toss
1670      what we've learned into the INFO array.  (For copies -- that is,
1671      non-moves -- the relative source URL NULL because it isn't a
1672      child of the TOP_URL at all.  That's okay, we'll deal with
1673      it.)  */
1674   for (i = 0; i < copy_pairs->nelts; i++)
1675     {
1676       svn_client__copy_pair_t *pair =
1677         APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1678       path_driver_info_t *info =
1679         APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1680       svn_node_kind_t dst_kind;
1681       const char *src_rel, *dst_rel;
1682
1683       src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1684       if (src_rel)
1685         {
1686           SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1687                                     &info->src_kind, pool));
1688         }
1689       else
1690         {
1691           const char *old_url;
1692
1693           src_rel = NULL;
1694           SVN_ERR_ASSERT(! is_move);
1695
1696           SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1697                                                     pair->src_abspath_or_url,
1698                                                     pool));
1699           SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1700                                     &info->src_kind, pool));
1701           SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1702         }
1703       if (info->src_kind == svn_node_none)
1704         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1705                                  _("Path '%s' does not exist in revision %ld"),
1706                                  pair->src_abspath_or_url, pair->src_revnum);
1707
1708       /* Figure out the basename that will result from this operation,
1709          and ensure that we aren't trying to overwrite existing paths.  */
1710       dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1711       SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1712                                 &dst_kind, pool));
1713       if (dst_kind != svn_node_none)
1714         return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1715                                  _("Path '%s' already exists"),
1716                                  pair->dst_abspath_or_url);
1717
1718       /* More info for our INFO structure.  */
1719       info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1720       info->dst_path = dst_rel;
1721
1722       svn_hash_sets(action_hash, info->dst_path, info);
1723       if (is_move && (! info->resurrection))
1724         svn_hash_sets(action_hash, info->src_path, info);
1725
1726       if (pin_externals)
1727         {
1728           apr_hash_t *pinned_externals;
1729
1730           SVN_ERR(resolve_pinned_externals(&pinned_externals,
1731                                            externals_to_pin, pair,
1732                                            ra_session, repos_root,
1733                                            ctx, pool, pool));
1734           if (pin_externals_only_infos == NULL)
1735             {
1736               pin_externals_only_infos =
1737                 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1738             }
1739           SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1740                                                     path_infos,
1741                                                     pinned_externals,
1742                                                     info, pool, pool));
1743         }
1744     }
1745
1746   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1747     {
1748       /* Produce a list of new paths to add, and provide it to the
1749          mechanism used to acquire a log message. */
1750       svn_client_commit_item3_t *item;
1751       const char *tmp_file;
1752       apr_array_header_t *commit_items
1753         = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1754
1755       /* Add any intermediate directories to the message */
1756       if (make_parents)
1757         {
1758           for (i = 0; i < new_dirs->nelts; i++)
1759             {
1760               const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1761
1762               item = svn_client_commit_item3_create(pool);
1763               item->url = svn_path_url_add_component2(top_url, relpath, pool);
1764               item->kind = svn_node_dir;
1765               item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1766               APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1767             }
1768         }
1769
1770       for (i = 0; i < path_infos->nelts; i++)
1771         {
1772           path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1773                                                    path_driver_info_t *);
1774
1775           item = svn_client_commit_item3_create(pool);
1776           item->url = svn_path_url_add_component2(top_url, info->dst_path,
1777                                                   pool);
1778           item->kind = info->src_kind;
1779           item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1780                               | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1781           item->copyfrom_url = info->src_url;
1782           item->copyfrom_rev = info->src_revnum;
1783           APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1784
1785           if (is_move && (! info->resurrection))
1786             {
1787               item = svn_client_commit_item3_create(pool);
1788               item->url = svn_path_url_add_component2(top_url, info->src_path,
1789                                                       pool);
1790               item->kind = info->src_kind;
1791               item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1792               APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1793             }
1794         }
1795
1796       SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1797                                       ctx, pool));
1798       if (! message)
1799         return SVN_NO_ERROR;
1800     }
1801   else
1802     message = "";
1803
1804   /* Setup our PATHS for the path-based editor drive. */
1805   /* First any intermediate directories. */
1806   if (make_parents)
1807     {
1808       for (i = 0; i < new_dirs->nelts; i++)
1809         {
1810           const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1811           path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1812
1813           info->dst_path = relpath;
1814           info->dir_add = TRUE;
1815
1816           APR_ARRAY_PUSH(paths, const char *) = relpath;
1817           svn_hash_sets(action_hash, relpath, info);
1818         }
1819     }
1820
1821   /* Then our copy destinations and move sources (if any). */
1822   for (i = 0; i < path_infos->nelts; i++)
1823     {
1824       path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1825                                                path_driver_info_t *);
1826
1827       APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1828       if (is_move && (! info->resurrection))
1829         APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1830     }
1831
1832   /* Add any items which only need their externals pinned. */
1833   if (pin_externals_only_infos)
1834     {
1835       for (i = 0; i < pin_externals_only_infos->nelts; i++)
1836         {
1837           path_driver_info_t *info;
1838
1839           info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1840           APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1841           svn_hash_sets(action_hash, info->dst_path, info);
1842         }
1843     }
1844
1845   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1846                                            message, ctx, pool));
1847
1848   /* Fetch RA commit editor. */
1849   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1850                         svn_client__get_shim_callbacks(ctx->wc_ctx,
1851                                                        NULL, pool)));
1852   SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1853                                     commit_revprops,
1854                                     commit_callback,
1855                                     commit_baton,
1856                                     NULL, TRUE, /* No lock tokens */
1857                                     pool));
1858
1859   /* Setup the callback baton. */
1860   cb_baton.editor = editor;
1861   cb_baton.edit_baton = edit_baton;
1862   cb_baton.action_hash = action_hash;
1863   cb_baton.is_move = is_move;
1864
1865   /* Call the path-based editor driver. */
1866   err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1867                                path_driver_cb_func, &cb_baton, pool);
1868   if (err)
1869     {
1870       /* At least try to abort the edit (and fs txn) before throwing err. */
1871       return svn_error_compose_create(
1872                     err,
1873                     editor->abort_edit(edit_baton, pool));
1874     }
1875
1876   if (ctx->notify_func2)
1877     {
1878       svn_wc_notify_t *notify;
1879       notify = svn_wc_create_notify_url(top_url,
1880                                         svn_wc_notify_commit_finalizing,
1881                                         pool);
1882       ctx->notify_func2(ctx->notify_baton2, notify, pool);
1883     }
1884
1885   /* Close the edit. */
1886   return svn_error_trace(editor->close_edit(edit_baton, pool));
1887 }
1888
1889 /* Baton for check_url_kind */
1890 struct check_url_kind_baton
1891 {
1892   svn_ra_session_t *session;
1893   const char *repos_root_url;
1894   svn_boolean_t should_reparent;
1895 };
1896
1897 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1898 static svn_error_t *
1899 check_url_kind(void *baton,
1900                svn_node_kind_t *kind,
1901                const char *url,
1902                svn_revnum_t revision,
1903                apr_pool_t *scratch_pool)
1904 {
1905   struct check_url_kind_baton *cukb = baton;
1906
1907   /* If we don't have a session or can't use the session, get one */
1908   if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1909     *kind = svn_node_none;
1910   else
1911     {
1912       cukb->should_reparent = TRUE;
1913
1914       SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1915
1916       SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1917                                 kind, scratch_pool));
1918     }
1919
1920   return SVN_NO_ERROR;
1921 }
1922
1923 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1924  * in the COMMIT_ITEMS list.
1925  * If the list does not already have a commit item for COMMIT_URL
1926  * add a new commit item for the property change.
1927  * Allocate results in RESULT_POOL.
1928  * Use SCRATCH_POOL for temporary allocations. */
1929 static svn_error_t *
1930 queue_prop_change_commit_items(const char *local_abspath,
1931                                const char *commit_url,
1932                                apr_array_header_t *commit_items,
1933                                const char *propname,
1934                                svn_string_t *propval,
1935                                apr_pool_t *result_pool,
1936                                apr_pool_t *scratch_pool)
1937 {
1938   svn_client_commit_item3_t *item = NULL;
1939   svn_prop_t *prop;
1940   int i;
1941
1942   for (i = 0; i < commit_items->nelts; i++)
1943     {
1944       svn_client_commit_item3_t *existing_item;
1945
1946       existing_item = APR_ARRAY_IDX(commit_items, i,
1947                                     svn_client_commit_item3_t *);
1948       if (strcmp(existing_item->url, commit_url) == 0)
1949         {
1950           item = existing_item;
1951           break;
1952         }
1953     }
1954
1955   if (item == NULL)
1956     {
1957       item = svn_client_commit_item3_create(result_pool);
1958       item->path = local_abspath;
1959       item->url = commit_url;
1960       item->kind = svn_node_dir;
1961       item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1962
1963       item->incoming_prop_changes = apr_array_make(result_pool, 1,
1964                                                    sizeof(svn_prop_t *));
1965       APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1966     }
1967   else
1968     item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1969
1970   if (item->outgoing_prop_changes == NULL)
1971     item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1972                                                  sizeof(svn_prop_t *));
1973
1974   prop = apr_palloc(result_pool, sizeof(*prop));
1975   prop->name = propname;
1976   prop->value = propval;
1977   APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1978
1979   return SVN_NO_ERROR;
1980 }
1981
1982 /* ### Copy ...
1983  * COMMIT_INFO_P is ...
1984  * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1985  * and each 'dst_abspath_or_url' is a URL.
1986  * MAKE_PARENTS is ...
1987  * REVPROP_TABLE is ...
1988  * CTX is ... */
1989 static svn_error_t *
1990 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1991                  svn_boolean_t make_parents,
1992                  const apr_hash_t *revprop_table,
1993                  svn_commit_callback2_t commit_callback,
1994                  void *commit_baton,
1995                  svn_boolean_t pin_externals,
1996                  const apr_hash_t *externals_to_pin,
1997                  svn_client_ctx_t *ctx,
1998                  apr_pool_t *scratch_pool)
1999 {
2000   const char *message;
2001   const char *top_src_path, *top_dst_url;
2002   struct check_url_kind_baton cukb;
2003   const char *top_src_abspath;
2004   svn_ra_session_t *ra_session;
2005   const svn_delta_editor_t *editor;
2006 #ifdef ENABLE_EV2_SHIMS
2007   apr_hash_t *relpath_map = NULL;
2008 #endif
2009   void *edit_baton;
2010   svn_client__committables_t *committables;
2011   apr_array_header_t *commit_items;
2012   apr_pool_t *iterpool;
2013   apr_array_header_t *new_dirs = NULL;
2014   apr_hash_t *commit_revprops;
2015   svn_client__copy_pair_t *first_pair;
2016   apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2017   apr_array_header_t *commit_items_for_dav;
2018   int i;
2019
2020   /* Find the common root of all the source paths */
2021   SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2022                                   scratch_pool));
2023
2024   /* Do we need to lock the working copy?  1.6 didn't take a write
2025      lock, but what happens if the working copy changes during the copy
2026      operation? */
2027
2028   iterpool = svn_pool_create(scratch_pool);
2029
2030   /* Determine the longest common ancestor for the destinations, and open an RA
2031      session to that location. */
2032   /* ### But why start by getting the _parent_ of the first one? */
2033   /* --- That works because multiple destinations always point to the same
2034    *     directory. I'm rather wondering why we need to find a common
2035    *     destination parent here at all, instead of simply getting
2036    *     top_dst_url from get_copy_pair_ancestors() above?
2037    *     It looks like the entire block of code hanging off this comment
2038    *     is redundant. */
2039   first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2040   top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2041   for (i = 1; i < copy_pairs->nelts; i++)
2042     {
2043       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2044                                                     svn_client__copy_pair_t *);
2045       top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2046                                                  pair->dst_abspath_or_url,
2047                                                  scratch_pool);
2048     }
2049
2050   SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2051
2052   commit_items_for_dav = apr_array_make(session_pool, 0,
2053                                         sizeof(svn_client_commit_item3_t*));
2054
2055   /* Open a session to help while determining the exact targets */
2056   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2057                                                top_src_abspath,
2058                                                commit_items_for_dav,
2059                                                FALSE /* write_dav_props */,
2060                                                TRUE /* read_dav_props */,
2061                                                ctx,
2062                                                session_pool, session_pool));
2063
2064   /* If requested, determine the nearest existing parent of the destination,
2065      and reparent the ra session there. */
2066   if (make_parents)
2067     {
2068       new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2069       SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2070                                    scratch_pool));
2071     }
2072
2073   /* Figure out the basename that will result from each copy and check to make
2074      sure it doesn't exist already. */
2075   for (i = 0; i < copy_pairs->nelts; i++)
2076     {
2077       svn_node_kind_t dst_kind;
2078       const char *dst_rel;
2079       svn_client__copy_pair_t *pair =
2080         APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2081
2082       svn_pool_clear(iterpool);
2083       dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2084                                       iterpool);
2085       SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2086                                 &dst_kind, iterpool));
2087       if (dst_kind != svn_node_none)
2088         {
2089           return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2090                                    _("Path '%s' already exists"),
2091                                    pair->dst_abspath_or_url);
2092         }
2093     }
2094
2095   cukb.session = ra_session;
2096   SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2097   cukb.should_reparent = FALSE;
2098
2099   /* Crawl the working copy for commit items. */
2100   /* ### TODO: Pass check_url_func for issue #3314 handling */
2101   SVN_ERR(svn_client__get_copy_committables(&committables,
2102                                             copy_pairs,
2103                                             check_url_kind, &cukb,
2104                                             ctx, scratch_pool, iterpool));
2105
2106   /* The committables are keyed by the repository root */
2107   commit_items = svn_hash_gets(committables->by_repository,
2108                                cukb.repos_root_url);
2109   SVN_ERR_ASSERT(commit_items != NULL);
2110
2111   if (cukb.should_reparent)
2112     SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2113
2114   /* If we are creating intermediate directories, tack them onto the list
2115      of committables. */
2116   if (make_parents)
2117     {
2118       for (i = 0; i < new_dirs->nelts; i++)
2119         {
2120           const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2121           svn_client_commit_item3_t *item;
2122
2123           item = svn_client_commit_item3_create(scratch_pool);
2124           item->url = url;
2125           item->kind = svn_node_dir;
2126           item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2127           item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2128                                                        sizeof(svn_prop_t *));
2129           APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2130         }
2131     }
2132
2133   /* ### TODO: This extra loop would be unnecessary if this code lived
2134      ### in svn_client__get_copy_committables(), which is incidentally
2135      ### only used above (so should really be in this source file). */
2136   for (i = 0; i < copy_pairs->nelts; i++)
2137     {
2138       apr_hash_t *mergeinfo, *wc_mergeinfo;
2139       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2140                                                     svn_client__copy_pair_t *);
2141       svn_client_commit_item3_t *item =
2142         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2143       svn_client__pathrev_t *src_origin;
2144
2145       svn_pool_clear(iterpool);
2146
2147       SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2148                                              pair->src_abspath_or_url,
2149                                              ctx, iterpool, iterpool));
2150
2151       /* Set the mergeinfo for the destination to the combined merge
2152          info known to the WC and the repository. */
2153       /* Repository mergeinfo (or NULL if it's locally added)... */
2154       if (src_origin)
2155         SVN_ERR(svn_client__get_repos_mergeinfo(
2156                   &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2157                   svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2158       else
2159         mergeinfo = NULL;
2160       /* ... and WC mergeinfo. */
2161       SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2162                                           pair->src_abspath_or_url,
2163                                           iterpool, iterpool));
2164       if (wc_mergeinfo && mergeinfo)
2165         SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2166                                      iterpool));
2167       else if (! mergeinfo)
2168         mergeinfo = wc_mergeinfo;
2169
2170       if (mergeinfo)
2171         {
2172           /* Push a mergeinfo prop representing MERGEINFO onto the
2173            * OUTGOING_PROP_CHANGES array. */
2174
2175           svn_prop_t *mergeinfo_prop
2176                             = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2177           svn_string_t *prop_value;
2178
2179           SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2180                                           scratch_pool));
2181
2182           if (!item->outgoing_prop_changes)
2183             {
2184               item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2185                                                            sizeof(svn_prop_t *));
2186             }
2187
2188           mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2189           mergeinfo_prop->value = prop_value;
2190           APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2191             = mergeinfo_prop;
2192         }
2193
2194       if (pin_externals)
2195         {
2196           apr_hash_t *pinned_externals;
2197           apr_hash_index_t *hi;
2198
2199           SVN_ERR(resolve_pinned_externals(&pinned_externals,
2200                                            externals_to_pin, pair,
2201                                            ra_session, cukb.repos_root_url,
2202                                            ctx, scratch_pool, iterpool));
2203           for (hi = apr_hash_first(scratch_pool, pinned_externals);
2204                hi;
2205                hi = apr_hash_next(hi))
2206             {
2207               const char *dst_relpath = apr_hash_this_key(hi);
2208               svn_string_t *externals_propval = apr_hash_this_val(hi);
2209               const char *dst_url;
2210               const char *commit_url;
2211               const char *src_abspath;
2212
2213               if (svn_path_is_url(pair->dst_abspath_or_url))
2214                 dst_url = pair->dst_abspath_or_url;
2215               else
2216                 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2217                                              pair->dst_abspath_or_url,
2218                                              scratch_pool, iterpool));
2219               commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2220                                                        scratch_pool);
2221               src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2222                                             dst_relpath, iterpool);
2223               SVN_ERR(queue_prop_change_commit_items(src_abspath,
2224                                                      commit_url, commit_items,
2225                                                      SVN_PROP_EXTERNALS,
2226                                                      externals_propval,
2227                                                      scratch_pool, iterpool));
2228             }
2229         }
2230     }
2231
2232   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2233     {
2234       const char *tmp_file;
2235
2236       SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2237                                       ctx, scratch_pool));
2238       if (! message)
2239         {
2240           svn_pool_destroy(iterpool);
2241           svn_pool_destroy(session_pool);
2242           return SVN_NO_ERROR;
2243         }
2244     }
2245   else
2246     message = "";
2247
2248   /* Sort and condense our COMMIT_ITEMS. */
2249   SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2250                                             commit_items, scratch_pool));
2251
2252   /* Add the commit items to the DAV commit item list to provide access
2253      to dav properties (for pre http-v2 DAV) */
2254   apr_array_cat(commit_items_for_dav, commit_items);
2255
2256 #ifdef ENABLE_EV2_SHIMS
2257   if (commit_items)
2258     {
2259       relpath_map = apr_hash_make(scratch_pool);
2260       for (i = 0; i < commit_items->nelts; i++)
2261         {
2262           svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2263                                                   svn_client_commit_item3_t *);
2264           const char *relpath;
2265
2266           if (!item->path)
2267             continue;
2268
2269           svn_pool_clear(iterpool);
2270           SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2271                                           NULL, NULL,
2272                                           ctx->wc_ctx, item->path, FALSE,
2273                                           scratch_pool, iterpool));
2274           if (relpath)
2275             svn_hash_sets(relpath_map, relpath, item->path);
2276         }
2277     }
2278 #endif
2279
2280   SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2281
2282   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2283                                            message, ctx, session_pool));
2284
2285   /* Fetch RA commit editor. */
2286 #ifdef ENABLE_EV2_SHIMS
2287   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2288                         svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2289                                                        session_pool)));
2290 #endif
2291   SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2292                                     commit_revprops,
2293                                     commit_callback,
2294                                     commit_baton, NULL,
2295                                     TRUE, /* No lock tokens */
2296                                     session_pool));
2297
2298   /* Perform the commit. */
2299   SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2300                                   editor, edit_baton,
2301                                   NULL /* notify_path_prefix */,
2302                                   NULL, ctx, session_pool, session_pool),
2303             _("Commit failed (details follow):"));
2304
2305   svn_pool_destroy(iterpool);
2306   svn_pool_destroy(session_pool);
2307
2308   return SVN_NO_ERROR;
2309 }
2310
2311 /* A baton for notification_adjust_func(). */
2312 struct notification_adjust_baton
2313 {
2314   svn_wc_notify_func2_t inner_func;
2315   void *inner_baton;
2316   const char *checkout_abspath;
2317   const char *final_abspath;
2318 };
2319
2320 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2321  * baton is BATON->inner_baton) and adjusts the notification paths that
2322  * start with BATON->checkout_abspath to start instead with
2323  * BATON->final_abspath. */
2324 static void
2325 notification_adjust_func(void *baton,
2326                          const svn_wc_notify_t *notify,
2327                          apr_pool_t *pool)
2328 {
2329   struct notification_adjust_baton *nb = baton;
2330   svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2331   const char *relpath;
2332
2333   relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2334   inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2335
2336   if (nb->inner_func)
2337     nb->inner_func(nb->inner_baton, inner_notify, pool);
2338 }
2339
2340 /* Peform each individual copy operation for a repos -> wc copy.  A
2341    helper for repos_to_wc_copy().
2342
2343    Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2344 static svn_error_t *
2345 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2346                         svn_client__copy_pair_t *pair,
2347                         svn_boolean_t same_repositories,
2348                         svn_boolean_t ignore_externals,
2349                         svn_boolean_t pin_externals,
2350                         const apr_hash_t *externals_to_pin,
2351                         svn_ra_session_t *ra_session,
2352                         svn_client_ctx_t *ctx,
2353                         apr_pool_t *pool)
2354 {
2355   apr_hash_t *src_mergeinfo;
2356   const char *dst_abspath = pair->dst_abspath_or_url;
2357
2358   SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2359
2360   if (!same_repositories && ctx->notify_func2)
2361     {
2362       svn_wc_notify_t *notify;
2363       notify = svn_wc_create_notify_url(
2364                             pair->src_abspath_or_url,
2365                             svn_wc_notify_foreign_copy_begin,
2366                             pool);
2367       notify->kind = pair->src_kind;
2368       ctx->notify_func2(ctx->notify_baton2, notify, pool);
2369
2370       /* Allow a theoretical cancel to get through. */
2371       if (ctx->cancel_func)
2372         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2373     }
2374
2375   if (pair->src_kind == svn_node_dir)
2376     {
2377       if (same_repositories)
2378         {
2379           const char *tmpdir_abspath, *tmp_abspath;
2380
2381           /* Find a temporary location in which to check out the copy source. */
2382           SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2383                                      pool, pool));
2384
2385           SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2386                                            svn_io_file_del_on_close, pool, pool));
2387
2388           /* Make a new checkout of the requested source. While doing so,
2389            * resolve pair->src_revnum to an actual revision number in case it
2390            * was until now 'invalid' meaning 'head'.  Ask this function not to
2391            * sleep for timestamps, by passing a sleep_needed output param.
2392            * Send notifications for all nodes except the root node, and adjust
2393            * them to refer to the destination rather than this temporary path. */
2394           {
2395             svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2396             void *old_notify_baton2 = ctx->notify_baton2;
2397             struct notification_adjust_baton nb;
2398             svn_error_t *err;
2399
2400             nb.inner_func = ctx->notify_func2;
2401             nb.inner_baton = ctx->notify_baton2;
2402             nb.checkout_abspath = tmp_abspath;
2403             nb.final_abspath = dst_abspath;
2404             ctx->notify_func2 = notification_adjust_func;
2405             ctx->notify_baton2 = &nb;
2406
2407             /* Avoid a chicken-and-egg problem:
2408              * If pinning externals we'll need to adjust externals
2409              * properties before checking out any externals.
2410              * But copy needs to happen before pinning because else there
2411              * are no svn:externals properties to pin. */
2412             if (pin_externals)
2413               ignore_externals = TRUE;
2414
2415             err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2416                                                 pair->src_original,
2417                                                 tmp_abspath,
2418                                                 &pair->src_peg_revision,
2419                                                 &pair->src_op_revision,
2420                                                 svn_depth_infinity,
2421                                                 ignore_externals, FALSE,
2422                                                 ra_session, ctx, pool);
2423
2424             ctx->notify_func2 = old_notify_func2;
2425             ctx->notify_baton2 = old_notify_baton2;
2426
2427             SVN_ERR(err);
2428           }
2429
2430           *timestamp_sleep = TRUE;
2431
2432           /* Schedule dst_path for addition in parent, with copy history.
2433              Don't send any notification here.
2434              Then remove the temporary checkout's .svn dir in preparation for
2435              moving the rest of it into the final destination. */
2436           SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2437                                TRUE /* metadata_only */,
2438                                ctx->cancel_func, ctx->cancel_baton,
2439                                NULL, NULL, pool));
2440           SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2441                                              FALSE, pool, pool));
2442           SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2443                                                        tmp_abspath,
2444                                                        FALSE, FALSE,
2445                                                        ctx->cancel_func,
2446                                                        ctx->cancel_baton,
2447                                                        pool));
2448
2449           /* Move the temporary disk tree into place. */
2450           SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, pool));
2451         }
2452       else
2453         {
2454           *timestamp_sleep = TRUE;
2455
2456           SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2457                                            dst_abspath,
2458                                            &pair->src_peg_revision,
2459                                            &pair->src_op_revision,
2460                                            svn_depth_infinity,
2461                                            FALSE /* make_parents */,
2462                                            TRUE /* already_locked */,
2463                                            ctx, pool));
2464
2465           return SVN_NO_ERROR;
2466         }
2467
2468       if (pin_externals)
2469         {
2470           apr_hash_t *pinned_externals;
2471           apr_hash_index_t *hi;
2472           apr_pool_t *iterpool;
2473           const char *repos_root_url;
2474           apr_hash_t *new_externals;
2475           apr_hash_t *new_depths;
2476
2477           SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2478           SVN_ERR(resolve_pinned_externals(&pinned_externals,
2479                                            externals_to_pin, pair,
2480                                            ra_session, repos_root_url,
2481                                            ctx, pool, pool));
2482
2483           iterpool = svn_pool_create(pool);
2484           for (hi = apr_hash_first(pool, pinned_externals);
2485                hi;
2486                hi = apr_hash_next(hi))
2487             {
2488               const char *dst_relpath = apr_hash_this_key(hi);
2489               svn_string_t *externals_propval = apr_hash_this_val(hi);
2490               const char *local_abspath;
2491
2492               svn_pool_clear(iterpool);
2493
2494               local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2495                                               dst_relpath, iterpool);
2496               /* ### use a work queue? */
2497               SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2498                                        SVN_PROP_EXTERNALS, externals_propval,
2499                                        svn_depth_empty, TRUE /* skip_checks */,
2500                                        NULL  /* changelist_filter */,
2501                                        ctx->cancel_func, ctx->cancel_baton,
2502                                        NULL, NULL, /* no extra notification */
2503                                        iterpool));
2504             }
2505
2506           /* Now update all externals in the newly created copy. */
2507           SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2508                                                        &new_depths,
2509                                                        ctx->wc_ctx,
2510                                                        dst_abspath,
2511                                                        svn_depth_infinity,
2512                                                        iterpool, iterpool));
2513           SVN_ERR(svn_client__handle_externals(new_externals,
2514                                                new_depths,
2515                                                repos_root_url, dst_abspath,
2516                                                svn_depth_infinity,
2517                                                timestamp_sleep,
2518                                                ra_session,
2519                                                ctx, iterpool));
2520           svn_pool_destroy(iterpool);
2521         }
2522     } /* end directory case */
2523
2524   else if (pair->src_kind == svn_node_file)
2525     {
2526       apr_hash_t *new_props;
2527       const char *src_rel;
2528       svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2529
2530       SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2531                                                   pair->src_abspath_or_url,
2532                                                   pool));
2533       /* Fetch the file content. While doing so, resolve pair->src_revnum
2534        * to an actual revision number if it's 'invalid' meaning 'head'. */
2535       SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2536                               new_base_contents,
2537                               &pair->src_revnum, &new_props, pool));
2538
2539       if (new_props && ! same_repositories)
2540         svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2541
2542       *timestamp_sleep = TRUE;
2543
2544       SVN_ERR(svn_wc_add_repos_file4(
2545          ctx->wc_ctx, dst_abspath,
2546          new_base_contents, NULL, new_props, NULL,
2547          same_repositories ? pair->src_abspath_or_url : NULL,
2548          same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2549          ctx->cancel_func, ctx->cancel_baton,
2550          pool));
2551     }
2552
2553   /* Record the implied mergeinfo (before the notification callback
2554      is invoked for the root node). */
2555   SVN_ERR(svn_client__get_repos_mergeinfo(
2556             &src_mergeinfo, ra_session,
2557             pair->src_abspath_or_url, pair->src_revnum,
2558             svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2559   SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2560
2561   /* Do our own notification for the root node, even if we could possibly
2562      have delegated it.  See also issue #1552.
2563
2564      ### Maybe this notification should mention the mergeinfo change. */
2565   if (ctx->notify_func2)
2566     {
2567       svn_wc_notify_t *notify = svn_wc_create_notify(
2568                                   dst_abspath, svn_wc_notify_add, pool);
2569       notify->kind = pair->src_kind;
2570       ctx->notify_func2(ctx->notify_baton2, notify, pool);
2571     }
2572
2573   return SVN_NO_ERROR;
2574 }
2575
2576 static svn_error_t *
2577 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2578                         const apr_array_header_t *copy_pairs,
2579                         const char *top_dst_abspath,
2580                         svn_boolean_t ignore_externals,
2581                         svn_boolean_t pin_externals,
2582                         const apr_hash_t *externals_to_pin,
2583                         svn_ra_session_t *ra_session,
2584                         svn_client_ctx_t *ctx,
2585                         apr_pool_t *scratch_pool)
2586 {
2587   int i;
2588   svn_boolean_t same_repositories;
2589   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2590
2591   /* We've already checked for physical obstruction by a working file.
2592      But there could also be logical obstruction by an entry whose
2593      working file happens to be missing.*/
2594   SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2595                          ctx, scratch_pool, iterpool));
2596
2597   /* Decide whether the two repositories are the same or not. */
2598   {
2599     const char *parent_abspath;
2600     const char *src_uuid, *dst_uuid;
2601
2602     /* Get the repository uuid of SRC_URL */
2603     SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool));
2604
2605     /* Get repository uuid of dst's parent directory, since dst may
2606        not exist.  ### TODO:  we should probably walk up the wc here,
2607        in case the parent dir has an imaginary URL.  */
2608     if (copy_pairs->nelts == 1)
2609       parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool);
2610     else
2611       parent_abspath = top_dst_abspath;
2612
2613     SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2614                                       parent_abspath, ctx,
2615                                       iterpool, iterpool));
2616     /* ### Also check repos_root_url? */
2617     same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2618   }
2619
2620   /* Perform the move for each of the copy_pairs. */
2621   for (i = 0; i < copy_pairs->nelts; i++)
2622     {
2623       /* Check for cancellation */
2624       if (ctx->cancel_func)
2625         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2626
2627       svn_pool_clear(iterpool);
2628
2629       SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2630                                       APR_ARRAY_IDX(copy_pairs, i,
2631                                                     svn_client__copy_pair_t *),
2632                                       same_repositories,
2633                                       ignore_externals,
2634                                       pin_externals, externals_to_pin,
2635                                       ra_session, ctx, iterpool));
2636     }
2637   svn_pool_destroy(iterpool);
2638
2639   return SVN_NO_ERROR;
2640 }
2641
2642 static svn_error_t *
2643 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2644                  const apr_array_header_t *copy_pairs,
2645                  svn_boolean_t make_parents,
2646                  svn_boolean_t ignore_externals,
2647                  svn_boolean_t pin_externals,
2648                  const apr_hash_t *externals_to_pin,
2649                  svn_client_ctx_t *ctx,
2650                  apr_pool_t *pool)
2651 {
2652   svn_ra_session_t *ra_session;
2653   const char *top_src_url, *top_dst_abspath;
2654   apr_pool_t *iterpool = svn_pool_create(pool);
2655   const char *lock_abspath;
2656   int i;
2657
2658   /* Get the real path for the source, based upon its peg revision. */
2659   for (i = 0; i < copy_pairs->nelts; i++)
2660     {
2661       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2662                                                     svn_client__copy_pair_t *);
2663       const char *src;
2664
2665       svn_pool_clear(iterpool);
2666
2667       SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2668                                           NULL,
2669                                           pair->src_abspath_or_url,
2670                                           &pair->src_peg_revision,
2671                                           &pair->src_op_revision, NULL,
2672                                           ctx, iterpool));
2673
2674       pair->src_original = pair->src_abspath_or_url;
2675       pair->src_abspath_or_url = apr_pstrdup(pool, src);
2676     }
2677
2678   SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_abspath,
2679                                   NULL, pool));
2680   lock_abspath = top_dst_abspath;
2681   if (copy_pairs->nelts == 1)
2682     {
2683       top_src_url = svn_uri_dirname(top_src_url, pool);
2684       lock_abspath = svn_dirent_dirname(top_dst_abspath, pool);
2685     }
2686
2687   /* Open a repository session to the longest common src ancestor.  We do not
2688      (yet) have a working copy, so we don't have a corresponding path and
2689      tempfiles cannot go into the admin area. */
2690   SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2691                                       ctx, pool, pool));
2692
2693   /* Get the correct src path for the peg revision used, and verify that we
2694      aren't overwriting an existing path. */
2695   for (i = 0; i < copy_pairs->nelts; i++)
2696     {
2697       svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2698                                                     svn_client__copy_pair_t *);
2699       svn_node_kind_t dst_parent_kind, dst_kind;
2700       const char *dst_parent;
2701       const char *src_rel;
2702
2703       svn_pool_clear(iterpool);
2704
2705       /* Next, make sure that the path exists in the repository. */
2706       SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2707                                                   pair->src_abspath_or_url,
2708                                                   iterpool));
2709       SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2710                                 &pair->src_kind, pool));
2711       if (pair->src_kind == svn_node_none)
2712         {
2713           if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2714             return svn_error_createf
2715               (SVN_ERR_FS_NOT_FOUND, NULL,
2716                _("Path '%s' not found in revision %ld"),
2717                pair->src_abspath_or_url, pair->src_revnum);
2718           else
2719             return svn_error_createf
2720               (SVN_ERR_FS_NOT_FOUND, NULL,
2721                _("Path '%s' not found in head revision"),
2722                pair->src_abspath_or_url);
2723         }
2724
2725       /* Figure out about dst. */
2726       SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2727                                 iterpool));
2728       if (dst_kind != svn_node_none)
2729         {
2730           return svn_error_createf(
2731             SVN_ERR_ENTRY_EXISTS, NULL,
2732             _("Path '%s' already exists"),
2733             svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2734         }
2735
2736       /* Make sure the destination parent is a directory and produce a clear
2737          error message if it is not. */
2738       dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2739       SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2740       if (make_parents && dst_parent_kind == svn_node_none)
2741         {
2742           SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2743                                                  iterpool));
2744         }
2745       else if (dst_parent_kind != svn_node_dir)
2746         {
2747           return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2748                                    _("Path '%s' is not a directory"),
2749                                    svn_dirent_local_style(dst_parent, pool));
2750         }
2751     }
2752   svn_pool_destroy(iterpool);
2753
2754   SVN_WC__CALL_WITH_WRITE_LOCK(
2755     repos_to_wc_copy_locked(timestamp_sleep,
2756                             copy_pairs, top_dst_abspath, ignore_externals,
2757                             pin_externals, externals_to_pin,
2758                             ra_session, ctx, pool),
2759     ctx->wc_ctx, lock_abspath, FALSE, pool);
2760   return SVN_NO_ERROR;
2761 }
2762
2763 #define NEED_REPOS_REVNUM(revision) \
2764         ((revision.kind != svn_opt_revision_unspecified) \
2765           && (revision.kind != svn_opt_revision_working))
2766
2767 /* ...
2768  *
2769  * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2770  * change *TIMESTAMP_SLEEP.  This output will be valid even if the
2771  * function returns an error.
2772  *
2773  * Perform all allocations in POOL.
2774  */
2775 static svn_error_t *
2776 try_copy(svn_boolean_t *timestamp_sleep,
2777          const apr_array_header_t *sources,
2778          const char *dst_path_in,
2779          svn_boolean_t is_move,
2780          svn_boolean_t allow_mixed_revisions,
2781          svn_boolean_t metadata_only,
2782          svn_boolean_t make_parents,
2783          svn_boolean_t ignore_externals,
2784          svn_boolean_t pin_externals,
2785          const apr_hash_t *externals_to_pin,
2786          const apr_hash_t *revprop_table,
2787          svn_commit_callback2_t commit_callback,
2788          void *commit_baton,
2789          svn_client_ctx_t *ctx,
2790          apr_pool_t *pool)
2791 {
2792   apr_array_header_t *copy_pairs =
2793                         apr_array_make(pool, sources->nelts,
2794                                        sizeof(svn_client__copy_pair_t *));
2795   svn_boolean_t srcs_are_urls, dst_is_url;
2796   int i;
2797
2798   /* Assert instead of crashing if the sources list is empty. */
2799   SVN_ERR_ASSERT(sources->nelts > 0);
2800
2801   /* Are either of our paths URLs?  Just check the first src_path.  If
2802      there are more than one, we'll check for homogeneity among them
2803      down below. */
2804   srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2805                                   svn_client_copy_source_t *)->path);
2806   dst_is_url = svn_path_is_url(dst_path_in);
2807   if (!dst_is_url)
2808     SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2809
2810   /* If we have multiple source paths, it implies the dst_path is a
2811      directory we are moving or copying into.  Populate the COPY_PAIRS
2812      array to contain a destination path for each of the source paths. */
2813   if (sources->nelts > 1)
2814     {
2815       apr_pool_t *iterpool = svn_pool_create(pool);
2816
2817       for (i = 0; i < sources->nelts; i++)
2818         {
2819           svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2820                                                svn_client_copy_source_t *);
2821           svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2822           const char *src_basename;
2823           svn_boolean_t src_is_url = svn_path_is_url(source->path);
2824
2825           svn_pool_clear(iterpool);
2826
2827           if (src_is_url)
2828             {
2829               pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2830               src_basename = svn_uri_basename(pair->src_abspath_or_url,
2831                                               iterpool);
2832             }
2833           else
2834             {
2835               SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2836                                               source->path, pool));
2837               src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2838                                                  iterpool);
2839             }
2840
2841           pair->src_op_revision = *source->revision;
2842           pair->src_peg_revision = *source->peg_revision;
2843           pair->src_kind = svn_node_unknown;
2844
2845           SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2846                                             &pair->src_op_revision,
2847                                             src_is_url,
2848                                             TRUE,
2849                                             iterpool));
2850
2851           /* Check to see if all the sources are urls or all working copy
2852            * paths. */
2853           if (src_is_url != srcs_are_urls)
2854             return svn_error_create
2855               (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2856                _("Cannot mix repository and working copy sources"));
2857
2858           if (dst_is_url)
2859             pair->dst_abspath_or_url =
2860               svn_path_url_add_component2(dst_path_in, src_basename, pool);
2861           else
2862             pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2863                                                        src_basename, pool);
2864           APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2865         }
2866
2867       svn_pool_destroy(iterpool);
2868     }
2869   else
2870     {
2871       /* Only one source path. */
2872       svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2873       svn_client_copy_source_t *source =
2874         APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2875       svn_boolean_t src_is_url = svn_path_is_url(source->path);
2876
2877       if (src_is_url)
2878         pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2879       else
2880         SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2881                                         source->path, pool));
2882       pair->src_op_revision = *source->revision;
2883       pair->src_peg_revision = *source->peg_revision;
2884       pair->src_kind = svn_node_unknown;
2885
2886       SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2887                                         &pair->src_op_revision,
2888                                         src_is_url, TRUE, pool));
2889
2890       pair->dst_abspath_or_url = dst_path_in;
2891       APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2892     }
2893
2894   if (!srcs_are_urls && !dst_is_url)
2895     {
2896       apr_pool_t *iterpool = svn_pool_create(pool);
2897
2898       for (i = 0; i < copy_pairs->nelts; i++)
2899         {
2900           svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2901                                             svn_client__copy_pair_t *);
2902
2903           svn_pool_clear(iterpool);
2904
2905           if (svn_dirent_is_child(pair->src_abspath_or_url,
2906                                   pair->dst_abspath_or_url, iterpool))
2907             return svn_error_createf
2908               (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2909                _("Cannot copy path '%s' into its own child '%s'"),
2910                svn_dirent_local_style(pair->src_abspath_or_url, pool),
2911                svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2912         }
2913
2914       svn_pool_destroy(iterpool);
2915     }
2916
2917   /* A file external should not be moved since the file external is
2918      implemented as a switched file and it would delete the file the
2919      file external is switched to, which is not the behavior the user
2920      would probably want. */
2921   if (is_move && !srcs_are_urls)
2922     {
2923       apr_pool_t *iterpool = svn_pool_create(pool);
2924
2925       for (i = 0; i < copy_pairs->nelts; i++)
2926         {
2927           svn_client__copy_pair_t *pair =
2928             APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2929           svn_node_kind_t external_kind;
2930           const char *defining_abspath;
2931
2932           svn_pool_clear(iterpool);
2933
2934           SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2935           SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2936                                              NULL, NULL, NULL, ctx->wc_ctx,
2937                                              pair->src_abspath_or_url,
2938                                              pair->src_abspath_or_url, TRUE,
2939                                              iterpool, iterpool));
2940
2941           if (external_kind != svn_node_none)
2942             return svn_error_createf(
2943                      SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2944                      NULL,
2945                      _("Cannot move the external at '%s'; please "
2946                        "edit the svn:externals property on '%s'."),
2947                      svn_dirent_local_style(pair->src_abspath_or_url, pool),
2948                      svn_dirent_local_style(defining_abspath, pool));
2949         }
2950       svn_pool_destroy(iterpool);
2951     }
2952
2953   if (is_move)
2954     {
2955       /* Disallow moves between the working copy and the repository. */
2956       if (srcs_are_urls != dst_is_url)
2957         {
2958           return svn_error_create
2959             (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2960              _("Moves between the working copy and the repository are not "
2961                "supported"));
2962         }
2963
2964       /* Disallow moving any path/URL onto or into itself. */
2965       for (i = 0; i < copy_pairs->nelts; i++)
2966         {
2967           svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2968                                             svn_client__copy_pair_t *);
2969
2970           if (strcmp(pair->src_abspath_or_url,
2971                      pair->dst_abspath_or_url) == 0)
2972             return svn_error_createf(
2973               SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2974               srcs_are_urls ?
2975                 _("Cannot move URL '%s' into itself") :
2976                 _("Cannot move path '%s' into itself"),
2977               srcs_are_urls ?
2978                 pair->src_abspath_or_url :
2979                 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2980         }
2981     }
2982   else
2983     {
2984       if (!srcs_are_urls)
2985         {
2986           /* If we are doing a wc->* copy, but with an operational revision
2987              other than the working copy revision, we are really doing a
2988              repo->* copy, because we're going to need to get the rev from the
2989              repo. */
2990
2991           svn_boolean_t need_repos_op_rev = FALSE;
2992           svn_boolean_t need_repos_peg_rev = FALSE;
2993
2994           /* Check to see if any revision is something other than
2995              svn_opt_revision_unspecified or svn_opt_revision_working. */
2996           for (i = 0; i < copy_pairs->nelts; i++)
2997             {
2998               svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2999                                                 svn_client__copy_pair_t *);
3000
3001               if (NEED_REPOS_REVNUM(pair->src_op_revision))
3002                 need_repos_op_rev = TRUE;
3003
3004               if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3005                 need_repos_peg_rev = TRUE;
3006
3007               if (need_repos_op_rev || need_repos_peg_rev)
3008                 break;
3009             }
3010
3011           if (need_repos_op_rev || need_repos_peg_rev)
3012             {
3013               apr_pool_t *iterpool = svn_pool_create(pool);
3014
3015               for (i = 0; i < copy_pairs->nelts; i++)
3016                 {
3017                   const char *copyfrom_repos_root_url;
3018                   const char *copyfrom_repos_relpath;
3019                   const char *url;
3020                   svn_revnum_t copyfrom_rev;
3021                   svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3022                                                     svn_client__copy_pair_t *);
3023
3024                   svn_pool_clear(iterpool);
3025
3026                   SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3027
3028                   SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
3029                                                   &copyfrom_repos_relpath,
3030                                                   &copyfrom_repos_root_url,
3031                                                   NULL, NULL, NULL,
3032                                                   ctx->wc_ctx,
3033                                                   pair->src_abspath_or_url,
3034                                                   TRUE, iterpool, iterpool));
3035
3036                   if (copyfrom_repos_relpath)
3037                     url = svn_path_url_add_component2(copyfrom_repos_root_url,
3038                                                       copyfrom_repos_relpath,
3039                                                       pool);
3040                   else
3041                     return svn_error_createf
3042                       (SVN_ERR_ENTRY_MISSING_URL, NULL,
3043                        _("'%s' does not have a URL associated with it"),
3044                        svn_dirent_local_style(pair->src_abspath_or_url, pool));
3045
3046                   pair->src_abspath_or_url = url;
3047
3048                   if (!need_repos_peg_rev
3049                       || pair->src_peg_revision.kind == svn_opt_revision_base)
3050                     {
3051                       /* Default the peg revision to that of the WC entry. */
3052                       pair->src_peg_revision.kind = svn_opt_revision_number;
3053                       pair->src_peg_revision.value.number = copyfrom_rev;
3054                     }
3055
3056                   if (pair->src_op_revision.kind == svn_opt_revision_base)
3057                     {
3058                       /* Use the entry's revision as the operational rev. */
3059                       pair->src_op_revision.kind = svn_opt_revision_number;
3060                       pair->src_op_revision.value.number = copyfrom_rev;
3061                     }
3062                 }
3063
3064               svn_pool_destroy(iterpool);
3065               srcs_are_urls = TRUE;
3066             }
3067         }
3068     }
3069
3070   /* Now, call the right handler for the operation. */
3071   if ((! srcs_are_urls) && (! dst_is_url))
3072     {
3073       SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3074                                       metadata_only, ctx, pool, pool));
3075
3076       /* Copy or move all targets. */
3077       if (is_move)
3078         return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3079                                                  copy_pairs, dst_path_in,
3080                                                  allow_mixed_revisions,
3081                                                  metadata_only,
3082                                                  ctx, pool));
3083       else
3084         {
3085           /* We ignore these values, so assert the default value */
3086           SVN_ERR_ASSERT(allow_mixed_revisions);
3087           return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3088                                                     copy_pairs,
3089                                                     metadata_only,
3090                                                     pin_externals,
3091                                                     externals_to_pin,
3092                                                     ctx, pool));
3093         }
3094     }
3095   else if ((! srcs_are_urls) && (dst_is_url))
3096     {
3097       return svn_error_trace(
3098         wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3099                          commit_callback, commit_baton,
3100                          pin_externals, externals_to_pin, ctx, pool));
3101     }
3102   else if ((srcs_are_urls) && (! dst_is_url))
3103     {
3104       return svn_error_trace(
3105         repos_to_wc_copy(timestamp_sleep,
3106                          copy_pairs, make_parents, ignore_externals,
3107                          pin_externals, externals_to_pin, ctx, pool));
3108     }
3109   else
3110     {
3111       return svn_error_trace(
3112         repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3113                             commit_callback, commit_baton, ctx, is_move,
3114                             pin_externals, externals_to_pin, pool));
3115     }
3116 }
3117
3118
3119 \f
3120 /* Public Interfaces */
3121 svn_error_t *
3122 svn_client_copy7(const apr_array_header_t *sources,
3123                  const char *dst_path,
3124                  svn_boolean_t copy_as_child,
3125                  svn_boolean_t make_parents,
3126                  svn_boolean_t ignore_externals,
3127                  svn_boolean_t metadata_only,
3128                  svn_boolean_t pin_externals,
3129                  const apr_hash_t *externals_to_pin,
3130                  const apr_hash_t *revprop_table,
3131                  svn_commit_callback2_t commit_callback,
3132                  void *commit_baton,
3133                  svn_client_ctx_t *ctx,
3134                  apr_pool_t *pool)
3135 {
3136   svn_error_t *err;
3137   svn_boolean_t timestamp_sleep = FALSE;
3138   apr_pool_t *subpool = svn_pool_create(pool);
3139
3140   if (sources->nelts > 1 && !copy_as_child)
3141     return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3142                             NULL, NULL);
3143
3144   err = try_copy(&timestamp_sleep,
3145                  sources, dst_path,
3146                  FALSE /* is_move */,
3147                  TRUE /* allow_mixed_revisions */,
3148                  metadata_only,
3149                  make_parents,
3150                  ignore_externals,
3151                  pin_externals,
3152                  externals_to_pin,
3153                  revprop_table,
3154                  commit_callback, commit_baton,
3155                  ctx,
3156                  subpool);
3157
3158   /* If the destination exists, try to copy the sources as children of the
3159      destination. */
3160   if (copy_as_child && err && (sources->nelts == 1)
3161         && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3162             || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3163     {
3164       const char *src_path = APR_ARRAY_IDX(sources, 0,
3165                                            svn_client_copy_source_t *)->path;
3166       const char *src_basename;
3167       svn_boolean_t src_is_url = svn_path_is_url(src_path);
3168       svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3169
3170       svn_error_clear(err);
3171       svn_pool_clear(subpool);
3172
3173       src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3174                                 : svn_dirent_basename(src_path, subpool);
3175       dst_path
3176         = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3177                                                    subpool)
3178                      : svn_dirent_join(dst_path, src_basename, subpool);
3179
3180       err = try_copy(&timestamp_sleep,
3181                      sources, dst_path,
3182                      FALSE /* is_move */,
3183                      TRUE /* allow_mixed_revisions */,
3184                      metadata_only,
3185                      make_parents,
3186                      ignore_externals,
3187                      pin_externals,
3188                      externals_to_pin,
3189                      revprop_table,
3190                      commit_callback, commit_baton,
3191                      ctx,
3192                      subpool);
3193     }
3194
3195   /* Sleep if required.  DST_PATH is not a URL in these cases. */
3196   if (timestamp_sleep)
3197     svn_io_sleep_for_timestamps(dst_path, subpool);
3198
3199   svn_pool_destroy(subpool);
3200   return svn_error_trace(err);
3201 }
3202
3203
3204 svn_error_t *
3205 svn_client_move7(const apr_array_header_t *src_paths,
3206                  const char *dst_path,
3207                  svn_boolean_t move_as_child,
3208                  svn_boolean_t make_parents,
3209                  svn_boolean_t allow_mixed_revisions,
3210                  svn_boolean_t metadata_only,
3211                  const apr_hash_t *revprop_table,
3212                  svn_commit_callback2_t commit_callback,
3213                  void *commit_baton,
3214                  svn_client_ctx_t *ctx,
3215                  apr_pool_t *pool)
3216 {
3217   const svn_opt_revision_t head_revision
3218     = { svn_opt_revision_head, { 0 } };
3219   svn_error_t *err;
3220   svn_boolean_t timestamp_sleep = FALSE;
3221   int i;
3222   apr_pool_t *subpool = svn_pool_create(pool);
3223   apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3224                                   sizeof(const svn_client_copy_source_t *));
3225
3226   if (src_paths->nelts > 1 && !move_as_child)
3227     return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3228                             NULL, NULL);
3229
3230   for (i = 0; i < src_paths->nelts; i++)
3231     {
3232       const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3233       svn_client_copy_source_t *copy_source = apr_palloc(pool,
3234                                                          sizeof(*copy_source));
3235
3236       copy_source->path = src_path;
3237       copy_source->revision = &head_revision;
3238       copy_source->peg_revision = &head_revision;
3239
3240       APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3241     }
3242
3243   err = try_copy(&timestamp_sleep,
3244                  sources, dst_path,
3245                  TRUE /* is_move */,
3246                  allow_mixed_revisions,
3247                  metadata_only,
3248                  make_parents,
3249                  FALSE /* ignore_externals */,
3250                  FALSE /* pin_externals */,
3251                  NULL /* externals_to_pin */,
3252                  revprop_table,
3253                  commit_callback, commit_baton,
3254                  ctx,
3255                  subpool);
3256
3257   /* If the destination exists, try to move the sources as children of the
3258      destination. */
3259   if (move_as_child && err && (src_paths->nelts == 1)
3260         && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3261             || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3262     {
3263       const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3264       const char *src_basename;
3265       svn_boolean_t src_is_url = svn_path_is_url(src_path);
3266       svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3267
3268       svn_error_clear(err);
3269       svn_pool_clear(subpool);
3270
3271       src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3272                                 : svn_dirent_basename(src_path, pool);
3273       dst_path
3274         = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3275                                                    subpool)
3276                      : svn_dirent_join(dst_path, src_basename, subpool);
3277
3278       err = try_copy(&timestamp_sleep,
3279                      sources, dst_path,
3280                      TRUE /* is_move */,
3281                      allow_mixed_revisions,
3282                      metadata_only,
3283                      make_parents,
3284                      FALSE /* ignore_externals */,
3285                      FALSE /* pin_externals */,
3286                      NULL /* externals_to_pin */,
3287                      revprop_table,
3288                      commit_callback, commit_baton,
3289                      ctx,
3290                      subpool);
3291     }
3292
3293   /* Sleep if required.  DST_PATH is not a URL in these cases. */
3294   if (timestamp_sleep)
3295     svn_io_sleep_for_timestamps(dst_path, subpool);
3296
3297   svn_pool_destroy(subpool);
3298   return svn_error_trace(err);
3299 }