1251881Speter/*
2251881Speter * copy.c:  copy/move wrappers around wc 'copy' functionality.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <string.h>
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_client.h"
33251881Speter#include "svn_error.h"
34251881Speter#include "svn_error_codes.h"
35251881Speter#include "svn_dirent_uri.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_opt.h"
38251881Speter#include "svn_time.h"
39251881Speter#include "svn_props.h"
40251881Speter#include "svn_mergeinfo.h"
41251881Speter#include "svn_pools.h"
42251881Speter
43251881Speter#include "client.h"
44251881Speter#include "mergeinfo.h"
45251881Speter
46251881Speter#include "svn_private_config.h"
47251881Speter#include "private/svn_wc_private.h"
48251881Speter#include "private/svn_ra_private.h"
49251881Speter#include "private/svn_mergeinfo_private.h"
50251881Speter#include "private/svn_client_private.h"
51251881Speter
52251881Speter
53251881Speter/*
54251881Speter * OUR BASIC APPROACH TO COPIES
55251881Speter * ============================
56251881Speter *
57251881Speter * for each source/destination pair
58251881Speter *   if (not exist src_path)
59251881Speter *     return ERR_BAD_SRC error
60251881Speter *
61251881Speter *   if (exist dst_path)
62251881Speter *     return ERR_OBSTRUCTION error
63251881Speter *   else
64251881Speter *     copy src_path into parent_of_dst_path as basename (dst_path)
65251881Speter *
66251881Speter *   if (this is a move)
67251881Speter *     delete src_path
68251881Speter */
69251881Speter
70251881Speter
71251881Speter
72251881Speter/*** Code. ***/
73251881Speter
74251881Speter/* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
75251881Speter   MERGEINFO to any mergeinfo pre-existing in the WC. */
76251881Speterstatic svn_error_t *
77251881Speterextend_wc_mergeinfo(const char *target_abspath,
78251881Speter                    apr_hash_t *mergeinfo,
79251881Speter                    svn_client_ctx_t *ctx,
80251881Speter                    apr_pool_t *pool)
81251881Speter{
82251881Speter  apr_hash_t *wc_mergeinfo;
83251881Speter
84251881Speter  /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
85251881Speter     updating it. */
86251881Speter  SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
87251881Speter                                      target_abspath, pool, pool));
88251881Speter
89251881Speter  /* Combine the provided mergeinfo with any mergeinfo from the WC. */
90251881Speter  if (wc_mergeinfo && mergeinfo)
91251881Speter    SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
92251881Speter  else if (! wc_mergeinfo)
93251881Speter    wc_mergeinfo = mergeinfo;
94251881Speter
95251881Speter  return svn_error_trace(
96251881Speter    svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
97251881Speter                                    FALSE, ctx, pool));
98251881Speter}
99251881Speter
100251881Speter/* Find the longest common ancestor of paths in COPY_PAIRS.  If
101251881Speter   SRC_ANCESTOR is NULL, ignore source paths in this calculation.  If
102251881Speter   DST_ANCESTOR is NULL, ignore destination paths in this calculation.
103251881Speter   COMMON_ANCESTOR will be the common ancestor of both the
104251881Speter   SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
105251881Speter   NULL.
106251881Speter */
107251881Speterstatic svn_error_t *
108251881Speterget_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
109251881Speter                        const char **src_ancestor,
110251881Speter                        const char **dst_ancestor,
111251881Speter                        const char **common_ancestor,
112251881Speter                        apr_pool_t *pool)
113251881Speter{
114251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
115251881Speter  svn_client__copy_pair_t *first;
116251881Speter  const char *first_dst;
117251881Speter  const char *first_src;
118251881Speter  const char *top_dst;
119251881Speter  svn_boolean_t src_is_url;
120251881Speter  svn_boolean_t dst_is_url;
121251881Speter  char *top_src;
122251881Speter  int i;
123251881Speter
124251881Speter  first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
125251881Speter
126251881Speter  /* Because all the destinations are in the same directory, we can easily
127251881Speter     determine their common ancestor. */
128251881Speter  first_dst = first->dst_abspath_or_url;
129251881Speter  dst_is_url = svn_path_is_url(first_dst);
130251881Speter
131251881Speter  if (copy_pairs->nelts == 1)
132251881Speter    top_dst = apr_pstrdup(subpool, first_dst);
133251881Speter  else
134251881Speter    top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
135251881Speter                         : svn_dirent_dirname(first_dst, subpool);
136251881Speter
137251881Speter  /* Sources can came from anywhere, so we have to actually do some
138251881Speter     work for them.  */
139251881Speter  first_src = first->src_abspath_or_url;
140251881Speter  src_is_url = svn_path_is_url(first_src);
141251881Speter  top_src = apr_pstrdup(subpool, first_src);
142251881Speter  for (i = 1; i < copy_pairs->nelts; i++)
143251881Speter    {
144251881Speter      /* We don't need to clear the subpool here for several reasons:
145251881Speter         1)  If we do, we can't use it to allocate the initial versions of
146251881Speter             top_src and top_dst (above).
147251881Speter         2)  We don't return any errors in the following loop, so we
148251881Speter             are guanteed to destroy the subpool at the end of this function.
149251881Speter         3)  The number of iterations is likely to be few, and the loop will
150251881Speter             be through quickly, so memory leakage will not be significant,
151251881Speter             in time or space.
152251881Speter      */
153251881Speter      const svn_client__copy_pair_t *pair =
154251881Speter        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
155251881Speter
156251881Speter      top_src = src_is_url
157251881Speter        ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
158251881Speter                                       subpool)
159251881Speter        : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
160251881Speter                                          subpool);
161251881Speter    }
162251881Speter
163251881Speter  if (src_ancestor)
164251881Speter    *src_ancestor = apr_pstrdup(pool, top_src);
165251881Speter
166251881Speter  if (dst_ancestor)
167251881Speter    *dst_ancestor = apr_pstrdup(pool, top_dst);
168251881Speter
169251881Speter  if (common_ancestor)
170251881Speter    *common_ancestor =
171251881Speter               src_is_url
172251881Speter                    ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
173251881Speter                    : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
174251881Speter
175251881Speter  svn_pool_destroy(subpool);
176251881Speter
177251881Speter  return SVN_NO_ERROR;
178251881Speter}
179251881Speter
180299742Sdim/* Quote a string if it would be handled as multiple or different tokens
181299742Sdim   during externals parsing */
182299742Sdimstatic const char *
183299742Sdimmaybe_quote(const char *value,
184299742Sdim            apr_pool_t *result_pool)
185299742Sdim{
186299742Sdim  apr_status_t status;
187299742Sdim  char **argv;
188251881Speter
189299742Sdim  status = apr_tokenize_to_argv(value, &argv, result_pool);
190299742Sdim
191299742Sdim  if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
192299742Sdim    return apr_pstrdup(result_pool, value);
193299742Sdim
194299742Sdim  {
195299742Sdim    svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
196299742Sdim    const char *c;
197299742Sdim
198299742Sdim    svn_stringbuf_appendbyte(sb, '\"');
199299742Sdim
200299742Sdim    for (c = value; *c; c++)
201299742Sdim      {
202299742Sdim        if (*c == '\\' || *c == '\"' || *c == '\'')
203299742Sdim          svn_stringbuf_appendbyte(sb, '\\');
204299742Sdim
205299742Sdim        svn_stringbuf_appendbyte(sb, *c);
206299742Sdim      }
207299742Sdim
208299742Sdim    svn_stringbuf_appendbyte(sb, '\"');
209299742Sdim
210299742Sdim#ifdef SVN_DEBUG
211299742Sdim    status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
212299742Sdim
213299742Sdim    SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
214299742Sdim                             && !strcmp(argv[0], value));
215299742Sdim#endif
216299742Sdim
217299742Sdim    return sb->data;
218299742Sdim  }
219299742Sdim}
220299742Sdim
221299742Sdim/* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for
222299742Sdim * use as a line in an svn:externals property, based on the external item
223299742Sdim * ITEM and the additional parser information in INFO. Pin the external
224299742Sdim * to EXTERNAL_PEGREV. Use POOL for all allocations. */
225299742Sdimstatic svn_error_t *
226299742Sdimmake_external_description(const char **new_external_description,
227299742Sdim                          const char *local_abspath_or_url,
228299742Sdim                          svn_wc_external_item2_t *item,
229299742Sdim                          svn_wc__externals_parser_info_t *info,
230299742Sdim                          svn_opt_revision_t external_pegrev,
231299742Sdim                          apr_pool_t *pool)
232299742Sdim{
233299742Sdim  const char *rev_str;
234299742Sdim  const char *peg_rev_str;
235299742Sdim
236299742Sdim  switch (info->format)
237299742Sdim    {
238299742Sdim      case svn_wc__external_description_format_1:
239299742Sdim        if (external_pegrev.kind == svn_opt_revision_unspecified)
240299742Sdim          {
241299742Sdim            /* If info->rev_str is NULL, this yields an empty string. */
242299742Sdim            rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
243299742Sdim          }
244299742Sdim        else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
245299742Sdim          rev_str = apr_psprintf(pool, "%s ", info->rev_str);
246299742Sdim        else
247299742Sdim          {
248299742Sdim            /* ### can't handle svn_opt_revision_date without info->rev_str */
249299742Sdim            SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
250299742Sdim            rev_str = apr_psprintf(pool, "-r%ld ",
251299742Sdim                                   external_pegrev.value.number);
252299742Sdim          }
253299742Sdim
254299742Sdim        *new_external_description =
255299742Sdim          apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
256299742Sdim                                          rev_str,
257299742Sdim                                          maybe_quote(item->url, pool));
258299742Sdim        break;
259299742Sdim
260299742Sdim      case svn_wc__external_description_format_2:
261299742Sdim        if (external_pegrev.kind == svn_opt_revision_unspecified)
262299742Sdim          {
263299742Sdim            /* If info->rev_str is NULL, this yields an empty string. */
264299742Sdim            rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
265299742Sdim          }
266299742Sdim        else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
267299742Sdim          rev_str = apr_psprintf(pool, "%s ", info->rev_str);
268299742Sdim        else
269299742Sdim          rev_str = "";
270299742Sdim
271299742Sdim        if (external_pegrev.kind == svn_opt_revision_unspecified)
272299742Sdim          peg_rev_str = info->peg_rev_str ? info->peg_rev_str : "";
273299742Sdim        else if (info->peg_rev_str &&
274299742Sdim                 item->peg_revision.kind != svn_opt_revision_head)
275299742Sdim          peg_rev_str = info->peg_rev_str;
276299742Sdim        else
277299742Sdim          {
278299742Sdim            /* ### can't handle svn_opt_revision_date without info->rev_str */
279299742Sdim            SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
280299742Sdim            peg_rev_str = apr_psprintf(pool, "@%ld",
281299742Sdim                                       external_pegrev.value.number);
282299742Sdim          }
283299742Sdim
284299742Sdim        *new_external_description =
285299742Sdim          apr_psprintf(pool, "%s%s %s\n", rev_str,
286299742Sdim                       maybe_quote(apr_psprintf(pool, "%s%s", item->url,
287299742Sdim                                                peg_rev_str),
288299742Sdim                                   pool),
289299742Sdim                       maybe_quote(item->target_dir, pool));
290299742Sdim        break;
291299742Sdim
292299742Sdim      default:
293299742Sdim        return svn_error_createf(
294299742Sdim                 SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
295299742Sdim                 _("%s property defined at '%s' is using an unsupported "
296299742Sdim                   "syntax"), SVN_PROP_EXTERNALS,
297299742Sdim                 svn_dirent_local_style(local_abspath_or_url, pool));
298299742Sdim    }
299299742Sdim
300299742Sdim  return SVN_NO_ERROR;
301299742Sdim}
302299742Sdim
303299742Sdim/* Pin all externals listed in EXTERNALS_PROP_VAL to their
304299742Sdim * last-changed revision. Set *PINNED_EXTERNALS to a new property
305299742Sdim * value allocated in RESULT_POOL, or to NULL if none of the externals
306299742Sdim * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the
307299742Sdim * path or URL defining the svn:externals property. Use SCRATCH_POOL
308299742Sdim * for temporary allocations.
309299742Sdim */
310299742Sdimstatic svn_error_t *
311299742Sdimpin_externals_prop(svn_string_t **pinned_externals,
312299742Sdim                   svn_string_t *externals_prop_val,
313299742Sdim                   const apr_hash_t *externals_to_pin,
314299742Sdim                   const char *repos_root_url,
315299742Sdim                   const char *local_abspath_or_url,
316299742Sdim                   svn_client_ctx_t *ctx,
317299742Sdim                   apr_pool_t *result_pool,
318299742Sdim                   apr_pool_t *scratch_pool)
319299742Sdim{
320299742Sdim  svn_stringbuf_t *buf;
321299742Sdim  apr_array_header_t *external_items;
322299742Sdim  apr_array_header_t *parser_infos;
323299742Sdim  apr_array_header_t *items_to_pin;
324299742Sdim  int pinned_items;
325299742Sdim  int i;
326299742Sdim  apr_pool_t *iterpool;
327299742Sdim
328299742Sdim  SVN_ERR(svn_wc__parse_externals_description(&external_items,
329299742Sdim                                              &parser_infos,
330299742Sdim                                              local_abspath_or_url,
331299742Sdim                                              externals_prop_val->data,
332299742Sdim                                              FALSE /* canonicalize_url */,
333299742Sdim                                              scratch_pool));
334299742Sdim
335299742Sdim  if (externals_to_pin)
336299742Sdim    {
337299742Sdim      items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
338299742Sdim                                   local_abspath_or_url);
339299742Sdim      if (!items_to_pin)
340299742Sdim        {
341299742Sdim          /* No pinning at all for this path. */
342299742Sdim          *pinned_externals = NULL;
343299742Sdim          return SVN_NO_ERROR;
344299742Sdim        }
345299742Sdim    }
346299742Sdim  else
347299742Sdim    items_to_pin = NULL;
348299742Sdim
349299742Sdim  buf = svn_stringbuf_create_empty(scratch_pool);
350299742Sdim  iterpool = svn_pool_create(scratch_pool);
351299742Sdim  pinned_items = 0;
352299742Sdim  for (i = 0; i < external_items->nelts; i++)
353299742Sdim    {
354299742Sdim      svn_wc_external_item2_t *item;
355299742Sdim      svn_wc__externals_parser_info_t *info;
356299742Sdim      svn_opt_revision_t external_pegrev;
357299742Sdim      const char *pinned_desc;
358299742Sdim
359299742Sdim      svn_pool_clear(iterpool);
360299742Sdim
361299742Sdim      item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
362299742Sdim      info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *);
363299742Sdim
364299742Sdim      if (items_to_pin)
365299742Sdim        {
366299742Sdim          int j;
367299742Sdim          svn_wc_external_item2_t *item_to_pin = NULL;
368299742Sdim
369299742Sdim          for (j = 0; j < items_to_pin->nelts; j++)
370299742Sdim            {
371299742Sdim              svn_wc_external_item2_t *const current =
372299742Sdim                APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
373299742Sdim
374299742Sdim
375299742Sdim              if (current
376299742Sdim                  && 0 == strcmp(item->url, current->url)
377299742Sdim                  && 0 == strcmp(item->target_dir, current->target_dir))
378299742Sdim                {
379299742Sdim                  item_to_pin = current;
380299742Sdim                  break;
381299742Sdim                }
382299742Sdim            }
383299742Sdim
384299742Sdim          /* If this item is not in our list of external items to pin then
385299742Sdim           * simply keep the external at its original value. */
386299742Sdim          if (!item_to_pin)
387299742Sdim            {
388299742Sdim              const char *desc;
389299742Sdim
390299742Sdim              external_pegrev.kind = svn_opt_revision_unspecified;
391299742Sdim              SVN_ERR(make_external_description(&desc, local_abspath_or_url,
392299742Sdim                                                item, info, external_pegrev,
393299742Sdim                                                iterpool));
394299742Sdim              svn_stringbuf_appendcstr(buf, desc);
395299742Sdim              continue;
396299742Sdim            }
397299742Sdim        }
398299742Sdim
399299742Sdim      if (item->peg_revision.kind == svn_opt_revision_date)
400299742Sdim        {
401299742Sdim          /* Already pinned ... copy the peg date. */
402299742Sdim          external_pegrev.kind = svn_opt_revision_date;
403299742Sdim          external_pegrev.value.date = item->peg_revision.value.date;
404299742Sdim        }
405299742Sdim      else if (item->peg_revision.kind == svn_opt_revision_number)
406299742Sdim        {
407299742Sdim          /* Already pinned ... copy the peg revision number. */
408299742Sdim          external_pegrev.kind = svn_opt_revision_number;
409299742Sdim          external_pegrev.value.number = item->peg_revision.value.number;
410299742Sdim        }
411299742Sdim      else
412299742Sdim        {
413299742Sdim          SVN_ERR_ASSERT(
414299742Sdim            item->peg_revision.kind == svn_opt_revision_head ||
415299742Sdim            item->peg_revision.kind == svn_opt_revision_unspecified);
416299742Sdim
417299742Sdim          /* We're actually going to change the peg revision. */
418299742Sdim          ++pinned_items;
419299742Sdim
420299742Sdim          if (svn_path_is_url(local_abspath_or_url))
421299742Sdim            {
422299742Sdim              const char *resolved_url;
423299742Sdim              svn_ra_session_t *external_ra_session;
424299742Sdim              svn_revnum_t latest_revnum;
425299742Sdim
426299742Sdim              SVN_ERR(svn_wc__resolve_relative_external_url(
427299742Sdim                        &resolved_url, item, repos_root_url,
428299742Sdim                        local_abspath_or_url, iterpool, iterpool));
429299742Sdim              SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
430299742Sdim                                                           NULL, resolved_url,
431299742Sdim                                                           NULL, NULL, FALSE,
432299742Sdim                                                           FALSE, ctx,
433299742Sdim                                                           iterpool,
434299742Sdim                                                           iterpool));
435299742Sdim              SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
436299742Sdim                                               &latest_revnum,
437299742Sdim                                               iterpool));
438299742Sdim
439299742Sdim              external_pegrev.kind = svn_opt_revision_number;
440299742Sdim              external_pegrev.value.number = latest_revnum;
441299742Sdim            }
442299742Sdim          else
443299742Sdim            {
444299742Sdim              const char *external_abspath;
445299742Sdim              svn_node_kind_t external_kind;
446299742Sdim              svn_revnum_t external_checked_out_rev;
447299742Sdim
448299742Sdim              external_abspath = svn_dirent_join(local_abspath_or_url,
449299742Sdim                                                 item->target_dir,
450299742Sdim                                                 iterpool);
451299742Sdim              SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL,
452299742Sdim                                                 NULL, NULL, ctx->wc_ctx,
453299742Sdim                                                 local_abspath_or_url,
454299742Sdim                                                 external_abspath, TRUE,
455299742Sdim                                                 iterpool,
456299742Sdim                                                 iterpool));
457299742Sdim              if (external_kind == svn_node_none)
458299742Sdim                return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
459299742Sdim                                         NULL,
460299742Sdim                                         _("Cannot pin external '%s' defined "
461299742Sdim                                           "in %s at '%s' because it is not "
462299742Sdim                                           "checked out in the working copy "
463299742Sdim                                           "at '%s'"),
464299742Sdim                                           item->url, SVN_PROP_EXTERNALS,
465299742Sdim                                           svn_dirent_local_style(
466299742Sdim                                             local_abspath_or_url, iterpool),
467299742Sdim                                           svn_dirent_local_style(
468299742Sdim                                             external_abspath, iterpool));
469299742Sdim              else if (external_kind == svn_node_dir)
470299742Sdim                {
471299742Sdim                  svn_boolean_t is_switched;
472299742Sdim                  svn_boolean_t is_modified;
473299742Sdim                  svn_revnum_t min_rev;
474299742Sdim                  svn_revnum_t max_rev;
475299742Sdim
476299742Sdim                  /* Perform some sanity checks on the checked-out external. */
477299742Sdim
478299742Sdim                  SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
479299742Sdim                                                        ctx->wc_ctx,
480299742Sdim                                                        external_abspath, NULL,
481299742Sdim                                                        iterpool));
482299742Sdim                  if (is_switched)
483299742Sdim                    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
484299742Sdim                                             NULL,
485299742Sdim                                             _("Cannot pin external '%s' defined "
486299742Sdim                                               "in %s at '%s' because '%s' has "
487299742Sdim                                               "switched subtrees (switches "
488299742Sdim                                               "cannot be represented in %s)"),
489299742Sdim                                             item->url, SVN_PROP_EXTERNALS,
490299742Sdim                                             svn_dirent_local_style(
491299742Sdim                                               local_abspath_or_url, iterpool),
492299742Sdim                                             svn_dirent_local_style(
493299742Sdim                                               external_abspath, iterpool),
494299742Sdim                                             SVN_PROP_EXTERNALS);
495299742Sdim
496299742Sdim                  SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
497299742Sdim                                                 external_abspath, TRUE,
498299742Sdim                                                 ctx->cancel_func,
499299742Sdim                                                 ctx->cancel_baton,
500299742Sdim                                                 iterpool));
501299742Sdim                  if (is_modified)
502299742Sdim                    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
503299742Sdim                                             NULL,
504299742Sdim                                             _("Cannot pin external '%s' defined "
505299742Sdim                                               "in %s at '%s' because '%s' has "
506299742Sdim                                               "local modifications (local "
507299742Sdim                                               "modifications cannot be "
508299742Sdim                                               "represented in %s)"),
509299742Sdim                                             item->url, SVN_PROP_EXTERNALS,
510299742Sdim                                             svn_dirent_local_style(
511299742Sdim                                               local_abspath_or_url, iterpool),
512299742Sdim                                             svn_dirent_local_style(
513299742Sdim                                               external_abspath, iterpool),
514299742Sdim                                             SVN_PROP_EXTERNALS);
515299742Sdim
516299742Sdim                  SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
517299742Sdim                                                    external_abspath, FALSE,
518299742Sdim                                                    iterpool));
519299742Sdim                  if (min_rev != max_rev)
520299742Sdim                    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
521299742Sdim                                             NULL,
522299742Sdim                                             _("Cannot pin external '%s' defined "
523299742Sdim                                               "in %s at '%s' because '%s' is a "
524299742Sdim                                               "mixed-revision working copy "
525299742Sdim                                               "(mixed-revisions cannot be "
526299742Sdim                                               "represented in %s)"),
527299742Sdim                                             item->url, SVN_PROP_EXTERNALS,
528299742Sdim                                             svn_dirent_local_style(
529299742Sdim                                               local_abspath_or_url, iterpool),
530299742Sdim                                             svn_dirent_local_style(
531299742Sdim                                               external_abspath, iterpool),
532299742Sdim                                             SVN_PROP_EXTERNALS);
533299742Sdim                  external_checked_out_rev = min_rev;
534299742Sdim                }
535299742Sdim              else
536299742Sdim                {
537299742Sdim                  SVN_ERR_ASSERT(external_kind == svn_node_file);
538299742Sdim                  SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
539299742Sdim                                                      NULL, NULL, NULL,
540299742Sdim                                                      ctx->wc_ctx, external_abspath,
541299742Sdim                                                      iterpool, iterpool));
542299742Sdim                }
543299742Sdim
544299742Sdim              external_pegrev.kind = svn_opt_revision_number;
545299742Sdim              external_pegrev.value.number = external_checked_out_rev;
546299742Sdim            }
547299742Sdim        }
548299742Sdim
549299742Sdim      SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
550299742Sdim                     external_pegrev.kind == svn_opt_revision_number);
551299742Sdim
552299742Sdim      SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
553299742Sdim                                        item, info, external_pegrev, iterpool));
554299742Sdim
555299742Sdim      svn_stringbuf_appendcstr(buf, pinned_desc);
556299742Sdim    }
557299742Sdim  svn_pool_destroy(iterpool);
558299742Sdim
559299742Sdim  if (pinned_items > 0)
560299742Sdim    *pinned_externals = svn_string_create_from_buf(buf, result_pool);
561299742Sdim  else
562299742Sdim    *pinned_externals = NULL;
563299742Sdim
564299742Sdim  return SVN_NO_ERROR;
565299742Sdim}
566299742Sdim
567299742Sdim/* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths
568299742Sdim * to svn:externals property values (as const char *), where some or all
569299742Sdim * external references have been pinned.
570299742Sdim * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals
571299742Sdim * mentioned in EXTERNALS_TO_PIN.
572299742Sdim * The pinning operation takes place as part of the copy operation for
573299742Sdim * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL
574299742Sdim * to contact the repository containing the externals definition, if neccesary.
575299742Sdim * Use CX to fopen additional RA sessions to external repositories, if
576299742Sdim * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL.
577299742Sdim * Use SCRATCH_POOL for temporary allocations. */
578299742Sdimstatic svn_error_t *
579299742Sdimresolve_pinned_externals(apr_hash_t **pinned_externals,
580299742Sdim                         const apr_hash_t *externals_to_pin,
581299742Sdim                         svn_client__copy_pair_t *pair,
582299742Sdim                         svn_ra_session_t *ra_session,
583299742Sdim                         const char *repos_root_url,
584299742Sdim                         svn_client_ctx_t *ctx,
585299742Sdim                         apr_pool_t *result_pool,
586299742Sdim                         apr_pool_t *scratch_pool)
587299742Sdim{
588299742Sdim  const char *old_url = NULL;
589299742Sdim  apr_hash_t *externals_props;
590299742Sdim  apr_hash_index_t *hi;
591299742Sdim  apr_pool_t *iterpool;
592299742Sdim
593299742Sdim  *pinned_externals = apr_hash_make(result_pool);
594299742Sdim
595299742Sdim  if (svn_path_is_url(pair->src_abspath_or_url))
596299742Sdim    {
597299742Sdim      SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
598299742Sdim                                                pair->src_abspath_or_url,
599299742Sdim                                                scratch_pool));
600299742Sdim      externals_props = apr_hash_make(scratch_pool);
601299742Sdim      SVN_ERR(svn_client__remote_propget(externals_props, NULL,
602299742Sdim                                         SVN_PROP_EXTERNALS,
603299742Sdim                                         pair->src_abspath_or_url, "",
604299742Sdim                                         svn_node_dir,
605299742Sdim                                         pair->src_revnum,
606299742Sdim                                         ra_session,
607299742Sdim                                         svn_depth_infinity,
608299742Sdim                                         scratch_pool,
609299742Sdim                                         scratch_pool));
610299742Sdim    }
611299742Sdim  else
612299742Sdim    {
613299742Sdim      SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
614299742Sdim                                                   ctx->wc_ctx,
615299742Sdim                                                   pair->src_abspath_or_url,
616299742Sdim                                                   svn_depth_infinity,
617299742Sdim                                                   scratch_pool, scratch_pool));
618299742Sdim
619299742Sdim      /* ### gather_definitions returns propvals as const char * */
620299742Sdim      for (hi = apr_hash_first(scratch_pool, externals_props);
621299742Sdim           hi;
622299742Sdim           hi = apr_hash_next(hi))
623299742Sdim        {
624299742Sdim          const char *local_abspath_or_url = apr_hash_this_key(hi);
625299742Sdim          const char *propval = apr_hash_this_val(hi);
626299742Sdim          svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
627299742Sdim
628299742Sdim          svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
629299742Sdim        }
630299742Sdim    }
631299742Sdim
632299742Sdim  if (apr_hash_count(externals_props) == 0)
633299742Sdim    {
634299742Sdim      if (old_url)
635299742Sdim        SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
636299742Sdim      return SVN_NO_ERROR;
637299742Sdim    }
638299742Sdim
639299742Sdim  iterpool = svn_pool_create(scratch_pool);
640299742Sdim  for (hi = apr_hash_first(scratch_pool, externals_props);
641299742Sdim       hi;
642299742Sdim       hi = apr_hash_next(hi))
643299742Sdim    {
644299742Sdim      const char *local_abspath_or_url = apr_hash_this_key(hi);
645299742Sdim      svn_string_t *externals_propval = apr_hash_this_val(hi);
646299742Sdim      const char *relpath;
647299742Sdim      svn_string_t *new_propval;
648299742Sdim
649299742Sdim      svn_pool_clear(iterpool);
650299742Sdim
651299742Sdim      SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
652299742Sdim                                 externals_to_pin,
653299742Sdim                                 repos_root_url, local_abspath_or_url, ctx,
654299742Sdim                                 result_pool, iterpool));
655299742Sdim      if (new_propval)
656299742Sdim        {
657299742Sdim          if (svn_path_is_url(pair->src_abspath_or_url))
658299742Sdim            relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
659299742Sdim                                            local_abspath_or_url,
660299742Sdim                                            result_pool);
661299742Sdim          else
662299742Sdim            relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
663299742Sdim                                               local_abspath_or_url);
664299742Sdim          SVN_ERR_ASSERT(relpath);
665299742Sdim
666299742Sdim          svn_hash_sets(*pinned_externals, relpath, new_propval);
667299742Sdim        }
668299742Sdim    }
669299742Sdim  svn_pool_destroy(iterpool);
670299742Sdim
671299742Sdim  if (old_url)
672299742Sdim    SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
673299742Sdim
674299742Sdim  return SVN_NO_ERROR;
675299742Sdim}
676299742Sdim
677299742Sdim
678299742Sdim
679251881Speter/* The guts of do_wc_to_wc_copies */
680251881Speterstatic svn_error_t *
681251881Speterdo_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
682251881Speter                                   const apr_array_header_t *copy_pairs,
683251881Speter                                   const char *dst_parent,
684299742Sdim                                   svn_boolean_t metadata_only,
685299742Sdim                                   svn_boolean_t pin_externals,
686299742Sdim                                   const apr_hash_t *externals_to_pin,
687251881Speter                                   svn_client_ctx_t *ctx,
688251881Speter                                   apr_pool_t *scratch_pool)
689251881Speter{
690251881Speter  int i;
691251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
692251881Speter  svn_error_t *err = SVN_NO_ERROR;
693251881Speter
694251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
695251881Speter    {
696251881Speter      const char *dst_abspath;
697251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
698251881Speter                                                    svn_client__copy_pair_t *);
699299742Sdim      apr_hash_t *pinned_externals = NULL;
700299742Sdim
701251881Speter      svn_pool_clear(iterpool);
702251881Speter
703251881Speter      /* Check for cancellation */
704251881Speter      if (ctx->cancel_func)
705251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
706251881Speter
707299742Sdim      if (pin_externals)
708299742Sdim        {
709299742Sdim          const char *repos_root_url;
710299742Sdim
711299742Sdim          SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
712299742Sdim                                          NULL, NULL, NULL, ctx->wc_ctx,
713299742Sdim                                          pair->src_abspath_or_url, FALSE,
714299742Sdim                                          scratch_pool, iterpool));
715299742Sdim          SVN_ERR(resolve_pinned_externals(&pinned_externals,
716299742Sdim                                           externals_to_pin, pair, NULL,
717299742Sdim                                           repos_root_url, ctx,
718299742Sdim                                           iterpool, iterpool));
719299742Sdim        }
720299742Sdim
721251881Speter      /* Perform the copy */
722251881Speter      dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
723251881Speter                                    iterpool);
724251881Speter      *timestamp_sleep = TRUE;
725251881Speter      err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
726299742Sdim                         metadata_only,
727251881Speter                         ctx->cancel_func, ctx->cancel_baton,
728251881Speter                         ctx->notify_func2, ctx->notify_baton2, iterpool);
729251881Speter      if (err)
730251881Speter        break;
731299742Sdim
732299742Sdim      if (pinned_externals)
733299742Sdim        {
734299742Sdim          apr_hash_index_t *hi;
735299742Sdim
736299742Sdim          for (hi = apr_hash_first(iterpool, pinned_externals);
737299742Sdim               hi;
738299742Sdim               hi = apr_hash_next(hi))
739299742Sdim            {
740299742Sdim              const char *dst_relpath = apr_hash_this_key(hi);
741299742Sdim              svn_string_t *externals_propval = apr_hash_this_val(hi);
742299742Sdim              const char *local_abspath;
743299742Sdim
744299742Sdim              local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
745299742Sdim                                              dst_relpath, iterpool);
746299742Sdim              /* ### use a work queue? */
747299742Sdim              SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
748299742Sdim                                       SVN_PROP_EXTERNALS, externals_propval,
749299742Sdim                                       svn_depth_empty, TRUE /* skip_checks */,
750299742Sdim                                       NULL  /* changelist_filter */,
751299742Sdim                                       ctx->cancel_func, ctx->cancel_baton,
752299742Sdim                                       NULL, NULL, /* no extra notification */
753299742Sdim                                       iterpool));
754299742Sdim            }
755299742Sdim        }
756251881Speter    }
757251881Speter  svn_pool_destroy(iterpool);
758251881Speter
759251881Speter  SVN_ERR(err);
760251881Speter  return SVN_NO_ERROR;
761251881Speter}
762251881Speter
763251881Speter/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST.  Use POOL for temporary
764251881Speter   allocations. */
765251881Speterstatic svn_error_t *
766251881Speterdo_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
767251881Speter                   const apr_array_header_t *copy_pairs,
768299742Sdim                   svn_boolean_t metadata_only,
769299742Sdim                   svn_boolean_t pin_externals,
770299742Sdim                   const apr_hash_t *externals_to_pin,
771251881Speter                   svn_client_ctx_t *ctx,
772251881Speter                   apr_pool_t *pool)
773251881Speter{
774251881Speter  const char *dst_parent, *dst_parent_abspath;
775251881Speter
776251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
777251881Speter  if (copy_pairs->nelts == 1)
778251881Speter    dst_parent = svn_dirent_dirname(dst_parent, pool);
779251881Speter
780251881Speter  SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
781251881Speter
782251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
783251881Speter    do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
784299742Sdim                                       metadata_only, pin_externals,
785299742Sdim                                       externals_to_pin, ctx, pool),
786251881Speter    ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
787251881Speter
788251881Speter  return SVN_NO_ERROR;
789251881Speter}
790251881Speter
791251881Speter/* The locked bit of do_wc_to_wc_moves. */
792251881Speterstatic svn_error_t *
793251881Speterdo_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
794251881Speter                              const char *dst_parent_abspath,
795251881Speter                              svn_boolean_t lock_src,
796251881Speter                              svn_boolean_t lock_dst,
797251881Speter                              svn_boolean_t allow_mixed_revisions,
798251881Speter                              svn_boolean_t metadata_only,
799251881Speter                              svn_client_ctx_t *ctx,
800251881Speter                              apr_pool_t *scratch_pool)
801251881Speter{
802251881Speter  const char *dst_abspath;
803251881Speter
804251881Speter  dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
805251881Speter                                scratch_pool);
806251881Speter
807251881Speter  SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
808251881Speter                        dst_abspath, metadata_only,
809251881Speter                        allow_mixed_revisions,
810251881Speter                        ctx->cancel_func, ctx->cancel_baton,
811251881Speter                        ctx->notify_func2, ctx->notify_baton2,
812251881Speter                        scratch_pool));
813251881Speter
814251881Speter  return SVN_NO_ERROR;
815251881Speter}
816251881Speter
817251881Speter/* Wrapper to add an optional second lock */
818251881Speterstatic svn_error_t *
819251881Speterdo_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
820251881Speter                              const char *dst_parent_abspath,
821251881Speter                              svn_boolean_t lock_src,
822251881Speter                              svn_boolean_t lock_dst,
823251881Speter                              svn_boolean_t allow_mixed_revisions,
824251881Speter                              svn_boolean_t metadata_only,
825251881Speter                              svn_client_ctx_t *ctx,
826251881Speter                              apr_pool_t *scratch_pool)
827251881Speter{
828251881Speter  if (lock_dst)
829251881Speter    SVN_WC__CALL_WITH_WRITE_LOCK(
830251881Speter      do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
831251881Speter                                    lock_dst, allow_mixed_revisions,
832251881Speter                                    metadata_only,
833251881Speter                                    ctx, scratch_pool),
834251881Speter      ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
835251881Speter  else
836251881Speter    SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
837251881Speter                                          lock_dst, allow_mixed_revisions,
838251881Speter                                          metadata_only,
839251881Speter                                          ctx, scratch_pool));
840251881Speter
841251881Speter  return SVN_NO_ERROR;
842251881Speter}
843251881Speter
844251881Speter/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
845251881Speter   afterwards.  Use POOL for temporary allocations. */
846251881Speterstatic svn_error_t *
847251881Speterdo_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
848251881Speter                  const apr_array_header_t *copy_pairs,
849251881Speter                  const char *dst_path,
850251881Speter                  svn_boolean_t allow_mixed_revisions,
851251881Speter                  svn_boolean_t metadata_only,
852251881Speter                  svn_client_ctx_t *ctx,
853251881Speter                  apr_pool_t *pool)
854251881Speter{
855251881Speter  int i;
856251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
857251881Speter  svn_error_t *err = SVN_NO_ERROR;
858251881Speter
859251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
860251881Speter    {
861251881Speter      const char *src_parent_abspath;
862251881Speter      svn_boolean_t lock_src, lock_dst;
863262253Speter      const char *src_wcroot_abspath;
864262253Speter      const char *dst_wcroot_abspath;
865251881Speter
866251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
867251881Speter                                                    svn_client__copy_pair_t *);
868251881Speter      svn_pool_clear(iterpool);
869251881Speter
870251881Speter      /* Check for cancellation */
871251881Speter      if (ctx->cancel_func)
872251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
873251881Speter
874251881Speter      src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
875251881Speter                                              iterpool);
876251881Speter
877262253Speter      SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
878262253Speter                                 ctx->wc_ctx, src_parent_abspath,
879262253Speter                                 iterpool, iterpool));
880262253Speter      SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
881262253Speter                                 ctx->wc_ctx, pair->dst_parent_abspath,
882262253Speter                                 iterpool, iterpool));
883262253Speter
884251881Speter      /* We now need to lock the right combination of batons.
885251881Speter         Four cases:
886251881Speter           1) src_parent == dst_parent
887251881Speter           2) src_parent is parent of dst_parent
888251881Speter           3) dst_parent is parent of src_parent
889251881Speter           4) src_parent and dst_parent are disjoint
890251881Speter         We can handle 1) as either 2) or 3) */
891251881Speter      if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
892262253Speter          || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
893262253Speter                                  NULL)
894262253Speter              && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
895262253Speter                                      NULL)))
896251881Speter        {
897251881Speter          lock_src = TRUE;
898251881Speter          lock_dst = FALSE;
899251881Speter        }
900251881Speter      else if (svn_dirent_is_child(pair->dst_parent_abspath,
901262253Speter                                   src_parent_abspath, NULL)
902262253Speter               && !svn_dirent_is_child(pair->dst_parent_abspath,
903262253Speter                                       src_wcroot_abspath, NULL))
904251881Speter        {
905251881Speter          lock_src = FALSE;
906251881Speter          lock_dst = TRUE;
907251881Speter        }
908251881Speter      else
909251881Speter        {
910251881Speter          lock_src = TRUE;
911251881Speter          lock_dst = TRUE;
912251881Speter        }
913251881Speter
914251881Speter      *timestamp_sleep = TRUE;
915251881Speter
916251881Speter      /* Perform the copy and then the delete. */
917251881Speter      if (lock_src)
918251881Speter        SVN_WC__CALL_WITH_WRITE_LOCK(
919251881Speter          do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
920251881Speter                                        lock_src, lock_dst,
921251881Speter                                        allow_mixed_revisions,
922251881Speter                                        metadata_only,
923251881Speter                                        ctx, iterpool),
924251881Speter          ctx->wc_ctx, src_parent_abspath,
925251881Speter          FALSE, iterpool);
926251881Speter      else
927251881Speter        SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
928251881Speter                                              lock_src, lock_dst,
929251881Speter                                              allow_mixed_revisions,
930251881Speter                                              metadata_only,
931251881Speter                                              ctx, iterpool));
932251881Speter
933251881Speter    }
934251881Speter  svn_pool_destroy(iterpool);
935251881Speter
936251881Speter  return svn_error_trace(err);
937251881Speter}
938251881Speter
939251881Speter/* Verify that the destinations stored in COPY_PAIRS are valid working copy
940251881Speter   destinations and set pair->dst_parent_abspath and pair->base_name for each
941251881Speter   item to the resulting location if they do */
942251881Speterstatic svn_error_t *
943251881Speterverify_wc_dsts(const apr_array_header_t *copy_pairs,
944251881Speter               svn_boolean_t make_parents,
945251881Speter               svn_boolean_t is_move,
946253734Speter               svn_boolean_t metadata_only,
947251881Speter               svn_client_ctx_t *ctx,
948251881Speter               apr_pool_t *result_pool,
949251881Speter               apr_pool_t *scratch_pool)
950251881Speter{
951251881Speter  int i;
952251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
953251881Speter
954251881Speter  /* Check that DST does not exist, but its parent does */
955251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
956251881Speter    {
957251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
958251881Speter                                                    svn_client__copy_pair_t *);
959251881Speter      svn_node_kind_t dst_kind, dst_parent_kind;
960251881Speter
961251881Speter      svn_pool_clear(iterpool);
962251881Speter
963251881Speter      /* If DST_PATH does not exist, then its basename will become a new
964251881Speter         file or dir added to its parent (possibly an implicit '.').
965251881Speter         Else, just error out. */
966251881Speter      SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
967251881Speter                                pair->dst_abspath_or_url,
968251881Speter                                FALSE /* show_deleted */,
969251881Speter                                TRUE /* show_hidden */,
970251881Speter                                iterpool));
971251881Speter      if (dst_kind != svn_node_none)
972251881Speter        {
973251881Speter          svn_boolean_t is_excluded;
974251881Speter          svn_boolean_t is_server_excluded;
975251881Speter
976251881Speter          SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
977251881Speter                                              &is_server_excluded, ctx->wc_ctx,
978251881Speter                                              pair->dst_abspath_or_url, FALSE,
979251881Speter                                              iterpool));
980251881Speter
981251881Speter          if (is_excluded || is_server_excluded)
982251881Speter            {
983251881Speter              return svn_error_createf(
984251881Speter                  SVN_ERR_WC_OBSTRUCTED_UPDATE,
985251881Speter                  NULL, _("Path '%s' exists, but is excluded"),
986251881Speter                  svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
987251881Speter            }
988251881Speter          else
989251881Speter            return svn_error_createf(
990251881Speter                            SVN_ERR_ENTRY_EXISTS, NULL,
991251881Speter                            _("Path '%s' already exists"),
992251881Speter                            svn_dirent_local_style(pair->dst_abspath_or_url,
993251881Speter                                                   scratch_pool));
994251881Speter        }
995251881Speter
996251881Speter      /* Check that there is no unversioned obstruction */
997253734Speter      if (metadata_only)
998253734Speter        dst_kind = svn_node_none;
999253734Speter      else
1000253734Speter        SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1001253734Speter                                  iterpool));
1002251881Speter
1003251881Speter      if (dst_kind != svn_node_none)
1004251881Speter        {
1005251881Speter          if (is_move
1006251881Speter              && copy_pairs->nelts == 1
1007251881Speter              && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
1008251881Speter                        svn_dirent_dirname(pair->dst_abspath_or_url,
1009251881Speter                                           iterpool)) == 0)
1010251881Speter            {
1011251881Speter              const char *dst;
1012251881Speter              char *dst_apr;
1013251881Speter              apr_status_t apr_err;
1014251881Speter              /* We have a rename inside a directory, which might collide
1015251881Speter                 just because the case insensivity of the filesystem makes
1016251881Speter                 the source match the destination. */
1017251881Speter
1018251881Speter              SVN_ERR(svn_path_cstring_from_utf8(&dst,
1019251881Speter                                                 pair->dst_abspath_or_url,
1020251881Speter                                                 scratch_pool));
1021251881Speter
1022251881Speter              apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
1023251881Speter                                           APR_FILEPATH_TRUENAME, iterpool);
1024251881Speter
1025251881Speter              if (!apr_err)
1026251881Speter                {
1027251881Speter                  /* And now bring it back to our canonical format */
1028251881Speter                  SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
1029251881Speter                  dst = svn_dirent_canonicalize(dst, iterpool);
1030251881Speter                }
1031251881Speter              /* else: Don't report this error; just report the normal error */
1032251881Speter
1033251881Speter              if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
1034251881Speter                {
1035251881Speter                  /* Ok, we have a single case only rename. Get out of here */
1036251881Speter                  svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1037251881Speter                                   pair->dst_abspath_or_url, result_pool);
1038251881Speter
1039251881Speter                  svn_pool_destroy(iterpool);
1040251881Speter                  return SVN_NO_ERROR;
1041251881Speter                }
1042251881Speter            }
1043251881Speter
1044251881Speter          return svn_error_createf(
1045251881Speter                            SVN_ERR_ENTRY_EXISTS, NULL,
1046251881Speter                            _("Path '%s' already exists as unversioned node"),
1047251881Speter                            svn_dirent_local_style(pair->dst_abspath_or_url,
1048251881Speter                                                   scratch_pool));
1049251881Speter        }
1050251881Speter
1051251881Speter      svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1052251881Speter                       pair->dst_abspath_or_url, result_pool);
1053251881Speter
1054251881Speter      /* Make sure the destination parent is a directory and produce a clear
1055251881Speter         error message if it is not. */
1056251881Speter      SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
1057251881Speter                                ctx->wc_ctx, pair->dst_parent_abspath,
1058251881Speter                                FALSE, TRUE,
1059251881Speter                                iterpool));
1060251881Speter      if (make_parents && dst_parent_kind == svn_node_none)
1061251881Speter        {
1062251881Speter          SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1063251881Speter                                                 TRUE, ctx, iterpool));
1064251881Speter        }
1065251881Speter      else if (dst_parent_kind != svn_node_dir)
1066251881Speter        {
1067251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1068251881Speter                                   _("Path '%s' is not a directory"),
1069251881Speter                                   svn_dirent_local_style(
1070251881Speter                                     pair->dst_parent_abspath, scratch_pool));
1071251881Speter        }
1072251881Speter
1073251881Speter      SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1074251881Speter                                &dst_parent_kind, scratch_pool));
1075251881Speter
1076251881Speter      if (dst_parent_kind != svn_node_dir)
1077251881Speter        return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1078251881Speter                                 _("Path '%s' is not a directory"),
1079251881Speter                                 svn_dirent_local_style(
1080251881Speter                                     pair->dst_parent_abspath, scratch_pool));
1081251881Speter    }
1082251881Speter
1083251881Speter  svn_pool_destroy(iterpool);
1084251881Speter
1085251881Speter  return SVN_NO_ERROR;
1086251881Speter}
1087251881Speter
1088251881Speterstatic svn_error_t *
1089251881Speterverify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1090251881Speter                        svn_boolean_t make_parents,
1091251881Speter                        svn_boolean_t is_move,
1092253734Speter                        svn_boolean_t metadata_only,
1093251881Speter                        svn_client_ctx_t *ctx,
1094251881Speter                        apr_pool_t *result_pool,
1095251881Speter                        apr_pool_t *scratch_pool)
1096251881Speter{
1097251881Speter  int i;
1098251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1099251881Speter
1100251881Speter  /* Check that all of our SRCs exist. */
1101251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1102251881Speter    {
1103251881Speter      svn_boolean_t deleted_ok;
1104251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1105251881Speter                                                    svn_client__copy_pair_t *);
1106251881Speter      svn_pool_clear(iterpool);
1107251881Speter
1108251881Speter      deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1109251881Speter                    || pair->src_op_revision.kind == svn_opt_revision_base);
1110251881Speter
1111251881Speter      /* Verify that SRC_PATH exists. */
1112251881Speter      SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1113251881Speter                               pair->src_abspath_or_url,
1114251881Speter                               deleted_ok, FALSE, iterpool));
1115251881Speter      if (pair->src_kind == svn_node_none)
1116251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1117251881Speter                                 _("Path '%s' does not exist"),
1118251881Speter                                 svn_dirent_local_style(
1119251881Speter                                        pair->src_abspath_or_url,
1120251881Speter                                        scratch_pool));
1121251881Speter    }
1122251881Speter
1123253734Speter  SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1124251881Speter                         result_pool, iterpool));
1125251881Speter
1126251881Speter  svn_pool_destroy(iterpool);
1127251881Speter
1128251881Speter  return SVN_NO_ERROR;
1129251881Speter}
1130251881Speter
1131251881Speter
1132251881Speter/* Path-specific state used as part of path_driver_cb_baton. */
1133251881Spetertypedef struct path_driver_info_t
1134251881Speter{
1135251881Speter  const char *src_url;
1136251881Speter  const char *src_path;
1137251881Speter  const char *dst_path;
1138251881Speter  svn_node_kind_t src_kind;
1139251881Speter  svn_revnum_t src_revnum;
1140251881Speter  svn_boolean_t resurrection;
1141251881Speter  svn_boolean_t dir_add;
1142251881Speter  svn_string_t *mergeinfo;  /* the new mergeinfo for the target */
1143299742Sdim  svn_string_t *externals; /* new externals definitions for the target */
1144299742Sdim  svn_boolean_t only_pin_externals;
1145251881Speter} path_driver_info_t;
1146251881Speter
1147251881Speter
1148251881Speter/* The baton used with the path_driver_cb_func() callback for a copy
1149251881Speter   or move operation. */
1150251881Speterstruct path_driver_cb_baton
1151251881Speter{
1152251881Speter  /* The editor (and its state) used to perform the operation. */
1153251881Speter  const svn_delta_editor_t *editor;
1154251881Speter  void *edit_baton;
1155251881Speter
1156251881Speter  /* A hash of path -> path_driver_info_t *'s. */
1157251881Speter  apr_hash_t *action_hash;
1158251881Speter
1159251881Speter  /* Whether the operation is a move or copy. */
1160251881Speter  svn_boolean_t is_move;
1161251881Speter};
1162251881Speter
1163251881Speterstatic svn_error_t *
1164251881Speterpath_driver_cb_func(void **dir_baton,
1165251881Speter                    void *parent_baton,
1166251881Speter                    void *callback_baton,
1167251881Speter                    const char *path,
1168251881Speter                    apr_pool_t *pool)
1169251881Speter{
1170251881Speter  struct path_driver_cb_baton *cb_baton = callback_baton;
1171251881Speter  svn_boolean_t do_delete = FALSE, do_add = FALSE;
1172251881Speter  path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1173251881Speter
1174251881Speter  /* Initialize return value. */
1175251881Speter  *dir_baton = NULL;
1176251881Speter
1177251881Speter  /* This function should never get an empty PATH.  We can neither
1178251881Speter     create nor delete the empty PATH, so if someone is calling us
1179251881Speter     with such, the code is just plain wrong. */
1180251881Speter  SVN_ERR_ASSERT(! svn_path_is_empty(path));
1181251881Speter
1182299742Sdim  /* Check to see if we need to add the path as a parent directory. */
1183251881Speter  if (path_info->dir_add)
1184251881Speter    {
1185251881Speter      return cb_baton->editor->add_directory(path, parent_baton, NULL,
1186251881Speter                                             SVN_INVALID_REVNUM, pool,
1187251881Speter                                             dir_baton);
1188251881Speter    }
1189251881Speter
1190251881Speter  /* If this is a resurrection, we know the source and dest paths are
1191251881Speter     the same, and that our driver will only be calling us once.  */
1192251881Speter  if (path_info->resurrection)
1193251881Speter    {
1194251881Speter      /* If this is a move, we do nothing.  Otherwise, we do the copy.  */
1195251881Speter      if (! cb_baton->is_move)
1196251881Speter        do_add = TRUE;
1197251881Speter    }
1198251881Speter  /* Not a resurrection. */
1199251881Speter  else
1200251881Speter    {
1201251881Speter      /* If this is a move, we check PATH to see if it is the source
1202251881Speter         or the destination of the move. */
1203251881Speter      if (cb_baton->is_move)
1204251881Speter        {
1205251881Speter          if (strcmp(path_info->src_path, path) == 0)
1206251881Speter            do_delete = TRUE;
1207251881Speter          else
1208251881Speter            do_add = TRUE;
1209251881Speter        }
1210251881Speter      /* Not a move?  This must just be the copy addition. */
1211251881Speter      else
1212251881Speter        {
1213299742Sdim          do_add = !path_info->only_pin_externals;
1214251881Speter        }
1215251881Speter    }
1216251881Speter
1217251881Speter  if (do_delete)
1218251881Speter    {
1219251881Speter      SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1220251881Speter                                             parent_baton, pool));
1221251881Speter    }
1222251881Speter  if (do_add)
1223251881Speter    {
1224251881Speter      SVN_ERR(svn_path_check_valid(path, pool));
1225251881Speter
1226251881Speter      if (path_info->src_kind == svn_node_file)
1227251881Speter        {
1228251881Speter          void *file_baton;
1229251881Speter          SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1230251881Speter                                             path_info->src_url,
1231251881Speter                                             path_info->src_revnum,
1232251881Speter                                             pool, &file_baton));
1233251881Speter          if (path_info->mergeinfo)
1234251881Speter            SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1235251881Speter                                                       SVN_PROP_MERGEINFO,
1236251881Speter                                                       path_info->mergeinfo,
1237251881Speter                                                       pool));
1238251881Speter          SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1239251881Speter        }
1240251881Speter      else
1241251881Speter        {
1242251881Speter          SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1243251881Speter                                                  path_info->src_url,
1244251881Speter                                                  path_info->src_revnum,
1245251881Speter                                                  pool, dir_baton));
1246251881Speter          if (path_info->mergeinfo)
1247251881Speter            SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1248251881Speter                                                      SVN_PROP_MERGEINFO,
1249251881Speter                                                      path_info->mergeinfo,
1250251881Speter                                                      pool));
1251251881Speter        }
1252251881Speter    }
1253299742Sdim
1254299742Sdim  if (path_info->externals)
1255299742Sdim    {
1256299742Sdim      if (*dir_baton == NULL)
1257299742Sdim        SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1258299742Sdim                                                 SVN_INVALID_REVNUM,
1259299742Sdim                                                 pool, dir_baton));
1260299742Sdim
1261299742Sdim      SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1262299742Sdim                                                path_info->externals, pool));
1263299742Sdim    }
1264299742Sdim
1265251881Speter  return SVN_NO_ERROR;
1266251881Speter}
1267251881Speter
1268251881Speter
1269251881Speter/* Starting with the path DIR relative to the RA_SESSION's session
1270251881Speter   URL, work up through DIR's parents until an existing node is found.
1271251881Speter   Push each nonexistent path onto the array NEW_DIRS, allocating in
1272251881Speter   POOL.  Raise an error if the existing node is not a directory.
1273251881Speter
1274251881Speter   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1275251881Speter   ### implementation susceptible to race conditions.  */
1276251881Speterstatic svn_error_t *
1277251881Speterfind_absent_parents1(svn_ra_session_t *ra_session,
1278251881Speter                     const char *dir,
1279251881Speter                     apr_array_header_t *new_dirs,
1280251881Speter                     apr_pool_t *pool)
1281251881Speter{
1282251881Speter  svn_node_kind_t kind;
1283251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1284251881Speter
1285251881Speter  SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1286251881Speter                            iterpool));
1287251881Speter
1288251881Speter  while (kind == svn_node_none)
1289251881Speter    {
1290251881Speter      svn_pool_clear(iterpool);
1291251881Speter
1292251881Speter      APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1293251881Speter      dir = svn_dirent_dirname(dir, pool);
1294251881Speter
1295251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1296251881Speter                                &kind, iterpool));
1297251881Speter    }
1298251881Speter
1299251881Speter  if (kind != svn_node_dir)
1300251881Speter    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1301251881Speter                             _("Path '%s' already exists, but is not a "
1302251881Speter                               "directory"), dir);
1303251881Speter
1304251881Speter  svn_pool_destroy(iterpool);
1305251881Speter  return SVN_NO_ERROR;
1306251881Speter}
1307251881Speter
1308251881Speter/* Starting with the URL *TOP_DST_URL which is also the root of
1309251881Speter   RA_SESSION, work up through its parents until an existing node is
1310251881Speter   found. Push each nonexistent URL onto the array NEW_DIRS,
1311251881Speter   allocating in POOL.  Raise an error if the existing node is not a
1312251881Speter   directory.
1313251881Speter
1314251881Speter   Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1315251881Speter
1316251881Speter   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1317251881Speter   ### implementation susceptible to race conditions.  */
1318251881Speterstatic svn_error_t *
1319251881Speterfind_absent_parents2(svn_ra_session_t *ra_session,
1320251881Speter                     const char **top_dst_url,
1321251881Speter                     apr_array_header_t *new_dirs,
1322251881Speter                     apr_pool_t *pool)
1323251881Speter{
1324251881Speter  const char *root_url = *top_dst_url;
1325251881Speter  svn_node_kind_t kind;
1326251881Speter
1327251881Speter  SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1328251881Speter                            pool));
1329251881Speter
1330251881Speter  while (kind == svn_node_none)
1331251881Speter    {
1332251881Speter      APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1333251881Speter      root_url = svn_uri_dirname(root_url, pool);
1334251881Speter
1335251881Speter      SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1336251881Speter      SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1337251881Speter                                pool));
1338251881Speter    }
1339251881Speter
1340251881Speter  if (kind != svn_node_dir)
1341251881Speter    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1342251881Speter                _("Path '%s' already exists, but is not a directory"),
1343251881Speter                root_url);
1344251881Speter
1345251881Speter  *top_dst_url = root_url;
1346251881Speter  return SVN_NO_ERROR;
1347251881Speter}
1348251881Speter
1349299742Sdim/* Queue property changes for pinning svn:externals properties set on
1350299742Sdim * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1351299742Sdim * is keyed by the relative path of each descendant which should have some
1352299742Sdim * or all of its externals pinned, with the corresponding pinned svn:externals
1353299742Sdim * properties as values. Property changes are queued in a new list of path
1354299742Sdim * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1355299742Sdim * existing item is found for the descendant. Allocate results in RESULT_POOL.
1356299742Sdim * Use SCRATCH_POOL for temporary allocations. */
1357251881Speterstatic svn_error_t *
1358299742Sdimqueue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1359299742Sdim                                  apr_array_header_t *path_infos,
1360299742Sdim                                  apr_hash_t *pinned_externals,
1361299742Sdim                                  path_driver_info_t *parent_info,
1362299742Sdim                                  apr_pool_t *result_pool,
1363299742Sdim                                  apr_pool_t *scratch_pool)
1364299742Sdim{
1365299742Sdim  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1366299742Sdim  apr_hash_index_t *hi;
1367299742Sdim
1368299742Sdim  for (hi = apr_hash_first(scratch_pool, pinned_externals);
1369299742Sdim       hi;
1370299742Sdim       hi = apr_hash_next(hi))
1371299742Sdim    {
1372299742Sdim      const char *dst_relpath = apr_hash_this_key(hi);
1373299742Sdim      svn_string_t *externals_prop = apr_hash_this_val(hi);
1374299742Sdim      const char *src_url;
1375299742Sdim      path_driver_info_t *info;
1376299742Sdim      int i;
1377299742Sdim
1378299742Sdim      svn_pool_clear(iterpool);
1379299742Sdim
1380299742Sdim      src_url = svn_path_url_add_component2(parent_info->src_url,
1381299742Sdim                                            dst_relpath, iterpool);
1382299742Sdim
1383299742Sdim      /* Try to find a path info the external change can be applied to. */
1384299742Sdim      info = NULL;
1385299742Sdim      for (i = 0; i < path_infos->nelts; i++)
1386299742Sdim        {
1387299742Sdim          path_driver_info_t *existing_info;
1388299742Sdim
1389299742Sdim          existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1390299742Sdim          if (strcmp(src_url, existing_info->src_url) == 0)
1391299742Sdim            {
1392299742Sdim              info = existing_info;
1393299742Sdim              break;
1394299742Sdim            }
1395299742Sdim        }
1396299742Sdim
1397299742Sdim      if (info == NULL)
1398299742Sdim        {
1399299742Sdim          /* A copied-along child needs its externals pinned.
1400299742Sdim             Create a new path info for this property change. */
1401299742Sdim          info = apr_pcalloc(result_pool, sizeof(*info));
1402299742Sdim          info->src_url = svn_path_url_add_component2(
1403299742Sdim                                parent_info->src_url, dst_relpath,
1404299742Sdim                                result_pool);
1405299742Sdim          info->src_path = NULL; /* Only needed on copied dirs */
1406299742Sdim          info->dst_path = svn_relpath_join(parent_info->dst_path,
1407299742Sdim                                            dst_relpath,
1408299742Sdim                                            result_pool);
1409299742Sdim          info->src_kind = svn_node_dir;
1410299742Sdim          info->only_pin_externals = TRUE;
1411299742Sdim          APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1412299742Sdim        }
1413299742Sdim
1414299742Sdim      info->externals = externals_prop;
1415299742Sdim    }
1416299742Sdim
1417299742Sdim  svn_pool_destroy(iterpool);
1418299742Sdim
1419299742Sdim  return SVN_NO_ERROR;
1420299742Sdim}
1421299742Sdim
1422299742Sdimstatic svn_error_t *
1423251881Speterrepos_to_repos_copy(const apr_array_header_t *copy_pairs,
1424251881Speter                    svn_boolean_t make_parents,
1425251881Speter                    const apr_hash_t *revprop_table,
1426251881Speter                    svn_commit_callback2_t commit_callback,
1427251881Speter                    void *commit_baton,
1428251881Speter                    svn_client_ctx_t *ctx,
1429251881Speter                    svn_boolean_t is_move,
1430299742Sdim                    svn_boolean_t pin_externals,
1431299742Sdim                    const apr_hash_t *externals_to_pin,
1432251881Speter                    apr_pool_t *pool)
1433251881Speter{
1434251881Speter  svn_error_t *err;
1435251881Speter  apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1436251881Speter                                             sizeof(const char *));
1437251881Speter  apr_hash_t *action_hash = apr_hash_make(pool);
1438251881Speter  apr_array_header_t *path_infos;
1439251881Speter  const char *top_url, *top_url_all, *top_url_dst;
1440251881Speter  const char *message, *repos_root;
1441251881Speter  svn_ra_session_t *ra_session = NULL;
1442251881Speter  const svn_delta_editor_t *editor;
1443251881Speter  void *edit_baton;
1444251881Speter  struct path_driver_cb_baton cb_baton;
1445251881Speter  apr_array_header_t *new_dirs = NULL;
1446251881Speter  apr_hash_t *commit_revprops;
1447299742Sdim  apr_array_header_t *pin_externals_only_infos = NULL;
1448251881Speter  int i;
1449251881Speter  svn_client__copy_pair_t *first_pair =
1450251881Speter    APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1451251881Speter
1452251881Speter  /* Open an RA session to the first copy pair's destination.  We'll
1453251881Speter     be verifying that every one of our copy source and destination
1454251881Speter     URLs is or is beneath this sucker's repository root URL as a form
1455251881Speter     of a cheap(ish) sanity check.  */
1456251881Speter  SVN_ERR(svn_client_open_ra_session2(&ra_session,
1457251881Speter                                      first_pair->src_abspath_or_url, NULL,
1458251881Speter                                      ctx, pool, pool));
1459251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1460251881Speter
1461251881Speter  /* Verify that sources and destinations are all at or under
1462251881Speter     REPOS_ROOT.  While here, create a path_info struct for each
1463251881Speter     src/dst pair and initialize portions of it with normalized source
1464251881Speter     location information.  */
1465251881Speter  path_infos = apr_array_make(pool, copy_pairs->nelts,
1466251881Speter                              sizeof(path_driver_info_t *));
1467251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1468251881Speter    {
1469251881Speter      path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1470251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1471251881Speter                                                    svn_client__copy_pair_t *);
1472251881Speter      apr_hash_t *mergeinfo;
1473251881Speter
1474251881Speter      /* Are the source and destination URLs at or under REPOS_ROOT? */
1475251881Speter      if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1476251881Speter             && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1477251881Speter        return svn_error_create
1478251881Speter          (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1479251881Speter           _("Source and destination URLs appear not to point to the "
1480251881Speter             "same repository."));
1481251881Speter
1482251881Speter      /* Run the history function to get the source's URL and revnum in the
1483251881Speter         operational revision. */
1484251881Speter      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1485251881Speter      SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1486251881Speter                                          &pair->src_revnum,
1487251881Speter                                          NULL, NULL,
1488251881Speter                                          ra_session,
1489251881Speter                                          pair->src_abspath_or_url,
1490251881Speter                                          &pair->src_peg_revision,
1491251881Speter                                          &pair->src_op_revision, NULL,
1492251881Speter                                          ctx, pool));
1493251881Speter
1494251881Speter      /* Go ahead and grab mergeinfo from the source, too. */
1495251881Speter      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1496251881Speter      SVN_ERR(svn_client__get_repos_mergeinfo(
1497251881Speter                &mergeinfo, ra_session,
1498251881Speter                pair->src_abspath_or_url, pair->src_revnum,
1499251881Speter                svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1500251881Speter      if (mergeinfo)
1501251881Speter        SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1502251881Speter
1503251881Speter      /* Plop an INFO structure onto our array thereof. */
1504251881Speter      info->src_url = pair->src_abspath_or_url;
1505251881Speter      info->src_revnum = pair->src_revnum;
1506251881Speter      info->resurrection = FALSE;
1507251881Speter      APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1508251881Speter    }
1509251881Speter
1510251881Speter  /* If this is a move, we have to open our session to the longest
1511251881Speter     path common to all SRC_URLS and DST_URLS in the repository so we
1512251881Speter     can do existence checks on all paths, and so we can operate on
1513251881Speter     all paths in the case of a move.  But if this is *not* a move,
1514251881Speter     then opening our session at the longest path common to sources
1515251881Speter     *and* destinations might be an optimization when the user is
1516251881Speter     authorized to access all that stuff, but could cause the
1517251881Speter     operation to fail altogether otherwise.  See issue #3242.  */
1518251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1519251881Speter                                  pool));
1520251881Speter  top_url = is_move ? top_url_all : top_url_dst;
1521251881Speter
1522251881Speter  /* Check each src/dst pair for resurrection, and verify that TOP_URL
1523251881Speter     is anchored high enough to cover all the editor_t activities
1524251881Speter     required for this operation.  */
1525251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1526251881Speter    {
1527251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1528251881Speter                                                    svn_client__copy_pair_t *);
1529251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1530251881Speter                                               path_driver_info_t *);
1531251881Speter
1532251881Speter      /* Source and destination are the same?  It's a resurrection. */
1533251881Speter      if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1534251881Speter        info->resurrection = TRUE;
1535251881Speter
1536251881Speter      /* We need to add each dst_URL, and (in a move) we'll need to
1537251881Speter         delete each src_URL.  Our selection of TOP_URL so far ensures
1538251881Speter         that all our destination URLs (and source URLs, for moves)
1539251881Speter         are at least as deep as TOP_URL, but we need to make sure
1540251881Speter         that TOP_URL is an *ancestor* of all our to-be-edited paths.
1541251881Speter
1542251881Speter         Issue #683 is demonstrates this scenario.  If you're
1543251881Speter         resurrecting a deleted item like this: 'svn cp -rN src_URL
1544251881Speter         dst_URL', then src_URL == dst_URL == top_url.  In this
1545251881Speter         situation, we want to open an RA session to be at least the
1546251881Speter         *parent* of all three. */
1547251881Speter      if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1548251881Speter          && (strcmp(top_url, repos_root) != 0))
1549251881Speter        {
1550251881Speter          top_url = svn_uri_dirname(top_url, pool);
1551251881Speter        }
1552251881Speter      if (is_move
1553251881Speter          && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1554251881Speter          && (strcmp(top_url, repos_root) != 0))
1555251881Speter        {
1556251881Speter          top_url = svn_uri_dirname(top_url, pool);
1557251881Speter        }
1558251881Speter    }
1559251881Speter
1560251881Speter  /* Point the RA session to our current TOP_URL. */
1561251881Speter  SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1562251881Speter
1563251881Speter  /* If we're allowed to create nonexistent parent directories of our
1564251881Speter     destinations, then make a list in NEW_DIRS of the parent
1565251881Speter     directories of the destination that don't yet exist.  */
1566251881Speter  if (make_parents)
1567251881Speter    {
1568251881Speter      new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1569251881Speter
1570251881Speter      /* If this is a move, TOP_URL is at least the common ancestor of
1571251881Speter         all the paths (sources and destinations) involved.  Assuming
1572251881Speter         the sources exist (which is fair, because if they don't, this
1573251881Speter         whole operation will fail anyway), TOP_URL must also exist.
1574251881Speter         So it's the paths between TOP_URL and the destinations which
1575251881Speter         we have to check for existence.  But here, we take advantage
1576251881Speter         of the knowledge of our caller.  We know that if there are
1577251881Speter         multiple copy/move operations being requested, then the
1578251881Speter         destinations of the copies/moves will all be siblings of one
1579251881Speter         another.  Therefore, we need only to check for the
1580251881Speter         nonexistent paths between TOP_URL and *one* of our
1581251881Speter         destinations to find nonexistent parents of all of them.  */
1582251881Speter      if (is_move)
1583251881Speter        {
1584251881Speter          /* Imagine a situation where the user tries to copy an
1585251881Speter             existing source directory to nonexistent directory with
1586251881Speter             --parents options specified:
1587251881Speter
1588251881Speter                svn copy --parents URL/src URL/dst
1589251881Speter
1590251881Speter             where src exists and dst does not.  If the dirname of the
1591251881Speter             destination path is equal to TOP_URL,
1592251881Speter             do not try to add dst to the NEW_DIRS list since it
1593251881Speter             will be added to the commit items array later in this
1594251881Speter             function. */
1595251881Speter          const char *dir = svn_uri_skip_ancestor(
1596251881Speter                              top_url,
1597251881Speter                              svn_uri_dirname(first_pair->dst_abspath_or_url,
1598251881Speter                                              pool),
1599251881Speter                              pool);
1600251881Speter          if (dir && *dir)
1601251881Speter            SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1602251881Speter        }
1603251881Speter      /* If, however, this is *not* a move, TOP_URL only points to the
1604251881Speter         common ancestor of our destination path(s), or possibly one
1605251881Speter         level higher.  We'll need to do an existence crawl toward the
1606251881Speter         root of the repository, starting with one of our destinations
1607251881Speter         (see "... take advantage of the knowledge of our caller ..."
1608251881Speter         above), and possibly adjusting TOP_URL as we go. */
1609251881Speter      else
1610251881Speter        {
1611251881Speter          apr_array_header_t *new_urls =
1612251881Speter            apr_array_make(pool, 0, sizeof(const char *));
1613251881Speter          SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1614251881Speter
1615251881Speter          /* Convert absolute URLs into relpaths relative to TOP_URL. */
1616251881Speter          for (i = 0; i < new_urls->nelts; i++)
1617251881Speter            {
1618251881Speter              const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1619251881Speter              const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1620251881Speter
1621251881Speter              APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1622251881Speter            }
1623251881Speter        }
1624251881Speter    }
1625251881Speter
1626251881Speter  /* For each src/dst pair, check to see if that SRC_URL is a child of
1627251881Speter     the DST_URL (excepting the case where DST_URL is the repo root).
1628251881Speter     If it is, and the parent of DST_URL is the current TOP_URL, then we
1629251881Speter     need to reparent the session one directory higher, the parent of
1630251881Speter     the DST_URL. */
1631251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1632251881Speter    {
1633251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1634251881Speter                                                    svn_client__copy_pair_t *);
1635251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1636251881Speter                                               path_driver_info_t *);
1637251881Speter      const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1638251881Speter                                                  pair->src_abspath_or_url,
1639251881Speter                                                  pool);
1640251881Speter
1641251881Speter      if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1642251881Speter          && (relpath != NULL && *relpath != '\0'))
1643251881Speter        {
1644251881Speter          info->resurrection = TRUE;
1645289166Speter          top_url = svn_uri_get_longest_ancestor(
1646289166Speter                            top_url,
1647289166Speter                            svn_uri_dirname(pair->dst_abspath_or_url, pool),
1648289166Speter                            pool);
1649251881Speter          SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1650251881Speter        }
1651251881Speter    }
1652251881Speter
1653251881Speter  /* Get the portions of the SRC and DST URLs that are relative to
1654251881Speter     TOP_URL (URI-decoding them while we're at it), verify that the
1655251881Speter     source exists and the proposed destination does not, and toss
1656251881Speter     what we've learned into the INFO array.  (For copies -- that is,
1657251881Speter     non-moves -- the relative source URL NULL because it isn't a
1658251881Speter     child of the TOP_URL at all.  That's okay, we'll deal with
1659251881Speter     it.)  */
1660251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1661251881Speter    {
1662251881Speter      svn_client__copy_pair_t *pair =
1663251881Speter        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1664251881Speter      path_driver_info_t *info =
1665251881Speter        APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1666251881Speter      svn_node_kind_t dst_kind;
1667251881Speter      const char *src_rel, *dst_rel;
1668251881Speter
1669251881Speter      src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1670251881Speter      if (src_rel)
1671251881Speter        {
1672251881Speter          SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1673251881Speter                                    &info->src_kind, pool));
1674251881Speter        }
1675251881Speter      else
1676251881Speter        {
1677251881Speter          const char *old_url;
1678251881Speter
1679251881Speter          src_rel = NULL;
1680251881Speter          SVN_ERR_ASSERT(! is_move);
1681251881Speter
1682251881Speter          SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1683251881Speter                                                    pair->src_abspath_or_url,
1684251881Speter                                                    pool));
1685251881Speter          SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1686251881Speter                                    &info->src_kind, pool));
1687251881Speter          SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1688251881Speter        }
1689251881Speter      if (info->src_kind == svn_node_none)
1690251881Speter        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1691251881Speter                                 _("Path '%s' does not exist in revision %ld"),
1692251881Speter                                 pair->src_abspath_or_url, pair->src_revnum);
1693251881Speter
1694251881Speter      /* Figure out the basename that will result from this operation,
1695251881Speter         and ensure that we aren't trying to overwrite existing paths.  */
1696251881Speter      dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1697251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1698251881Speter                                &dst_kind, pool));
1699251881Speter      if (dst_kind != svn_node_none)
1700299742Sdim        {
1701299742Sdim          const char *path = svn_uri_skip_ancestor(repos_root,
1702299742Sdim                                                   pair->dst_abspath_or_url,
1703299742Sdim                                                   pool);
1704299742Sdim          return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1705299742Sdim                                   _("Path '/%s' already exists"), path);
1706299742Sdim        }
1707251881Speter
1708251881Speter      /* More info for our INFO structure.  */
1709299742Sdim      info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1710251881Speter      info->dst_path = dst_rel;
1711251881Speter
1712251881Speter      svn_hash_sets(action_hash, info->dst_path, info);
1713251881Speter      if (is_move && (! info->resurrection))
1714251881Speter        svn_hash_sets(action_hash, info->src_path, info);
1715299742Sdim
1716299742Sdim      if (pin_externals)
1717299742Sdim        {
1718299742Sdim          apr_hash_t *pinned_externals;
1719299742Sdim
1720299742Sdim          SVN_ERR(resolve_pinned_externals(&pinned_externals,
1721299742Sdim                                           externals_to_pin, pair,
1722299742Sdim                                           ra_session, repos_root,
1723299742Sdim                                           ctx, pool, pool));
1724299742Sdim          if (pin_externals_only_infos == NULL)
1725299742Sdim            {
1726299742Sdim              pin_externals_only_infos =
1727299742Sdim                apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1728299742Sdim            }
1729299742Sdim          SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1730299742Sdim                                                    path_infos,
1731299742Sdim                                                    pinned_externals,
1732299742Sdim                                                    info, pool, pool));
1733299742Sdim        }
1734251881Speter    }
1735251881Speter
1736251881Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1737251881Speter    {
1738251881Speter      /* Produce a list of new paths to add, and provide it to the
1739251881Speter         mechanism used to acquire a log message. */
1740251881Speter      svn_client_commit_item3_t *item;
1741251881Speter      const char *tmp_file;
1742251881Speter      apr_array_header_t *commit_items
1743251881Speter        = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1744251881Speter
1745251881Speter      /* Add any intermediate directories to the message */
1746251881Speter      if (make_parents)
1747251881Speter        {
1748251881Speter          for (i = 0; i < new_dirs->nelts; i++)
1749251881Speter            {
1750251881Speter              const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1751251881Speter
1752251881Speter              item = svn_client_commit_item3_create(pool);
1753251881Speter              item->url = svn_path_url_add_component2(top_url, relpath, pool);
1754299742Sdim              item->kind = svn_node_dir;
1755251881Speter              item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1756251881Speter              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1757251881Speter            }
1758251881Speter        }
1759251881Speter
1760251881Speter      for (i = 0; i < path_infos->nelts; i++)
1761251881Speter        {
1762251881Speter          path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1763251881Speter                                                   path_driver_info_t *);
1764251881Speter
1765251881Speter          item = svn_client_commit_item3_create(pool);
1766251881Speter          item->url = svn_path_url_add_component2(top_url, info->dst_path,
1767251881Speter                                                  pool);
1768299742Sdim          item->kind = info->src_kind;
1769299742Sdim          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1770299742Sdim                              | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1771299742Sdim          item->copyfrom_url = info->src_url;
1772299742Sdim          item->copyfrom_rev = info->src_revnum;
1773251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1774251881Speter
1775251881Speter          if (is_move && (! info->resurrection))
1776251881Speter            {
1777299742Sdim              item = svn_client_commit_item3_create(pool);
1778251881Speter              item->url = svn_path_url_add_component2(top_url, info->src_path,
1779251881Speter                                                      pool);
1780299742Sdim              item->kind = info->src_kind;
1781251881Speter              item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1782251881Speter              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1783251881Speter            }
1784251881Speter        }
1785251881Speter
1786251881Speter      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1787251881Speter                                      ctx, pool));
1788251881Speter      if (! message)
1789251881Speter        return SVN_NO_ERROR;
1790251881Speter    }
1791251881Speter  else
1792251881Speter    message = "";
1793251881Speter
1794251881Speter  /* Setup our PATHS for the path-based editor drive. */
1795251881Speter  /* First any intermediate directories. */
1796251881Speter  if (make_parents)
1797251881Speter    {
1798251881Speter      for (i = 0; i < new_dirs->nelts; i++)
1799251881Speter        {
1800251881Speter          const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1801251881Speter          path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1802251881Speter
1803251881Speter          info->dst_path = relpath;
1804251881Speter          info->dir_add = TRUE;
1805251881Speter
1806251881Speter          APR_ARRAY_PUSH(paths, const char *) = relpath;
1807251881Speter          svn_hash_sets(action_hash, relpath, info);
1808251881Speter        }
1809251881Speter    }
1810251881Speter
1811251881Speter  /* Then our copy destinations and move sources (if any). */
1812251881Speter  for (i = 0; i < path_infos->nelts; i++)
1813251881Speter    {
1814251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1815251881Speter                                               path_driver_info_t *);
1816251881Speter
1817251881Speter      APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1818251881Speter      if (is_move && (! info->resurrection))
1819251881Speter        APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1820251881Speter    }
1821251881Speter
1822299742Sdim  /* Add any items which only need their externals pinned. */
1823299742Sdim  if (pin_externals_only_infos)
1824299742Sdim    {
1825299742Sdim      for (i = 0; i < pin_externals_only_infos->nelts; i++)
1826299742Sdim        {
1827299742Sdim          path_driver_info_t *info;
1828299742Sdim
1829299742Sdim          info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1830299742Sdim          APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1831299742Sdim          svn_hash_sets(action_hash, info->dst_path, info);
1832299742Sdim        }
1833299742Sdim    }
1834299742Sdim
1835251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1836251881Speter                                           message, ctx, pool));
1837251881Speter
1838251881Speter  /* Fetch RA commit editor. */
1839251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1840251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx,
1841251881Speter                                                       NULL, pool)));
1842251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1843251881Speter                                    commit_revprops,
1844251881Speter                                    commit_callback,
1845251881Speter                                    commit_baton,
1846251881Speter                                    NULL, TRUE, /* No lock tokens */
1847251881Speter                                    pool));
1848251881Speter
1849251881Speter  /* Setup the callback baton. */
1850251881Speter  cb_baton.editor = editor;
1851251881Speter  cb_baton.edit_baton = edit_baton;
1852251881Speter  cb_baton.action_hash = action_hash;
1853251881Speter  cb_baton.is_move = is_move;
1854251881Speter
1855251881Speter  /* Call the path-based editor driver. */
1856251881Speter  err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1857251881Speter                               path_driver_cb_func, &cb_baton, pool);
1858251881Speter  if (err)
1859251881Speter    {
1860251881Speter      /* At least try to abort the edit (and fs txn) before throwing err. */
1861251881Speter      return svn_error_compose_create(
1862251881Speter                    err,
1863251881Speter                    editor->abort_edit(edit_baton, pool));
1864251881Speter    }
1865251881Speter
1866299742Sdim  if (ctx->notify_func2)
1867299742Sdim    {
1868299742Sdim      svn_wc_notify_t *notify;
1869299742Sdim      notify = svn_wc_create_notify_url(top_url,
1870299742Sdim                                        svn_wc_notify_commit_finalizing,
1871299742Sdim                                        pool);
1872299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, pool);
1873299742Sdim    }
1874299742Sdim
1875251881Speter  /* Close the edit. */
1876251881Speter  return svn_error_trace(editor->close_edit(edit_baton, pool));
1877251881Speter}
1878251881Speter
1879251881Speter/* Baton for check_url_kind */
1880251881Speterstruct check_url_kind_baton
1881251881Speter{
1882251881Speter  svn_ra_session_t *session;
1883251881Speter  const char *repos_root_url;
1884251881Speter  svn_boolean_t should_reparent;
1885251881Speter};
1886251881Speter
1887251881Speter/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1888251881Speterstatic svn_error_t *
1889251881Spetercheck_url_kind(void *baton,
1890251881Speter               svn_node_kind_t *kind,
1891251881Speter               const char *url,
1892251881Speter               svn_revnum_t revision,
1893251881Speter               apr_pool_t *scratch_pool)
1894251881Speter{
1895251881Speter  struct check_url_kind_baton *cukb = baton;
1896251881Speter
1897251881Speter  /* If we don't have a session or can't use the session, get one */
1898251881Speter  if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1899251881Speter    *kind = svn_node_none;
1900251881Speter  else
1901251881Speter    {
1902251881Speter      cukb->should_reparent = TRUE;
1903251881Speter
1904251881Speter      SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1905251881Speter
1906251881Speter      SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1907251881Speter                                kind, scratch_pool));
1908251881Speter    }
1909251881Speter
1910251881Speter  return SVN_NO_ERROR;
1911251881Speter}
1912251881Speter
1913299742Sdim/* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1914299742Sdim * in the COMMIT_ITEMS list.
1915299742Sdim * If the list does not already have a commit item for COMMIT_URL
1916299742Sdim * add a new commit item for the property change.
1917299742Sdim * Allocate results in RESULT_POOL.
1918299742Sdim * Use SCRATCH_POOL for temporary allocations. */
1919299742Sdimstatic svn_error_t *
1920299742Sdimqueue_prop_change_commit_items(const char *local_abspath,
1921299742Sdim                               const char *commit_url,
1922299742Sdim                               apr_array_header_t *commit_items,
1923299742Sdim                               const char *propname,
1924299742Sdim                               svn_string_t *propval,
1925299742Sdim                               apr_pool_t *result_pool,
1926299742Sdim                               apr_pool_t *scratch_pool)
1927299742Sdim{
1928299742Sdim  svn_client_commit_item3_t *item = NULL;
1929299742Sdim  svn_prop_t *prop;
1930299742Sdim  int i;
1931299742Sdim
1932299742Sdim  for (i = 0; i < commit_items->nelts; i++)
1933299742Sdim    {
1934299742Sdim      svn_client_commit_item3_t *existing_item;
1935299742Sdim
1936299742Sdim      existing_item = APR_ARRAY_IDX(commit_items, i,
1937299742Sdim                                    svn_client_commit_item3_t *);
1938299742Sdim      if (strcmp(existing_item->url, commit_url) == 0)
1939299742Sdim        {
1940299742Sdim          item = existing_item;
1941299742Sdim          break;
1942299742Sdim        }
1943299742Sdim    }
1944299742Sdim
1945299742Sdim  if (item == NULL)
1946299742Sdim    {
1947299742Sdim      item = svn_client_commit_item3_create(result_pool);
1948299742Sdim      item->path = local_abspath;
1949299742Sdim      item->url = commit_url;
1950299742Sdim      item->kind = svn_node_dir;
1951299742Sdim      item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1952299742Sdim
1953299742Sdim      item->incoming_prop_changes = apr_array_make(result_pool, 1,
1954299742Sdim                                                   sizeof(svn_prop_t *));
1955299742Sdim      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1956299742Sdim    }
1957299742Sdim  else
1958299742Sdim    item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1959299742Sdim
1960299742Sdim  if (item->outgoing_prop_changes == NULL)
1961299742Sdim    item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1962299742Sdim                                                 sizeof(svn_prop_t *));
1963299742Sdim
1964299742Sdim  prop = apr_palloc(result_pool, sizeof(*prop));
1965299742Sdim  prop->name = propname;
1966299742Sdim  prop->value = propval;
1967299742Sdim  APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1968299742Sdim
1969299742Sdim  return SVN_NO_ERROR;
1970299742Sdim}
1971299742Sdim
1972251881Speter/* ### Copy ...
1973251881Speter * COMMIT_INFO_P is ...
1974251881Speter * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1975251881Speter * and each 'dst_abspath_or_url' is a URL.
1976251881Speter * MAKE_PARENTS is ...
1977251881Speter * REVPROP_TABLE is ...
1978251881Speter * CTX is ... */
1979251881Speterstatic svn_error_t *
1980251881Speterwc_to_repos_copy(const apr_array_header_t *copy_pairs,
1981251881Speter                 svn_boolean_t make_parents,
1982251881Speter                 const apr_hash_t *revprop_table,
1983251881Speter                 svn_commit_callback2_t commit_callback,
1984251881Speter                 void *commit_baton,
1985299742Sdim                 svn_boolean_t pin_externals,
1986299742Sdim                 const apr_hash_t *externals_to_pin,
1987251881Speter                 svn_client_ctx_t *ctx,
1988251881Speter                 apr_pool_t *scratch_pool)
1989251881Speter{
1990251881Speter  const char *message;
1991251881Speter  const char *top_src_path, *top_dst_url;
1992251881Speter  struct check_url_kind_baton cukb;
1993251881Speter  const char *top_src_abspath;
1994251881Speter  svn_ra_session_t *ra_session;
1995251881Speter  const svn_delta_editor_t *editor;
1996299742Sdim#ifdef ENABLE_EV2_SHIMS
1997251881Speter  apr_hash_t *relpath_map = NULL;
1998299742Sdim#endif
1999251881Speter  void *edit_baton;
2000251881Speter  svn_client__committables_t *committables;
2001251881Speter  apr_array_header_t *commit_items;
2002251881Speter  apr_pool_t *iterpool;
2003251881Speter  apr_array_header_t *new_dirs = NULL;
2004251881Speter  apr_hash_t *commit_revprops;
2005251881Speter  svn_client__copy_pair_t *first_pair;
2006251881Speter  apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2007299742Sdim  apr_array_header_t *commit_items_for_dav;
2008251881Speter  int i;
2009251881Speter
2010251881Speter  /* Find the common root of all the source paths */
2011251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2012251881Speter                                  scratch_pool));
2013251881Speter
2014251881Speter  /* Do we need to lock the working copy?  1.6 didn't take a write
2015251881Speter     lock, but what happens if the working copy changes during the copy
2016251881Speter     operation? */
2017251881Speter
2018251881Speter  iterpool = svn_pool_create(scratch_pool);
2019251881Speter
2020251881Speter  /* Determine the longest common ancestor for the destinations, and open an RA
2021251881Speter     session to that location. */
2022251881Speter  /* ### But why start by getting the _parent_ of the first one? */
2023251881Speter  /* --- That works because multiple destinations always point to the same
2024251881Speter   *     directory. I'm rather wondering why we need to find a common
2025251881Speter   *     destination parent here at all, instead of simply getting
2026251881Speter   *     top_dst_url from get_copy_pair_ancestors() above?
2027251881Speter   *     It looks like the entire block of code hanging off this comment
2028251881Speter   *     is redundant. */
2029251881Speter  first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2030251881Speter  top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2031251881Speter  for (i = 1; i < copy_pairs->nelts; i++)
2032251881Speter    {
2033251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2034251881Speter                                                    svn_client__copy_pair_t *);
2035251881Speter      top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2036251881Speter                                                 pair->dst_abspath_or_url,
2037251881Speter                                                 scratch_pool);
2038251881Speter    }
2039251881Speter
2040251881Speter  SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2041251881Speter
2042299742Sdim  commit_items_for_dav = apr_array_make(session_pool, 0,
2043299742Sdim                                        sizeof(svn_client_commit_item3_t*));
2044299742Sdim
2045251881Speter  /* Open a session to help while determining the exact targets */
2046251881Speter  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2047299742Sdim                                               top_src_abspath,
2048299742Sdim                                               commit_items_for_dav,
2049251881Speter                                               FALSE /* write_dav_props */,
2050251881Speter                                               TRUE /* read_dav_props */,
2051251881Speter                                               ctx,
2052251881Speter                                               session_pool, session_pool));
2053251881Speter
2054251881Speter  /* If requested, determine the nearest existing parent of the destination,
2055251881Speter     and reparent the ra session there. */
2056251881Speter  if (make_parents)
2057251881Speter    {
2058251881Speter      new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2059251881Speter      SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2060251881Speter                                   scratch_pool));
2061251881Speter    }
2062251881Speter
2063251881Speter  /* Figure out the basename that will result from each copy and check to make
2064251881Speter     sure it doesn't exist already. */
2065251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
2066251881Speter    {
2067251881Speter      svn_node_kind_t dst_kind;
2068251881Speter      const char *dst_rel;
2069251881Speter      svn_client__copy_pair_t *pair =
2070251881Speter        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2071251881Speter
2072251881Speter      svn_pool_clear(iterpool);
2073251881Speter      dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2074251881Speter                                      iterpool);
2075251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2076251881Speter                                &dst_kind, iterpool));
2077251881Speter      if (dst_kind != svn_node_none)
2078251881Speter        {
2079251881Speter          return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2080251881Speter                                   _("Path '%s' already exists"),
2081251881Speter                                   pair->dst_abspath_or_url);
2082251881Speter        }
2083251881Speter    }
2084251881Speter
2085251881Speter  cukb.session = ra_session;
2086251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2087251881Speter  cukb.should_reparent = FALSE;
2088251881Speter
2089251881Speter  /* Crawl the working copy for commit items. */
2090251881Speter  /* ### TODO: Pass check_url_func for issue #3314 handling */
2091251881Speter  SVN_ERR(svn_client__get_copy_committables(&committables,
2092251881Speter                                            copy_pairs,
2093251881Speter                                            check_url_kind, &cukb,
2094251881Speter                                            ctx, scratch_pool, iterpool));
2095251881Speter
2096251881Speter  /* The committables are keyed by the repository root */
2097251881Speter  commit_items = svn_hash_gets(committables->by_repository,
2098251881Speter                               cukb.repos_root_url);
2099251881Speter  SVN_ERR_ASSERT(commit_items != NULL);
2100251881Speter
2101251881Speter  if (cukb.should_reparent)
2102251881Speter    SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2103251881Speter
2104251881Speter  /* If we are creating intermediate directories, tack them onto the list
2105251881Speter     of committables. */
2106251881Speter  if (make_parents)
2107251881Speter    {
2108251881Speter      for (i = 0; i < new_dirs->nelts; i++)
2109251881Speter        {
2110251881Speter          const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2111251881Speter          svn_client_commit_item3_t *item;
2112251881Speter
2113251881Speter          item = svn_client_commit_item3_create(scratch_pool);
2114251881Speter          item->url = url;
2115299742Sdim          item->kind = svn_node_dir;
2116251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2117251881Speter          item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2118251881Speter                                                       sizeof(svn_prop_t *));
2119251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2120251881Speter        }
2121251881Speter    }
2122251881Speter
2123251881Speter  /* ### TODO: This extra loop would be unnecessary if this code lived
2124251881Speter     ### in svn_client__get_copy_committables(), which is incidentally
2125251881Speter     ### only used above (so should really be in this source file). */
2126251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
2127251881Speter    {
2128251881Speter      apr_hash_t *mergeinfo, *wc_mergeinfo;
2129251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2130251881Speter                                                    svn_client__copy_pair_t *);
2131251881Speter      svn_client_commit_item3_t *item =
2132251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2133251881Speter      svn_client__pathrev_t *src_origin;
2134251881Speter
2135251881Speter      svn_pool_clear(iterpool);
2136251881Speter
2137251881Speter      SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2138251881Speter                                             pair->src_abspath_or_url,
2139251881Speter                                             ctx, iterpool, iterpool));
2140251881Speter
2141251881Speter      /* Set the mergeinfo for the destination to the combined merge
2142251881Speter         info known to the WC and the repository. */
2143251881Speter      /* Repository mergeinfo (or NULL if it's locally added)... */
2144251881Speter      if (src_origin)
2145251881Speter        SVN_ERR(svn_client__get_repos_mergeinfo(
2146251881Speter                  &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2147251881Speter                  svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2148251881Speter      else
2149251881Speter        mergeinfo = NULL;
2150251881Speter      /* ... and WC mergeinfo. */
2151251881Speter      SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2152251881Speter                                          pair->src_abspath_or_url,
2153251881Speter                                          iterpool, iterpool));
2154251881Speter      if (wc_mergeinfo && mergeinfo)
2155251881Speter        SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2156251881Speter                                     iterpool));
2157251881Speter      else if (! mergeinfo)
2158251881Speter        mergeinfo = wc_mergeinfo;
2159299742Sdim
2160251881Speter      if (mergeinfo)
2161251881Speter        {
2162251881Speter          /* Push a mergeinfo prop representing MERGEINFO onto the
2163251881Speter           * OUTGOING_PROP_CHANGES array. */
2164251881Speter
2165251881Speter          svn_prop_t *mergeinfo_prop
2166299742Sdim                            = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2167251881Speter          svn_string_t *prop_value;
2168251881Speter
2169251881Speter          SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2170299742Sdim                                          scratch_pool));
2171251881Speter
2172299742Sdim          if (!item->outgoing_prop_changes)
2173299742Sdim            {
2174299742Sdim              item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2175299742Sdim                                                           sizeof(svn_prop_t *));
2176299742Sdim            }
2177299742Sdim
2178251881Speter          mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2179251881Speter          mergeinfo_prop->value = prop_value;
2180251881Speter          APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2181251881Speter            = mergeinfo_prop;
2182251881Speter        }
2183299742Sdim
2184299742Sdim      if (pin_externals)
2185299742Sdim        {
2186299742Sdim          apr_hash_t *pinned_externals;
2187299742Sdim          apr_hash_index_t *hi;
2188299742Sdim
2189299742Sdim          SVN_ERR(resolve_pinned_externals(&pinned_externals,
2190299742Sdim                                           externals_to_pin, pair,
2191299742Sdim                                           ra_session, cukb.repos_root_url,
2192299742Sdim                                           ctx, scratch_pool, iterpool));
2193299742Sdim          for (hi = apr_hash_first(scratch_pool, pinned_externals);
2194299742Sdim               hi;
2195299742Sdim               hi = apr_hash_next(hi))
2196299742Sdim            {
2197299742Sdim              const char *dst_relpath = apr_hash_this_key(hi);
2198299742Sdim              svn_string_t *externals_propval = apr_hash_this_val(hi);
2199299742Sdim              const char *dst_url;
2200299742Sdim              const char *commit_url;
2201299742Sdim              const char *src_abspath;
2202299742Sdim
2203299742Sdim              if (svn_path_is_url(pair->dst_abspath_or_url))
2204299742Sdim                dst_url = pair->dst_abspath_or_url;
2205299742Sdim              else
2206299742Sdim                SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2207299742Sdim                                             pair->dst_abspath_or_url,
2208299742Sdim                                             scratch_pool, iterpool));
2209299742Sdim              commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2210299742Sdim                                                       scratch_pool);
2211299742Sdim              src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2212299742Sdim                                            dst_relpath, iterpool);
2213299742Sdim              SVN_ERR(queue_prop_change_commit_items(src_abspath,
2214299742Sdim                                                     commit_url, commit_items,
2215299742Sdim                                                     SVN_PROP_EXTERNALS,
2216299742Sdim                                                     externals_propval,
2217299742Sdim                                                     scratch_pool, iterpool));
2218299742Sdim            }
2219299742Sdim        }
2220251881Speter    }
2221251881Speter
2222299742Sdim  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2223299742Sdim    {
2224299742Sdim      const char *tmp_file;
2225299742Sdim
2226299742Sdim      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2227299742Sdim                                      ctx, scratch_pool));
2228299742Sdim      if (! message)
2229299742Sdim        {
2230299742Sdim          svn_pool_destroy(iterpool);
2231299742Sdim          svn_pool_destroy(session_pool);
2232299742Sdim          return SVN_NO_ERROR;
2233299742Sdim        }
2234299742Sdim    }
2235299742Sdim  else
2236299742Sdim    message = "";
2237299742Sdim
2238251881Speter  /* Sort and condense our COMMIT_ITEMS. */
2239251881Speter  SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2240251881Speter                                            commit_items, scratch_pool));
2241251881Speter
2242299742Sdim  /* Add the commit items to the DAV commit item list to provide access
2243299742Sdim     to dav properties (for pre http-v2 DAV) */
2244299742Sdim  apr_array_cat(commit_items_for_dav, commit_items);
2245299742Sdim
2246251881Speter#ifdef ENABLE_EV2_SHIMS
2247251881Speter  if (commit_items)
2248251881Speter    {
2249299742Sdim      relpath_map = apr_hash_make(scratch_pool);
2250251881Speter      for (i = 0; i < commit_items->nelts; i++)
2251251881Speter        {
2252251881Speter          svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2253251881Speter                                                  svn_client_commit_item3_t *);
2254251881Speter          const char *relpath;
2255251881Speter
2256251881Speter          if (!item->path)
2257251881Speter            continue;
2258251881Speter
2259251881Speter          svn_pool_clear(iterpool);
2260299742Sdim          SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2261299742Sdim                                          NULL, NULL,
2262251881Speter                                          ctx->wc_ctx, item->path, FALSE,
2263251881Speter                                          scratch_pool, iterpool));
2264251881Speter          if (relpath)
2265251881Speter            svn_hash_sets(relpath_map, relpath, item->path);
2266251881Speter        }
2267251881Speter    }
2268251881Speter#endif
2269251881Speter
2270299742Sdim  SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2271251881Speter
2272251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2273251881Speter                                           message, ctx, session_pool));
2274251881Speter
2275251881Speter  /* Fetch RA commit editor. */
2276299742Sdim#ifdef ENABLE_EV2_SHIMS
2277251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2278251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2279251881Speter                                                       session_pool)));
2280299742Sdim#endif
2281251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2282251881Speter                                    commit_revprops,
2283251881Speter                                    commit_callback,
2284251881Speter                                    commit_baton, NULL,
2285251881Speter                                    TRUE, /* No lock tokens */
2286251881Speter                                    session_pool));
2287251881Speter
2288251881Speter  /* Perform the commit. */
2289251881Speter  SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2290251881Speter                                  editor, edit_baton,
2291299742Sdim                                  NULL /* notify_path_prefix */,
2292251881Speter                                  NULL, ctx, session_pool, session_pool),
2293251881Speter            _("Commit failed (details follow):"));
2294251881Speter
2295251881Speter  svn_pool_destroy(iterpool);
2296251881Speter  svn_pool_destroy(session_pool);
2297251881Speter
2298251881Speter  return SVN_NO_ERROR;
2299251881Speter}
2300251881Speter
2301251881Speter/* A baton for notification_adjust_func(). */
2302251881Speterstruct notification_adjust_baton
2303251881Speter{
2304251881Speter  svn_wc_notify_func2_t inner_func;
2305251881Speter  void *inner_baton;
2306251881Speter  const char *checkout_abspath;
2307251881Speter  const char *final_abspath;
2308251881Speter};
2309251881Speter
2310251881Speter/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2311251881Speter * baton is BATON->inner_baton) and adjusts the notification paths that
2312251881Speter * start with BATON->checkout_abspath to start instead with
2313251881Speter * BATON->final_abspath. */
2314251881Speterstatic void
2315251881Speternotification_adjust_func(void *baton,
2316251881Speter                         const svn_wc_notify_t *notify,
2317251881Speter                         apr_pool_t *pool)
2318251881Speter{
2319251881Speter  struct notification_adjust_baton *nb = baton;
2320251881Speter  svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2321251881Speter  const char *relpath;
2322251881Speter
2323251881Speter  relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2324251881Speter  inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2325251881Speter
2326251881Speter  if (nb->inner_func)
2327251881Speter    nb->inner_func(nb->inner_baton, inner_notify, pool);
2328251881Speter}
2329251881Speter
2330251881Speter/* Peform each individual copy operation for a repos -> wc copy.  A
2331251881Speter   helper for repos_to_wc_copy().
2332251881Speter
2333251881Speter   Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2334251881Speterstatic svn_error_t *
2335251881Speterrepos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2336251881Speter                        svn_client__copy_pair_t *pair,
2337251881Speter                        svn_boolean_t same_repositories,
2338251881Speter                        svn_boolean_t ignore_externals,
2339299742Sdim                        svn_boolean_t pin_externals,
2340299742Sdim                        const apr_hash_t *externals_to_pin,
2341251881Speter                        svn_ra_session_t *ra_session,
2342251881Speter                        svn_client_ctx_t *ctx,
2343251881Speter                        apr_pool_t *pool)
2344251881Speter{
2345251881Speter  apr_hash_t *src_mergeinfo;
2346251881Speter  const char *dst_abspath = pair->dst_abspath_or_url;
2347251881Speter
2348251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2349251881Speter
2350251881Speter  if (!same_repositories && ctx->notify_func2)
2351251881Speter    {
2352251881Speter      svn_wc_notify_t *notify;
2353251881Speter      notify = svn_wc_create_notify_url(
2354251881Speter                            pair->src_abspath_or_url,
2355251881Speter                            svn_wc_notify_foreign_copy_begin,
2356251881Speter                            pool);
2357251881Speter      notify->kind = pair->src_kind;
2358251881Speter      ctx->notify_func2(ctx->notify_baton2, notify, pool);
2359251881Speter
2360251881Speter      /* Allow a theoretical cancel to get through. */
2361251881Speter      if (ctx->cancel_func)
2362251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2363251881Speter    }
2364251881Speter
2365251881Speter  if (pair->src_kind == svn_node_dir)
2366251881Speter    {
2367251881Speter      if (same_repositories)
2368251881Speter        {
2369251881Speter          const char *tmpdir_abspath, *tmp_abspath;
2370251881Speter
2371251881Speter          /* Find a temporary location in which to check out the copy source. */
2372251881Speter          SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2373251881Speter                                     pool, pool));
2374251881Speter
2375251881Speter          SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2376251881Speter                                           svn_io_file_del_on_close, pool, pool));
2377251881Speter
2378251881Speter          /* Make a new checkout of the requested source. While doing so,
2379251881Speter           * resolve pair->src_revnum to an actual revision number in case it
2380251881Speter           * was until now 'invalid' meaning 'head'.  Ask this function not to
2381251881Speter           * sleep for timestamps, by passing a sleep_needed output param.
2382251881Speter           * Send notifications for all nodes except the root node, and adjust
2383251881Speter           * them to refer to the destination rather than this temporary path. */
2384251881Speter          {
2385251881Speter            svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2386251881Speter            void *old_notify_baton2 = ctx->notify_baton2;
2387251881Speter            struct notification_adjust_baton nb;
2388251881Speter            svn_error_t *err;
2389251881Speter
2390251881Speter            nb.inner_func = ctx->notify_func2;
2391251881Speter            nb.inner_baton = ctx->notify_baton2;
2392251881Speter            nb.checkout_abspath = tmp_abspath;
2393251881Speter            nb.final_abspath = dst_abspath;
2394251881Speter            ctx->notify_func2 = notification_adjust_func;
2395251881Speter            ctx->notify_baton2 = &nb;
2396251881Speter
2397299742Sdim            /* Avoid a chicken-and-egg problem:
2398299742Sdim             * If pinning externals we'll need to adjust externals
2399299742Sdim             * properties before checking out any externals.
2400299742Sdim             * But copy needs to happen before pinning because else there
2401299742Sdim             * are no svn:externals properties to pin. */
2402299742Sdim            if (pin_externals)
2403299742Sdim              ignore_externals = TRUE;
2404299742Sdim
2405299742Sdim            err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2406251881Speter                                                pair->src_original,
2407251881Speter                                                tmp_abspath,
2408251881Speter                                                &pair->src_peg_revision,
2409251881Speter                                                &pair->src_op_revision,
2410251881Speter                                                svn_depth_infinity,
2411251881Speter                                                ignore_externals, FALSE,
2412299742Sdim                                                ra_session, ctx, pool);
2413251881Speter
2414251881Speter            ctx->notify_func2 = old_notify_func2;
2415251881Speter            ctx->notify_baton2 = old_notify_baton2;
2416251881Speter
2417251881Speter            SVN_ERR(err);
2418251881Speter          }
2419251881Speter
2420251881Speter          *timestamp_sleep = TRUE;
2421251881Speter
2422251881Speter      /* Schedule dst_path for addition in parent, with copy history.
2423251881Speter         Don't send any notification here.
2424251881Speter         Then remove the temporary checkout's .svn dir in preparation for
2425251881Speter         moving the rest of it into the final destination. */
2426251881Speter          SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2427251881Speter                               TRUE /* metadata_only */,
2428251881Speter                               ctx->cancel_func, ctx->cancel_baton,
2429251881Speter                               NULL, NULL, pool));
2430251881Speter          SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2431251881Speter                                             FALSE, pool, pool));
2432251881Speter          SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2433251881Speter                                                       tmp_abspath,
2434251881Speter                                                       FALSE, FALSE,
2435251881Speter                                                       ctx->cancel_func,
2436251881Speter                                                       ctx->cancel_baton,
2437251881Speter                                                       pool));
2438251881Speter
2439251881Speter          /* Move the temporary disk tree into place. */
2440251881Speter          SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
2441251881Speter        }
2442251881Speter      else
2443251881Speter        {
2444251881Speter          *timestamp_sleep = TRUE;
2445251881Speter
2446251881Speter          SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2447251881Speter                                           dst_abspath,
2448251881Speter                                           &pair->src_peg_revision,
2449251881Speter                                           &pair->src_op_revision,
2450251881Speter                                           svn_depth_infinity,
2451251881Speter                                           FALSE /* make_parents */,
2452251881Speter                                           TRUE /* already_locked */,
2453251881Speter                                           ctx, pool));
2454251881Speter
2455251881Speter          return SVN_NO_ERROR;
2456251881Speter        }
2457299742Sdim
2458299742Sdim      if (pin_externals)
2459299742Sdim        {
2460299742Sdim          apr_hash_t *pinned_externals;
2461299742Sdim          apr_hash_index_t *hi;
2462299742Sdim          apr_pool_t *iterpool;
2463299742Sdim          const char *repos_root_url;
2464299742Sdim          apr_hash_t *new_externals;
2465299742Sdim          apr_hash_t *new_depths;
2466299742Sdim
2467299742Sdim          SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2468299742Sdim          SVN_ERR(resolve_pinned_externals(&pinned_externals,
2469299742Sdim                                           externals_to_pin, pair,
2470299742Sdim                                           ra_session, repos_root_url,
2471299742Sdim                                           ctx, pool, pool));
2472299742Sdim
2473299742Sdim          iterpool = svn_pool_create(pool);
2474299742Sdim          for (hi = apr_hash_first(pool, pinned_externals);
2475299742Sdim               hi;
2476299742Sdim               hi = apr_hash_next(hi))
2477299742Sdim            {
2478299742Sdim              const char *dst_relpath = apr_hash_this_key(hi);
2479299742Sdim              svn_string_t *externals_propval = apr_hash_this_val(hi);
2480299742Sdim              const char *local_abspath;
2481299742Sdim
2482299742Sdim              svn_pool_clear(iterpool);
2483299742Sdim
2484299742Sdim              local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2485299742Sdim                                              dst_relpath, iterpool);
2486299742Sdim              /* ### use a work queue? */
2487299742Sdim              SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2488299742Sdim                                       SVN_PROP_EXTERNALS, externals_propval,
2489299742Sdim                                       svn_depth_empty, TRUE /* skip_checks */,
2490299742Sdim                                       NULL  /* changelist_filter */,
2491299742Sdim                                       ctx->cancel_func, ctx->cancel_baton,
2492299742Sdim                                       NULL, NULL, /* no extra notification */
2493299742Sdim                                       iterpool));
2494299742Sdim            }
2495299742Sdim
2496299742Sdim          /* Now update all externals in the newly created copy. */
2497299742Sdim          SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2498299742Sdim                                                       &new_depths,
2499299742Sdim                                                       ctx->wc_ctx,
2500299742Sdim                                                       dst_abspath,
2501299742Sdim                                                       svn_depth_infinity,
2502299742Sdim                                                       iterpool, iterpool));
2503299742Sdim          SVN_ERR(svn_client__handle_externals(new_externals,
2504299742Sdim                                               new_depths,
2505299742Sdim                                               repos_root_url, dst_abspath,
2506299742Sdim                                               svn_depth_infinity,
2507299742Sdim                                               timestamp_sleep,
2508299742Sdim                                               ra_session,
2509299742Sdim                                               ctx, iterpool));
2510299742Sdim          svn_pool_destroy(iterpool);
2511299742Sdim        }
2512251881Speter    } /* end directory case */
2513251881Speter
2514251881Speter  else if (pair->src_kind == svn_node_file)
2515251881Speter    {
2516251881Speter      apr_hash_t *new_props;
2517251881Speter      const char *src_rel;
2518251881Speter      svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2519251881Speter
2520251881Speter      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2521251881Speter                                                  pair->src_abspath_or_url,
2522251881Speter                                                  pool));
2523251881Speter      /* Fetch the file content. While doing so, resolve pair->src_revnum
2524251881Speter       * to an actual revision number if it's 'invalid' meaning 'head'. */
2525251881Speter      SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2526251881Speter                              new_base_contents,
2527251881Speter                              &pair->src_revnum, &new_props, pool));
2528251881Speter
2529251881Speter      if (new_props && ! same_repositories)
2530251881Speter        svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2531251881Speter
2532251881Speter      *timestamp_sleep = TRUE;
2533251881Speter
2534251881Speter      SVN_ERR(svn_wc_add_repos_file4(
2535251881Speter         ctx->wc_ctx, dst_abspath,
2536251881Speter         new_base_contents, NULL, new_props, NULL,
2537251881Speter         same_repositories ? pair->src_abspath_or_url : NULL,
2538251881Speter         same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2539251881Speter         ctx->cancel_func, ctx->cancel_baton,
2540251881Speter         pool));
2541251881Speter    }
2542251881Speter
2543251881Speter  /* Record the implied mergeinfo (before the notification callback
2544251881Speter     is invoked for the root node). */
2545251881Speter  SVN_ERR(svn_client__get_repos_mergeinfo(
2546251881Speter            &src_mergeinfo, ra_session,
2547251881Speter            pair->src_abspath_or_url, pair->src_revnum,
2548251881Speter            svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2549251881Speter  SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2550251881Speter
2551251881Speter  /* Do our own notification for the root node, even if we could possibly
2552251881Speter     have delegated it.  See also issue #1552.
2553251881Speter
2554251881Speter     ### Maybe this notification should mention the mergeinfo change. */
2555251881Speter  if (ctx->notify_func2)
2556251881Speter    {
2557251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(
2558251881Speter                                  dst_abspath, svn_wc_notify_add, pool);
2559251881Speter      notify->kind = pair->src_kind;
2560299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, pool);
2561251881Speter    }
2562251881Speter
2563251881Speter  return SVN_NO_ERROR;
2564251881Speter}
2565251881Speter
2566251881Speterstatic svn_error_t *
2567251881Speterrepos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2568251881Speter                        const apr_array_header_t *copy_pairs,
2569251881Speter                        const char *top_dst_path,
2570251881Speter                        svn_boolean_t ignore_externals,
2571299742Sdim                        svn_boolean_t pin_externals,
2572299742Sdim                        const apr_hash_t *externals_to_pin,
2573251881Speter                        svn_ra_session_t *ra_session,
2574251881Speter                        svn_client_ctx_t *ctx,
2575251881Speter                        apr_pool_t *scratch_pool)
2576251881Speter{
2577251881Speter  int i;
2578251881Speter  svn_boolean_t same_repositories;
2579251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2580251881Speter
2581251881Speter  /* We've already checked for physical obstruction by a working file.
2582251881Speter     But there could also be logical obstruction by an entry whose
2583251881Speter     working file happens to be missing.*/
2584253734Speter  SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2585253734Speter                         ctx, scratch_pool, iterpool));
2586251881Speter
2587251881Speter  /* Decide whether the two repositories are the same or not. */
2588251881Speter  {
2589251881Speter    svn_error_t *src_err, *dst_err;
2590251881Speter    const char *parent;
2591251881Speter    const char *parent_abspath;
2592251881Speter    const char *src_uuid, *dst_uuid;
2593251881Speter
2594251881Speter    /* Get the repository uuid of SRC_URL */
2595251881Speter    src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
2596251881Speter    if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2597251881Speter      return svn_error_trace(src_err);
2598251881Speter
2599251881Speter    /* Get repository uuid of dst's parent directory, since dst may
2600251881Speter       not exist.  ### TODO:  we should probably walk up the wc here,
2601251881Speter       in case the parent dir has an imaginary URL.  */
2602251881Speter    if (copy_pairs->nelts == 1)
2603251881Speter      parent = svn_dirent_dirname(top_dst_path, scratch_pool);
2604251881Speter    else
2605251881Speter      parent = top_dst_path;
2606251881Speter
2607251881Speter    SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
2608251881Speter    dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2609251881Speter                                        parent_abspath, ctx,
2610251881Speter                                        iterpool, iterpool);
2611251881Speter    if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
2612251881Speter      return dst_err;
2613251881Speter
2614251881Speter    /* If either of the UUIDs are nonexistent, then at least one of
2615251881Speter       the repositories must be very old.  Rather than punish the
2616251881Speter       user, just assume the repositories are different, so no
2617251881Speter       copy-history is attempted. */
2618251881Speter    if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
2619251881Speter      same_repositories = FALSE;
2620251881Speter    else
2621251881Speter      same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2622251881Speter  }
2623251881Speter
2624251881Speter  /* Perform the move for each of the copy_pairs. */
2625251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
2626251881Speter    {
2627251881Speter      /* Check for cancellation */
2628251881Speter      if (ctx->cancel_func)
2629251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2630251881Speter
2631251881Speter      svn_pool_clear(iterpool);
2632251881Speter
2633251881Speter      SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2634251881Speter                                      APR_ARRAY_IDX(copy_pairs, i,
2635251881Speter                                                    svn_client__copy_pair_t *),
2636251881Speter                                      same_repositories,
2637251881Speter                                      ignore_externals,
2638299742Sdim                                      pin_externals, externals_to_pin,
2639251881Speter                                      ra_session, ctx, iterpool));
2640251881Speter    }
2641251881Speter  svn_pool_destroy(iterpool);
2642251881Speter
2643251881Speter  return SVN_NO_ERROR;
2644251881Speter}
2645251881Speter
2646251881Speterstatic svn_error_t *
2647251881Speterrepos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2648251881Speter                 const apr_array_header_t *copy_pairs,
2649251881Speter                 svn_boolean_t make_parents,
2650251881Speter                 svn_boolean_t ignore_externals,
2651299742Sdim                 svn_boolean_t pin_externals,
2652299742Sdim                 const apr_hash_t *externals_to_pin,
2653251881Speter                 svn_client_ctx_t *ctx,
2654251881Speter                 apr_pool_t *pool)
2655251881Speter{
2656251881Speter  svn_ra_session_t *ra_session;
2657251881Speter  const char *top_src_url, *top_dst_path;
2658251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
2659251881Speter  const char *lock_abspath;
2660251881Speter  int i;
2661251881Speter
2662251881Speter  /* Get the real path for the source, based upon its peg revision. */
2663251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
2664251881Speter    {
2665251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2666251881Speter                                                    svn_client__copy_pair_t *);
2667251881Speter      const char *src;
2668251881Speter
2669251881Speter      svn_pool_clear(iterpool);
2670251881Speter
2671251881Speter      SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2672251881Speter                                          NULL,
2673251881Speter                                          pair->src_abspath_or_url,
2674251881Speter                                          &pair->src_peg_revision,
2675251881Speter                                          &pair->src_op_revision, NULL,
2676251881Speter                                          ctx, iterpool));
2677251881Speter
2678251881Speter      pair->src_original = pair->src_abspath_or_url;
2679251881Speter      pair->src_abspath_or_url = apr_pstrdup(pool, src);
2680251881Speter    }
2681251881Speter
2682251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
2683251881Speter                                  NULL, pool));
2684251881Speter  lock_abspath = top_dst_path;
2685251881Speter  if (copy_pairs->nelts == 1)
2686251881Speter    {
2687251881Speter      top_src_url = svn_uri_dirname(top_src_url, pool);
2688251881Speter      lock_abspath = svn_dirent_dirname(top_dst_path, pool);
2689251881Speter    }
2690251881Speter
2691251881Speter  /* Open a repository session to the longest common src ancestor.  We do not
2692251881Speter     (yet) have a working copy, so we don't have a corresponding path and
2693251881Speter     tempfiles cannot go into the admin area. */
2694251881Speter  SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2695251881Speter                                      ctx, pool, pool));
2696251881Speter
2697251881Speter  /* Get the correct src path for the peg revision used, and verify that we
2698251881Speter     aren't overwriting an existing path. */
2699251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
2700251881Speter    {
2701251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2702251881Speter                                                    svn_client__copy_pair_t *);
2703251881Speter      svn_node_kind_t dst_parent_kind, dst_kind;
2704251881Speter      const char *dst_parent;
2705251881Speter      const char *src_rel;
2706251881Speter
2707251881Speter      svn_pool_clear(iterpool);
2708251881Speter
2709251881Speter      /* Next, make sure that the path exists in the repository. */
2710251881Speter      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2711251881Speter                                                  pair->src_abspath_or_url,
2712251881Speter                                                  iterpool));
2713251881Speter      SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2714251881Speter                                &pair->src_kind, pool));
2715251881Speter      if (pair->src_kind == svn_node_none)
2716251881Speter        {
2717251881Speter          if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2718251881Speter            return svn_error_createf
2719251881Speter              (SVN_ERR_FS_NOT_FOUND, NULL,
2720251881Speter               _("Path '%s' not found in revision %ld"),
2721251881Speter               pair->src_abspath_or_url, pair->src_revnum);
2722251881Speter          else
2723251881Speter            return svn_error_createf
2724251881Speter              (SVN_ERR_FS_NOT_FOUND, NULL,
2725251881Speter               _("Path '%s' not found in head revision"),
2726251881Speter               pair->src_abspath_or_url);
2727251881Speter        }
2728251881Speter
2729251881Speter      /* Figure out about dst. */
2730251881Speter      SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2731251881Speter                                iterpool));
2732251881Speter      if (dst_kind != svn_node_none)
2733251881Speter        {
2734251881Speter          return svn_error_createf(
2735251881Speter            SVN_ERR_ENTRY_EXISTS, NULL,
2736251881Speter            _("Path '%s' already exists"),
2737251881Speter            svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2738251881Speter        }
2739251881Speter
2740251881Speter      /* Make sure the destination parent is a directory and produce a clear
2741251881Speter         error message if it is not. */
2742251881Speter      dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2743251881Speter      SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2744251881Speter      if (make_parents && dst_parent_kind == svn_node_none)
2745251881Speter        {
2746251881Speter          SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2747251881Speter                                                 iterpool));
2748251881Speter        }
2749251881Speter      else if (dst_parent_kind != svn_node_dir)
2750251881Speter        {
2751251881Speter          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2752251881Speter                                   _("Path '%s' is not a directory"),
2753251881Speter                                   svn_dirent_local_style(dst_parent, pool));
2754251881Speter        }
2755251881Speter    }
2756251881Speter  svn_pool_destroy(iterpool);
2757251881Speter
2758251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
2759251881Speter    repos_to_wc_copy_locked(timestamp_sleep,
2760251881Speter                            copy_pairs, top_dst_path, ignore_externals,
2761299742Sdim                            pin_externals, externals_to_pin,
2762251881Speter                            ra_session, ctx, pool),
2763251881Speter    ctx->wc_ctx, lock_abspath, FALSE, pool);
2764251881Speter  return SVN_NO_ERROR;
2765251881Speter}
2766251881Speter
2767251881Speter#define NEED_REPOS_REVNUM(revision) \
2768251881Speter        ((revision.kind != svn_opt_revision_unspecified) \
2769251881Speter          && (revision.kind != svn_opt_revision_working))
2770251881Speter
2771251881Speter/* ...
2772251881Speter *
2773251881Speter * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2774251881Speter * change *TIMESTAMP_SLEEP.  This output will be valid even if the
2775251881Speter * function returns an error.
2776251881Speter *
2777251881Speter * Perform all allocations in POOL.
2778251881Speter */
2779251881Speterstatic svn_error_t *
2780251881Spetertry_copy(svn_boolean_t *timestamp_sleep,
2781251881Speter         const apr_array_header_t *sources,
2782251881Speter         const char *dst_path_in,
2783251881Speter         svn_boolean_t is_move,
2784251881Speter         svn_boolean_t allow_mixed_revisions,
2785251881Speter         svn_boolean_t metadata_only,
2786251881Speter         svn_boolean_t make_parents,
2787251881Speter         svn_boolean_t ignore_externals,
2788299742Sdim         svn_boolean_t pin_externals,
2789299742Sdim         const apr_hash_t *externals_to_pin,
2790251881Speter         const apr_hash_t *revprop_table,
2791251881Speter         svn_commit_callback2_t commit_callback,
2792251881Speter         void *commit_baton,
2793251881Speter         svn_client_ctx_t *ctx,
2794251881Speter         apr_pool_t *pool)
2795251881Speter{
2796251881Speter  apr_array_header_t *copy_pairs =
2797251881Speter                        apr_array_make(pool, sources->nelts,
2798251881Speter                                       sizeof(svn_client__copy_pair_t *));
2799251881Speter  svn_boolean_t srcs_are_urls, dst_is_url;
2800251881Speter  int i;
2801251881Speter
2802299742Sdim  /* Assert instead of crashing if the sources list is empty. */
2803299742Sdim  SVN_ERR_ASSERT(sources->nelts > 0);
2804299742Sdim
2805251881Speter  /* Are either of our paths URLs?  Just check the first src_path.  If
2806251881Speter     there are more than one, we'll check for homogeneity among them
2807251881Speter     down below. */
2808251881Speter  srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2809251881Speter                                  svn_client_copy_source_t *)->path);
2810251881Speter  dst_is_url = svn_path_is_url(dst_path_in);
2811251881Speter  if (!dst_is_url)
2812251881Speter    SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2813251881Speter
2814251881Speter  /* If we have multiple source paths, it implies the dst_path is a
2815251881Speter     directory we are moving or copying into.  Populate the COPY_PAIRS
2816251881Speter     array to contain a destination path for each of the source paths. */
2817251881Speter  if (sources->nelts > 1)
2818251881Speter    {
2819251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2820251881Speter
2821251881Speter      for (i = 0; i < sources->nelts; i++)
2822251881Speter        {
2823251881Speter          svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2824251881Speter                                               svn_client_copy_source_t *);
2825299742Sdim          svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2826251881Speter          const char *src_basename;
2827251881Speter          svn_boolean_t src_is_url = svn_path_is_url(source->path);
2828251881Speter
2829251881Speter          svn_pool_clear(iterpool);
2830251881Speter
2831251881Speter          if (src_is_url)
2832251881Speter            {
2833251881Speter              pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2834251881Speter              src_basename = svn_uri_basename(pair->src_abspath_or_url,
2835251881Speter                                              iterpool);
2836251881Speter            }
2837251881Speter          else
2838251881Speter            {
2839251881Speter              SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2840251881Speter                                              source->path, pool));
2841251881Speter              src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2842251881Speter                                                 iterpool);
2843251881Speter            }
2844251881Speter
2845251881Speter          pair->src_op_revision = *source->revision;
2846251881Speter          pair->src_peg_revision = *source->peg_revision;
2847299742Sdim          pair->src_kind = svn_node_unknown;
2848251881Speter
2849251881Speter          SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2850251881Speter                                            &pair->src_op_revision,
2851251881Speter                                            src_is_url,
2852251881Speter                                            TRUE,
2853251881Speter                                            iterpool));
2854251881Speter
2855251881Speter          /* Check to see if all the sources are urls or all working copy
2856251881Speter           * paths. */
2857251881Speter          if (src_is_url != srcs_are_urls)
2858251881Speter            return svn_error_create
2859251881Speter              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2860251881Speter               _("Cannot mix repository and working copy sources"));
2861251881Speter
2862251881Speter          if (dst_is_url)
2863251881Speter            pair->dst_abspath_or_url =
2864251881Speter              svn_path_url_add_component2(dst_path_in, src_basename, pool);
2865251881Speter          else
2866251881Speter            pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2867251881Speter                                                       src_basename, pool);
2868251881Speter          APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2869251881Speter        }
2870251881Speter
2871251881Speter      svn_pool_destroy(iterpool);
2872251881Speter    }
2873251881Speter  else
2874251881Speter    {
2875251881Speter      /* Only one source path. */
2876299742Sdim      svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2877251881Speter      svn_client_copy_source_t *source =
2878251881Speter        APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2879251881Speter      svn_boolean_t src_is_url = svn_path_is_url(source->path);
2880251881Speter
2881251881Speter      if (src_is_url)
2882251881Speter        pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2883251881Speter      else
2884251881Speter        SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2885251881Speter                                        source->path, pool));
2886251881Speter      pair->src_op_revision = *source->revision;
2887251881Speter      pair->src_peg_revision = *source->peg_revision;
2888299742Sdim      pair->src_kind = svn_node_unknown;
2889251881Speter
2890251881Speter      SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2891251881Speter                                        &pair->src_op_revision,
2892251881Speter                                        src_is_url, TRUE, pool));
2893251881Speter
2894251881Speter      pair->dst_abspath_or_url = dst_path_in;
2895251881Speter      APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2896251881Speter    }
2897251881Speter
2898251881Speter  if (!srcs_are_urls && !dst_is_url)
2899251881Speter    {
2900251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2901251881Speter
2902251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2903251881Speter        {
2904251881Speter          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2905251881Speter                                            svn_client__copy_pair_t *);
2906251881Speter
2907251881Speter          svn_pool_clear(iterpool);
2908251881Speter
2909251881Speter          if (svn_dirent_is_child(pair->src_abspath_or_url,
2910251881Speter                                  pair->dst_abspath_or_url, iterpool))
2911251881Speter            return svn_error_createf
2912251881Speter              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2913251881Speter               _("Cannot copy path '%s' into its own child '%s'"),
2914251881Speter               svn_dirent_local_style(pair->src_abspath_or_url, pool),
2915251881Speter               svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2916251881Speter        }
2917251881Speter
2918251881Speter      svn_pool_destroy(iterpool);
2919251881Speter    }
2920251881Speter
2921251881Speter  /* A file external should not be moved since the file external is
2922251881Speter     implemented as a switched file and it would delete the file the
2923251881Speter     file external is switched to, which is not the behavior the user
2924251881Speter     would probably want. */
2925251881Speter  if (is_move && !srcs_are_urls)
2926251881Speter    {
2927251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2928251881Speter
2929251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2930251881Speter        {
2931251881Speter          svn_client__copy_pair_t *pair =
2932251881Speter            APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2933251881Speter          svn_node_kind_t external_kind;
2934251881Speter          const char *defining_abspath;
2935251881Speter
2936251881Speter          svn_pool_clear(iterpool);
2937251881Speter
2938251881Speter          SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2939251881Speter          SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2940251881Speter                                             NULL, NULL, NULL, ctx->wc_ctx,
2941251881Speter                                             pair->src_abspath_or_url,
2942251881Speter                                             pair->src_abspath_or_url, TRUE,
2943251881Speter                                             iterpool, iterpool));
2944251881Speter
2945251881Speter          if (external_kind != svn_node_none)
2946251881Speter            return svn_error_createf(
2947251881Speter                     SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2948251881Speter                     NULL,
2949251881Speter                     _("Cannot move the external at '%s'; please "
2950251881Speter                       "edit the svn:externals property on '%s'."),
2951251881Speter                     svn_dirent_local_style(pair->src_abspath_or_url, pool),
2952251881Speter                     svn_dirent_local_style(defining_abspath, pool));
2953251881Speter        }
2954251881Speter      svn_pool_destroy(iterpool);
2955251881Speter    }
2956251881Speter
2957251881Speter  if (is_move)
2958251881Speter    {
2959251881Speter      /* Disallow moves between the working copy and the repository. */
2960251881Speter      if (srcs_are_urls != dst_is_url)
2961251881Speter        {
2962251881Speter          return svn_error_create
2963251881Speter            (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2964251881Speter             _("Moves between the working copy and the repository are not "
2965251881Speter               "supported"));
2966251881Speter        }
2967251881Speter
2968251881Speter      /* Disallow moving any path/URL onto or into itself. */
2969251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2970251881Speter        {
2971251881Speter          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2972251881Speter                                            svn_client__copy_pair_t *);
2973251881Speter
2974251881Speter          if (strcmp(pair->src_abspath_or_url,
2975251881Speter                     pair->dst_abspath_or_url) == 0)
2976251881Speter            return svn_error_createf(
2977251881Speter              SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2978251881Speter              srcs_are_urls ?
2979251881Speter                _("Cannot move URL '%s' into itself") :
2980251881Speter                _("Cannot move path '%s' into itself"),
2981251881Speter              srcs_are_urls ?
2982251881Speter                pair->src_abspath_or_url :
2983251881Speter                svn_dirent_local_style(pair->src_abspath_or_url, pool));
2984251881Speter        }
2985251881Speter    }
2986251881Speter  else
2987251881Speter    {
2988251881Speter      if (!srcs_are_urls)
2989251881Speter        {
2990251881Speter          /* If we are doing a wc->* copy, but with an operational revision
2991251881Speter             other than the working copy revision, we are really doing a
2992251881Speter             repo->* copy, because we're going to need to get the rev from the
2993251881Speter             repo. */
2994251881Speter
2995251881Speter          svn_boolean_t need_repos_op_rev = FALSE;
2996251881Speter          svn_boolean_t need_repos_peg_rev = FALSE;
2997251881Speter
2998251881Speter          /* Check to see if any revision is something other than
2999251881Speter             svn_opt_revision_unspecified or svn_opt_revision_working. */
3000251881Speter          for (i = 0; i < copy_pairs->nelts; i++)
3001251881Speter            {
3002251881Speter              svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3003251881Speter                                                svn_client__copy_pair_t *);
3004251881Speter
3005251881Speter              if (NEED_REPOS_REVNUM(pair->src_op_revision))
3006251881Speter                need_repos_op_rev = TRUE;
3007251881Speter
3008251881Speter              if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3009251881Speter                need_repos_peg_rev = TRUE;
3010251881Speter
3011251881Speter              if (need_repos_op_rev || need_repos_peg_rev)
3012251881Speter                break;
3013251881Speter            }
3014251881Speter
3015251881Speter          if (need_repos_op_rev || need_repos_peg_rev)
3016251881Speter            {
3017251881Speter              apr_pool_t *iterpool = svn_pool_create(pool);
3018251881Speter
3019251881Speter              for (i = 0; i < copy_pairs->nelts; i++)
3020251881Speter                {
3021251881Speter                  const char *copyfrom_repos_root_url;
3022251881Speter                  const char *copyfrom_repos_relpath;
3023251881Speter                  const char *url;
3024251881Speter                  svn_revnum_t copyfrom_rev;
3025251881Speter                  svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3026251881Speter                                                    svn_client__copy_pair_t *);
3027251881Speter
3028251881Speter                  svn_pool_clear(iterpool);
3029251881Speter
3030251881Speter                  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3031251881Speter
3032251881Speter                  SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
3033251881Speter                                                  &copyfrom_repos_relpath,
3034251881Speter                                                  &copyfrom_repos_root_url,
3035299742Sdim                                                  NULL, NULL, NULL,
3036251881Speter                                                  ctx->wc_ctx,
3037251881Speter                                                  pair->src_abspath_or_url,
3038251881Speter                                                  TRUE, iterpool, iterpool));
3039251881Speter
3040251881Speter                  if (copyfrom_repos_relpath)
3041251881Speter                    url = svn_path_url_add_component2(copyfrom_repos_root_url,
3042251881Speter                                                      copyfrom_repos_relpath,
3043251881Speter                                                      pool);
3044251881Speter                  else
3045251881Speter                    return svn_error_createf
3046251881Speter                      (SVN_ERR_ENTRY_MISSING_URL, NULL,
3047251881Speter                       _("'%s' does not have a URL associated with it"),
3048251881Speter                       svn_dirent_local_style(pair->src_abspath_or_url, pool));
3049251881Speter
3050251881Speter                  pair->src_abspath_or_url = url;
3051251881Speter
3052251881Speter                  if (!need_repos_peg_rev
3053251881Speter                      || pair->src_peg_revision.kind == svn_opt_revision_base)
3054251881Speter                    {
3055251881Speter                      /* Default the peg revision to that of the WC entry. */
3056251881Speter                      pair->src_peg_revision.kind = svn_opt_revision_number;
3057251881Speter                      pair->src_peg_revision.value.number = copyfrom_rev;
3058251881Speter                    }
3059251881Speter
3060251881Speter                  if (pair->src_op_revision.kind == svn_opt_revision_base)
3061251881Speter                    {
3062251881Speter                      /* Use the entry's revision as the operational rev. */
3063251881Speter                      pair->src_op_revision.kind = svn_opt_revision_number;
3064251881Speter                      pair->src_op_revision.value.number = copyfrom_rev;
3065251881Speter                    }
3066251881Speter                }
3067251881Speter
3068251881Speter              svn_pool_destroy(iterpool);
3069251881Speter              srcs_are_urls = TRUE;
3070251881Speter            }
3071251881Speter        }
3072251881Speter    }
3073251881Speter
3074251881Speter  /* Now, call the right handler for the operation. */
3075251881Speter  if ((! srcs_are_urls) && (! dst_is_url))
3076251881Speter    {
3077251881Speter      SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3078253734Speter                                      metadata_only, ctx, pool, pool));
3079251881Speter
3080251881Speter      /* Copy or move all targets. */
3081251881Speter      if (is_move)
3082251881Speter        return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3083251881Speter                                                 copy_pairs, dst_path_in,
3084251881Speter                                                 allow_mixed_revisions,
3085251881Speter                                                 metadata_only,
3086251881Speter                                                 ctx, pool));
3087251881Speter      else
3088251881Speter        {
3089251881Speter          /* We ignore these values, so assert the default value */
3090299742Sdim          SVN_ERR_ASSERT(allow_mixed_revisions);
3091251881Speter          return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3092299742Sdim                                                    copy_pairs,
3093299742Sdim                                                    metadata_only,
3094299742Sdim                                                    pin_externals,
3095299742Sdim                                                    externals_to_pin,
3096299742Sdim                                                    ctx, pool));
3097251881Speter        }
3098251881Speter    }
3099251881Speter  else if ((! srcs_are_urls) && (dst_is_url))
3100251881Speter    {
3101251881Speter      return svn_error_trace(
3102251881Speter        wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3103299742Sdim                         commit_callback, commit_baton,
3104299742Sdim                         pin_externals, externals_to_pin, ctx, pool));
3105251881Speter    }
3106251881Speter  else if ((srcs_are_urls) && (! dst_is_url))
3107251881Speter    {
3108251881Speter      return svn_error_trace(
3109251881Speter        repos_to_wc_copy(timestamp_sleep,
3110251881Speter                         copy_pairs, make_parents, ignore_externals,
3111299742Sdim                         pin_externals, externals_to_pin, ctx, pool));
3112251881Speter    }
3113251881Speter  else
3114251881Speter    {
3115251881Speter      return svn_error_trace(
3116251881Speter        repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3117251881Speter                            commit_callback, commit_baton, ctx, is_move,
3118299742Sdim                            pin_externals, externals_to_pin, pool));
3119251881Speter    }
3120251881Speter}
3121251881Speter
3122251881Speter
3123251881Speter
3124251881Speter/* Public Interfaces */
3125251881Spetersvn_error_t *
3126299742Sdimsvn_client_copy7(const apr_array_header_t *sources,
3127251881Speter                 const char *dst_path,
3128251881Speter                 svn_boolean_t copy_as_child,
3129251881Speter                 svn_boolean_t make_parents,
3130251881Speter                 svn_boolean_t ignore_externals,
3131299742Sdim                 svn_boolean_t metadata_only,
3132299742Sdim                 svn_boolean_t pin_externals,
3133299742Sdim                 const apr_hash_t *externals_to_pin,
3134251881Speter                 const apr_hash_t *revprop_table,
3135251881Speter                 svn_commit_callback2_t commit_callback,
3136251881Speter                 void *commit_baton,
3137251881Speter                 svn_client_ctx_t *ctx,
3138251881Speter                 apr_pool_t *pool)
3139251881Speter{
3140251881Speter  svn_error_t *err;
3141251881Speter  svn_boolean_t timestamp_sleep = FALSE;
3142251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
3143251881Speter
3144251881Speter  if (sources->nelts > 1 && !copy_as_child)
3145251881Speter    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3146251881Speter                            NULL, NULL);
3147251881Speter
3148251881Speter  err = try_copy(&timestamp_sleep,
3149251881Speter                 sources, dst_path,
3150251881Speter                 FALSE /* is_move */,
3151251881Speter                 TRUE /* allow_mixed_revisions */,
3152299742Sdim                 metadata_only,
3153251881Speter                 make_parents,
3154251881Speter                 ignore_externals,
3155299742Sdim                 pin_externals,
3156299742Sdim                 externals_to_pin,
3157251881Speter                 revprop_table,
3158251881Speter                 commit_callback, commit_baton,
3159251881Speter                 ctx,
3160251881Speter                 subpool);
3161251881Speter
3162251881Speter  /* If the destination exists, try to copy the sources as children of the
3163251881Speter     destination. */
3164251881Speter  if (copy_as_child && err && (sources->nelts == 1)
3165251881Speter        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3166251881Speter            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3167251881Speter    {
3168251881Speter      const char *src_path = APR_ARRAY_IDX(sources, 0,
3169251881Speter                                           svn_client_copy_source_t *)->path;
3170251881Speter      const char *src_basename;
3171251881Speter      svn_boolean_t src_is_url = svn_path_is_url(src_path);
3172251881Speter      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3173251881Speter
3174251881Speter      svn_error_clear(err);
3175251881Speter      svn_pool_clear(subpool);
3176251881Speter
3177251881Speter      src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3178251881Speter                                : svn_dirent_basename(src_path, subpool);
3179251881Speter      dst_path
3180251881Speter        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3181251881Speter                                                   subpool)
3182251881Speter                     : svn_dirent_join(dst_path, src_basename, subpool);
3183251881Speter
3184251881Speter      err = try_copy(&timestamp_sleep,
3185251881Speter                     sources, dst_path,
3186251881Speter                     FALSE /* is_move */,
3187251881Speter                     TRUE /* allow_mixed_revisions */,
3188299742Sdim                     metadata_only,
3189251881Speter                     make_parents,
3190251881Speter                     ignore_externals,
3191299742Sdim                     pin_externals,
3192299742Sdim                     externals_to_pin,
3193251881Speter                     revprop_table,
3194251881Speter                     commit_callback, commit_baton,
3195251881Speter                     ctx,
3196251881Speter                     subpool);
3197251881Speter    }
3198251881Speter
3199251881Speter  /* Sleep if required.  DST_PATH is not a URL in these cases. */
3200251881Speter  if (timestamp_sleep)
3201251881Speter    svn_io_sleep_for_timestamps(dst_path, subpool);
3202251881Speter
3203251881Speter  svn_pool_destroy(subpool);
3204251881Speter  return svn_error_trace(err);
3205251881Speter}
3206251881Speter
3207251881Speter
3208251881Spetersvn_error_t *
3209251881Spetersvn_client_move7(const apr_array_header_t *src_paths,
3210251881Speter                 const char *dst_path,
3211251881Speter                 svn_boolean_t move_as_child,
3212251881Speter                 svn_boolean_t make_parents,
3213251881Speter                 svn_boolean_t allow_mixed_revisions,
3214251881Speter                 svn_boolean_t metadata_only,
3215251881Speter                 const apr_hash_t *revprop_table,
3216251881Speter                 svn_commit_callback2_t commit_callback,
3217251881Speter                 void *commit_baton,
3218251881Speter                 svn_client_ctx_t *ctx,
3219251881Speter                 apr_pool_t *pool)
3220251881Speter{
3221251881Speter  const svn_opt_revision_t head_revision
3222251881Speter    = { svn_opt_revision_head, { 0 } };
3223251881Speter  svn_error_t *err;
3224251881Speter  svn_boolean_t timestamp_sleep = FALSE;
3225251881Speter  int i;
3226251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
3227251881Speter  apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3228251881Speter                                  sizeof(const svn_client_copy_source_t *));
3229251881Speter
3230251881Speter  if (src_paths->nelts > 1 && !move_as_child)
3231251881Speter    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3232251881Speter                            NULL, NULL);
3233251881Speter
3234251881Speter  for (i = 0; i < src_paths->nelts; i++)
3235251881Speter    {
3236251881Speter      const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3237251881Speter      svn_client_copy_source_t *copy_source = apr_palloc(pool,
3238251881Speter                                                         sizeof(*copy_source));
3239251881Speter
3240251881Speter      copy_source->path = src_path;
3241251881Speter      copy_source->revision = &head_revision;
3242251881Speter      copy_source->peg_revision = &head_revision;
3243251881Speter
3244251881Speter      APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3245251881Speter    }
3246251881Speter
3247251881Speter  err = try_copy(&timestamp_sleep,
3248251881Speter                 sources, dst_path,
3249251881Speter                 TRUE /* is_move */,
3250251881Speter                 allow_mixed_revisions,
3251251881Speter                 metadata_only,
3252251881Speter                 make_parents,
3253251881Speter                 FALSE /* ignore_externals */,
3254299742Sdim                 FALSE /* pin_externals */,
3255299742Sdim                 NULL /* externals_to_pin */,
3256251881Speter                 revprop_table,
3257251881Speter                 commit_callback, commit_baton,
3258251881Speter                 ctx,
3259251881Speter                 subpool);
3260251881Speter
3261251881Speter  /* If the destination exists, try to move the sources as children of the
3262251881Speter     destination. */
3263251881Speter  if (move_as_child && err && (src_paths->nelts == 1)
3264251881Speter        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3265251881Speter            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3266251881Speter    {
3267251881Speter      const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3268251881Speter      const char *src_basename;
3269251881Speter      svn_boolean_t src_is_url = svn_path_is_url(src_path);
3270251881Speter      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3271251881Speter
3272251881Speter      svn_error_clear(err);
3273251881Speter      svn_pool_clear(subpool);
3274251881Speter
3275251881Speter      src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3276251881Speter                                : svn_dirent_basename(src_path, pool);
3277251881Speter      dst_path
3278251881Speter        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3279251881Speter                                                   subpool)
3280251881Speter                     : svn_dirent_join(dst_path, src_basename, subpool);
3281251881Speter
3282251881Speter      err = try_copy(&timestamp_sleep,
3283251881Speter                     sources, dst_path,
3284251881Speter                     TRUE /* is_move */,
3285251881Speter                     allow_mixed_revisions,
3286251881Speter                     metadata_only,
3287251881Speter                     make_parents,
3288251881Speter                     FALSE /* ignore_externals */,
3289299742Sdim                     FALSE /* pin_externals */,
3290299742Sdim                     NULL /* externals_to_pin */,
3291251881Speter                     revprop_table,
3292251881Speter                     commit_callback, commit_baton,
3293251881Speter                     ctx,
3294251881Speter                     subpool);
3295251881Speter    }
3296251881Speter
3297251881Speter  /* Sleep if required.  DST_PATH is not a URL in these cases. */
3298251881Speter  if (timestamp_sleep)
3299251881Speter    svn_io_sleep_for_timestamps(dst_path, subpool);
3300251881Speter
3301251881Speter  svn_pool_destroy(subpool);
3302251881Speter  return svn_error_trace(err);
3303251881Speter}
3304