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