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